Components
Sidebar
The navigation shell for a dashboard: a collapsible rail of grouped menu items, with a content area beside it. Holds the open state and the keyboard shortcut; persisting that state is the app's job.
When to usefor an application's primary navigation — the frame the rest of the UI lives in. For a menu that opens from a button, reach for Dropdown menu; for a panel that slides over the content, Sheet.
Import
import {
Sidebar, SidebarProvider, SidebarInset, SidebarTrigger,
SidebarHeader, SidebarContent, SidebarFooter,
SidebarGroup, SidebarGroupLabel, SidebarGroupContent,
SidebarMenu, SidebarMenuItem, SidebarMenuButton,
} from "@ravnhq/efni/sidebar"Default
SidebarProvider wraps the sidebar and the content. Cmd/Ctrl+B toggles it. The rail is position: fixed, because it is a page shell — the previews below are confined with contain: layout so they do not escape onto this page.
Content sits in SidebarInset.
<SidebarProvider>
<Sidebar>
<SidebarHeader>
<SidebarInput aria-label="Search missions" placeholder="Search" />
</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Operations</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton isActive tooltip="Missions">
<Icon icon={Rocket} />
<span>Missions</span>
</SidebarMenuButton>
<SidebarMenuBadge>12</SidebarMenuBadge>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>OP-0142</SidebarFooter>
</Sidebar>
<SidebarInset>
<SidebarTrigger />
</SidebarInset>
</SidebarProvider>Collapsing to icons
collapsible="icon" keeps the rail visible at icon width. The tooltip on each button only shows while collapsed, which is why it exists.
Content sits in SidebarInset.
<Sidebar collapsible="icon">
...
<SidebarMenuButton tooltip="Missions">
<Icon icon={Rocket} />
<span>Missions</span>
</SidebarMenuButton>
</Sidebar>Controlling the state yourself
The component keeps no cookie and no localStorage. Pass open and onOpenChange and persist it wherever your app already persists preferences.
const [open, setOpen] = useState(true);
<SidebarProvider open={open} onOpenChange={(next) => {
setOpen(next);
savePreference("sidebar", next); // your app, not the library
}}>Accessibility
| Key | Action |
|---|---|
| Cmd/Ctrl + B | Toggle the sidebar |
| Tab | Move through the menu items |
| Enter / Space | Activate the focused item |
- The trigger and the rail both carry
aria-label, since neither has a visible text label. - On a viewport under 768px the sidebar becomes a Sheet, which brings its own focus trap and
Escapehandling. - The active item is marked with
data-active, not colour alone.
Guidelines
- Give every SidebarMenuButton a tooltip — it is the only label left once the rail collapses to icons.
- Put page content in SidebarInset so the layout reflows with the sidebar.
- Group items with SidebarGroup and label the groups; a flat list of twelve links is hard to scan.
- Don't nest a second Sidebar inside SidebarInset; one shell per screen.
- Don't rely on the library to remember the collapsed state — it deliberately does not.
- Don't put a form in the footer; the footer is for identity and status, not input.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
SidebarProvider.open | boolean | — | Controlled open state. Omit to let the component own it from defaultOpen. |
SidebarProvider.onOpenChange | (open: boolean) => void | — | Fires on toggle. Persist here if you want persistence. |
SidebarProvider.defaultOpen | boolean | true | Initial state when uncontrolled. |
side | "left" | "right" | "left" | Which edge the rail sits on. |
variant | "sidebar" | "floating" | "inset" | "sidebar" | Flush against the edge, a floating card, or inset with the content. |
collapsible | "icon" | "none" | "offcanvas" | "offcanvas" | Slides away, collapses to an icon rail, or never collapses. |
SidebarMenuButton.tooltip | React.ReactNode | — | Shown as a tooltip only while the sidebar is collapsed to icons. |
SidebarMenuButton.isActive | boolean | false | Emits data-active for the current page. |
SidebarMenuButton.variant | "default" | "outline" | "default" | Visual style. |
SidebarMenuButton.size | "default" | "sm" | "lg" | "default" | Height and padding. |
SidebarMenuAction.showOnHover | boolean | false | Keep the action hidden until the row is hovered or focused. |
SidebarMenuSkeleton.showIcon | boolean | false | Reserve a leading icon block, so the skeleton matches the real row. |
SidebarMenuSubButton.size | "sm" | "md" | "md" | Text size of the sub-item. |
…props | React.ComponentProps<"div"> | — | Everything Sidebar passes to the element it renders. |
useSidebar() | { state, open, setOpen, openMobile, setOpenMobile, isMobile, toggleSidebar } | — | Read or drive the sidebar from anywhere inside the provider. |