Efniv0.1

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

KeyAction
Cmd/Ctrl + BToggle the sidebar
TabMove through the menu items
Enter / SpaceActivate 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 Escape handling.
  • The active item is marked with data-active, not colour alone.

Guidelines

Do
  • 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
  • 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

PropTypeDefaultDescription
SidebarProvider.openboolean—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.defaultOpenbooleantrueInitial 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.tooltipReact.ReactNode—Shown as a tooltip only while the sidebar is collapsed to icons.
SidebarMenuButton.isActivebooleanfalseEmits data-active for the current page.
SidebarMenuButton.variant"default" | "outline""default"Visual style.
SidebarMenuButton.size"default" | "sm" | "lg""default"Height and padding.
SidebarMenuAction.showOnHoverbooleanfalseKeep the action hidden until the row is hovered or focused.
SidebarMenuSkeleton.showIconbooleanfalseReserve a leading icon block, so the skeleton matches the real row.
SidebarMenuSubButton.size"sm" | "md""md"Text size of the sub-item.
…propsReact.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.