Navbar
stablev0.2.0The primary structural navigation container of every Filigran product: a vertical, collapsible sidebar hosting a header slot (typically ProductSwitcher), a scrollable list of NavbarItem/NavbarSubmenu rows (separated into groups by NavbarSeparator, optionally labelled by NavbarTitle), an optional footer slot, and a built-in collapse toggle. A thin composition root, not a monolith — it owns the collapsed/expanded state, the <nav> landmark, and the width transition, sharing collapsed with its descendants via NavbarContext rather than rendering their content itself. Renders a real <nav aria-label> landmark, a real accessibility improvement over both opencti and openaev's current MUI Drawer-based sidebars (neither renders a landmark today).
Variants
Expanded
Collapsed
Expanded, submenuShowIcons=false
Long labels (hover a row to read it in full)
Examples
// Uncontrolled — Navbar manages its own collapsed state internally
<Navbar aria-label="Main navigation" header={<ProductSwitcher .../>}>
<NavbarItem icon={<Icon name="layout-panel-left" size={16} />} aria-current="page">Dashboard</NavbarItem>
<NavbarSeparator />
<NavbarSubmenu label="Knowledge" icon={<Icon name="book" size={16} />}>
<NavbarSubmenuItem href="/reports">Reports</NavbarSubmenuItem>
</NavbarSubmenu>
</Navbar>// Controlled collapse, persisted by the consuming product
<Navbar
aria-label="Main navigation"
collapsed={collapsed}
onCollapsedChange={(next) => { setCollapsed(next); localStorage.setItem('navOpen', String(!next)); }}
>
...
</Navbar>// Optional footer slot, rendered above the built-in collapse toggle
<Navbar aria-label="Main navigation" footer={<NavbarItem icon={<Icon name="circle-question-mark" size={16} />}>Help</NavbarItem>}>
...
</Navbar>// Global toggle for every NavbarSubmenuItem's leading icon — wire to a user setting
// (opencti's real me.submenu_show_icons GraphQL preference is the confirmed precedent).
// Does not affect NavbarItem's own icon, including a NavbarSubmenu trigger row.
<Navbar aria-label="Main navigation" submenuShowIcons={userPrefersSubmenuIcons}>
...
</Navbar>// Label a group of rows with a section title (hidden on the collapsed rail unless given an icon)
<Navbar aria-label="Main navigation">
<NavbarTitle>Navigation</NavbarTitle>
<NavbarItem icon={<Icon name="layout-panel-left" size={16} />} aria-current="page">Dashboard</NavbarItem>
<NavbarSeparator />
<NavbarTitle icon={<Icon name="book" size={16} />}>Knowledge</NavbarTitle>
<NavbarSubmenu label="Reports" icon={<Icon name="file-text" size={16} />}>
<NavbarSubmenuItem href="/reports/all">All reports</NavbarSubmenuItem>
</NavbarSubmenu>
</Navbar>Props
| Prop | Type |
|---|---|
aria-label | string (required) — accessible landmark name, no default (product/locale-specific) |
collapsed | boolean — controlled collapsed state, omit for uncontrolled |
defaultCollapsed | boolean — initial collapsed state (uncontrolled), default false |
onCollapsedChange | (collapsed: boolean) => void — notifies on footer-toggle-driven changes |
submenuShowIcons | boolean — global toggle for every descendant NavbarSubmenuItem's leading icon, default true. Wire to a user setting (opencti's real submenu_show_icons GraphQL preference is the confirmed precedent); Navbar never reads that preference itself. ANDed with each row's own showIcon; does not affect NavbarItem's own icon, including a NavbarSubmenu trigger row. |
header | ReactNode — typically a <ProductSwitcher>, rendered in the 68px header row |
children | ReactNode — NavbarItem / NavbarSubmenu / NavbarSeparator content |
footer | ReactNode — rendered above the built-in collapse toggle (e.g. a help/settings NavbarItem) |
NavbarSeparator (named export) | Divider between item groups — a plain <hr>, no props of its own beyond native <hr> attributes |
NavbarTitle (named export) | Non-interactive section label introducing a group of rows, e.g. "Components". React port of Figma's `navbar title`. Composes Text for Content/caption typography, text-default-disabled, 28px tall. Renders an <h2> heading by default (a navigable group title for AT); pass `as` ("h2".."h6" for outline depth, or "p"/"div"/"span" to opt out of the heading outline when the Navbar is chrome mounted ahead of the page's own <h1>). Optional `icon` prop = the glyph shown centred on the collapsed 48px rail (label kept as the accessible name); with no icon the title is hidden while collapsed. Reads NavbarContext, no props of its own beyond `icon` + `as` + native heading attributes. |
Accessibility
Root element is a native <nav aria-label={ariaLabel}> landmark (required prop, no default — the exact wording is product/locale-specific and cannot be safely defaulted, §5.3). No interaction of its own beyond Tab reaching interactive children in DOM order (header/ProductSwitcher → items/submenus → footer collapse toggle) — no custom keyboard handling at the Navbar level (§5.2). Collapsed/expanded is a purely visual/layout state, not announced via a live region (§5.3) — aria-expanded is used at the item/submenu level (NavbarSubmenu), never at the Navbar root, since the root has no ARIA state of its own (it's a landmark, not a disclosure widget). Focus management on collapse (an open submenu's focus returning to its parent trigger) is implemented by NavbarSubmenu reacting to NavbarContext — Navbar only needs to expose collapsed correctly, not implement the mechanism itself (§5.2). Width transition: 150ms, Tailwind's default easing, with motion-reduce:transition-none — reused from Tooltip's already-proven 150ms duration rather than opencti's real 225ms (LeftBar.jsx:438-441), because opencti's transition never respects prefers-reduced-motion anywhere in its codebase (repo-wide grep confirmed zero matches) — this is the first prefers-reduced-motion implementation in the library (§3.5). The built-in footer collapse-toggle row deliberately diverges from opencti's real implementation (LeftBar.jsx:812-814, label={t_i18n('Collapse')} unconditionally, icon-only swap): a static 'Collapse' label read by AT users while already collapsed is misleading (the next action is actually 'expand'), the same class of clarity issue already corrected on ProductSwitcher's own chevron — so the label here is state-dependent ('Collapse' expanded / 'Expand' collapsed) instead of a static string, a disclosed refinement beyond the RFC's literal §4.2 text, not a silent change to an arbitrated decision.