Skip to content
UI

Sidebar

beta

The collapsible navigation column of an application shell.

@zenoxai/ui/components/sidebar Markdown
Alerts
24 open alerts. Press Ctrl+B or Cmd+B to collapse the sidebar.
<div className="h-96 w-full overflow-hidden rounded-md border border-border">
  <SidebarProvider className="relative h-full min-h-0">
    <Sidebar collapsible="icon" className="absolute h-full">
      <SidebarHeader>
        <SidebarMenu>
          <SidebarMenuItem>
            <SidebarMenuButton
              size="lg"
              render={
                <a href="#home">
                  <div className="flex size-8 shrink-0 items-center justify-center rounded-md bg-primary text-primary-foreground">
                    <ShieldIcon />
                  </div>
                  <div className="flex flex-col">
                    <span className="font-medium">Vydar</span>
                    <span className="text-xs text-text-muted">
                      Acme Corp
                    </span>
                  </div>
                </a>
              }
            />
          </SidebarMenuItem>
        </SidebarMenu>
      </SidebarHeader>
      <SidebarContent>
        <SidebarGroup>
          <SidebarGroupLabel>Monitor</SidebarGroupLabel>
          <SidebarGroupContent>
            <SidebarMenu>
              <SidebarMenuItem>
                <SidebarMenuButton
                  tooltip="Dashboard"
                  render={
                    <a href="#dashboard">
                      <LayoutDashboardIcon />
                      <span>Dashboard</span>
                    </a>
                  }
                />
              </SidebarMenuItem>
              <SidebarMenuItem>
                <SidebarMenuButton
                  tooltip="Alerts"
                  isActive
                  render={
                    <a href="#alerts" aria-current="page">
                      <BellIcon />
                      <span>Alerts</span>
                    </a>
                  }
                />
                <SidebarMenuBadge>24</SidebarMenuBadge>
              </SidebarMenuItem>
              <SidebarMenuItem>
                <SidebarMenuButton
                  tooltip="Assets"
                  render={
                    <a href="#assets">
                      <ServerIcon />
                      <span>Assets</span>
                    </a>
                  }
                />
              </SidebarMenuItem>
            </SidebarMenu>
          </SidebarGroupContent>
        </SidebarGroup>
        <SidebarGroup>
          <SidebarGroupLabel>Analyze</SidebarGroupLabel>
          <SidebarGroupContent>
            <SidebarMenu>
              <SidebarMenuItem>
                <SidebarMenuButton
                  tooltip="Reports"
                  render={
                    <a href="#reports">
                      <FileTextIcon />
                      <span>Reports</span>
                    </a>
                  }
                />
              </SidebarMenuItem>
            </SidebarMenu>
          </SidebarGroupContent>
        </SidebarGroup>
      </SidebarContent>
      <SidebarFooter>
        <SidebarMenu>
          <SidebarMenuItem>
            <SidebarMenuButton
              tooltip="Settings"
              render={
                <a href="#settings">
                  <SettingsIcon />
                  <span>Settings</span>
                </a>
              }
            />
          </SidebarMenuItem>
        </SidebarMenu>
      </SidebarFooter>
    </Sidebar>
    <SidebarInset>
      <header className="flex h-12 items-center gap-2 border-b border-border px-3">
        <SidebarTrigger />
        <span className="text-sm font-medium text-text-primary">
          Alerts
        </span>
      </header>
      <div className="p-4 text-sm text-text-secondary">
        24 open alerts. Press Ctrl+B or Cmd+B to collapse the sidebar.
      </div>
    </SidebarInset>
  </SidebarProvider>
</div>

Usage

import {
  Sidebar,
  SidebarContent,
  SidebarGroup,
  SidebarGroupContent,
  SidebarGroupLabel,
  SidebarInset,
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,
  SidebarProvider,
  SidebarTrigger,
} from "@zenoxai/ui/components/sidebar"
<SidebarProvider>
  <Sidebar collapsible="icon">
    <SidebarContent>
      <SidebarGroup>
        <SidebarGroupLabel>Monitor</SidebarGroupLabel>
        <SidebarGroupContent>
          <SidebarMenu>
            <SidebarMenuItem>
              <SidebarMenuButton
                tooltip="Alerts"
                render={
                  <a href="/alerts">
                    <BellIcon />
                    <span>Alerts</span>
                  </a>
                }
              />
            </SidebarMenuItem>
          </SidebarMenu>
        </SidebarGroupContent>
      </SidebarGroup>
    </SidebarContent>
  </Sidebar>
  <SidebarInset>
    <SidebarTrigger />
    {children}
  </SidebarInset>
</SidebarProvider>

Zenox apps get their shell, sidebar included, from the platform package. Use this component only to build the shell of a new app that does not use the platform package yet. It is the shadcn/ui sidebar on Zenox tokens, and its API may change.

The parts nest in this order: SidebarProvider around the whole layout, then Sidebar and SidebarInset side by side. Inside Sidebar, SidebarHeader and SidebarFooter stay put while SidebarContent scrolls. Group links with SidebarGroup and a SidebarGroupLabel, and list them with SidebarMenu, SidebarMenuItem and SidebarMenuButton.

  • collapsible: offcanvas (default) slides the sidebar out of view, icon shrinks it to a column of icons, none keeps it fixed.
  • variant: sidebar (default), floating or inset.
  • SidebarMenuButton renders a <button>. For navigation, pass a link with its icon and label to render, as above. Set tooltip so the label stays available when the sidebar collapses to icons.
  • isActive marks the current page with the selection colors, a primary-soft fill and text. Hover and press use the neutral surface-muted fill, so a hovered entry never looks like the current one (zx/selected-state in Layout and craft).
  • size on SidebarMenuButton sets the row height: sm 28px, default 32px, lg 48px for two-line entries. The text stays at the 12.5px body size in every size, and in both sizes of SidebarMenuSubButton.
  • SidebarProvider takes defaultOpen, or open and onOpenChange to control the state. It stores the state in the sidebar_state cookie; read it on the server to render the sidebar in the state the user left it.

The demos on this page override the full-height layout (relative h-full min-h-0 on the provider, absolute h-full on Sidebar) so the sidebar fits in a box. In an app, render the provider at the root and leave those classes out.

Examples

Sub-items and group action

SidebarMenuSub nests links under an item. SidebarGroupAction adds an icon button to the group label; give it an aria-label.

142 domains across 3 tenants.
<div className="h-64 w-full overflow-hidden rounded-md border border-border">
  <SidebarProvider className="h-full min-h-0">
    <Sidebar collapsible="none" className="border-r border-border">
      <SidebarContent>
        <SidebarGroup>
          <SidebarGroupLabel>Inventory</SidebarGroupLabel>
          <SidebarGroupAction aria-label="Add asset">
            <PlusIcon />
          </SidebarGroupAction>
          <SidebarGroupContent>
            <SidebarMenu>
              <SidebarMenuItem>
                <SidebarMenuButton
                  render={
                    <a href="#assets">
                      <ServerIcon />
                      <span>Assets</span>
                    </a>
                  }
                />
                <SidebarMenuSub>
                  <SidebarMenuSubItem>
                    <SidebarMenuSubButton
                      href="#domains"
                      isActive
                      aria-current="page"
                    >
                      <GlobeIcon />
                      <span>Domains</span>
                    </SidebarMenuSubButton>
                  </SidebarMenuSubItem>
                  <SidebarMenuSubItem>
                    <SidebarMenuSubButton href="#hosts">
                      <span>Hosts</span>
                    </SidebarMenuSubButton>
                  </SidebarMenuSubItem>
                  <SidebarMenuSubItem>
                    <SidebarMenuSubButton href="#cloud-accounts">
                      <span>Cloud accounts</span>
                    </SidebarMenuSubButton>
                  </SidebarMenuSubItem>
                </SidebarMenuSub>
              </SidebarMenuItem>
            </SidebarMenu>
          </SidebarGroupContent>
        </SidebarGroup>
      </SidebarContent>
    </Sidebar>
    <SidebarInset>
      <div className="p-4 text-sm text-text-secondary">
        142 domains across 3 tenants.
      </div>
    </SidebarInset>
  </SidebarProvider>
</div>

Accessibility

  • SidebarTrigger toggles the sidebar, and so does Ctrl+B or Cmd+B, except while typing in a text field. Below 768px the sidebar renders as a Sheet, with focus trapped inside while it is open.
  • isActive highlights the entry and sets aria-current="page": set it on the current page.
  • The labels of SidebarTrigger, SidebarRail and the mobile sheet are fixed English strings. Pass a translated aria-label to SidebarTrigger and SidebarRail when the interface is not in English.

API reference

Generated from the TypeScript source. className, style and children are accepted everywhere and omitted below.

Sidebar

Sidebar is the app's main navigation column. On desktop it collapses off canvas or to a rail of icons; on mobile it opens as a Sheet. Wrap the layout in SidebarProvider, put the Sidebar beside a SidebarInset that holds the page, and toggle it with SidebarTrigger. For links in a site header, use NavigationMenu.

PropTypeDefault
collapsible"none" | "icon" | "offcanvas"

How it collapses on desktop: `offcanvas` (default) slides it out of view; `icon` shrinks it to a 48px rail of icons, with SidebarMenuButton tooltips; `none` keeps it open.

offcanvas
side"left" | "right"

Edge of the screen it sits on. Defaults to left.

left
variant"inset" | "sidebar" | "floating"

`sidebar` (default) is flush with the screen edge, behind a border; `floating` is a rounded panel inset from the edges; `inset` renders the SidebarInset beside it as a rounded card.

sidebar

SidebarContent

SidebarContent is the scrolling middle of the sidebar, with its groups.

No props of its own. Accepts the props of the element it renders.

SidebarFooter

SidebarFooter is the bottom band of the sidebar, below the scrolling content, for the user menu.

No props of its own. Accepts the props of the element it renders.

SidebarGroup

SidebarGroup is a section of the sidebar, with an optional SidebarGroupLabel and SidebarGroupAction above its SidebarGroupContent.

No props of its own. Accepts the props of the element it renders.

SidebarGroupAction

SidebarGroupAction is an icon button at the top-right of a group, such as "Add project". It needs aria-label.

PropTypeDefault
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, {}>

Allows you to replace the component's HTML element with a different tag, or compose it with another component.

-

SidebarGroupContent

No props of its own. Accepts the props of the element it renders.

SidebarGroupLabel

SidebarGroupLabel is the small heading of a group; it fades out when the sidebar collapses to icons.

PropTypeDefault
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, {}>

Allows you to replace the component's HTML element with a different tag, or compose it with another component.

-

SidebarHeader

SidebarHeader is the top band of the sidebar, above the scrolling content, for the logo or a workspace switcher.

No props of its own. Accepts the props of the element it renders.

SidebarInput

SidebarInput is an Input sized for the sidebar, such as a search field.

PropTypeDefault
size"default" | "sm" | "lg"

Height: `sm` 28px, `default` 32px, `lg` 36px, the heights of the Button sizes of the same names. Replaces the native `size` attribute (a width in characters); set the width with a class instead.

-

SidebarInset

SidebarInset is the <main> element that holds the page beside the sidebar. With the sidebar's `variant="inset"`, it renders as a rounded card.

No props of its own. Accepts the props of the element it renders.

SidebarMenu

SidebarMenu is the list of navigation entries in a group; each SidebarMenuItem holds a SidebarMenuButton.

No props of its own. Accepts the props of the element it renders.

SidebarMenuAction

SidebarMenuAction is an icon button at the end of a menu entry, such as a "More" menu. It needs aria-label.

PropTypeDefault
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, {}>

Allows you to replace the component's HTML element with a different tag, or compose it with another component.

-
showOnHoverboolean

On desktop, hides it until the entry is hovered or focused.

false

SidebarMenuBadge

SidebarMenuBadge is a count at the end of a menu entry, such as unread items; it hides when the sidebar collapses to icons.

No props of its own. Accepts the props of the element it renders.

SidebarMenuButton

SidebarMenuButton is a navigation entry, an icon and a label, rendered as a button or, with `render`, as a link. On the entry of the current page set `isActive`, which highlights it and sets aria-current="page".

PropTypeDefault
isActiveboolean

Marks the entry as the current page: highlight and aria-current="page".

false
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, {}>

Allows you to replace the component's HTML element with a different tag, or compose it with another component.

-
size"default" | "sm" | "lg" | null

Height: `sm` 28px, `default` 32px, `lg` 48px, for an entry with two lines such as a workspace switcher. Every size uses the 12.5px body text.

default
tooltipstring | (TooltipPopupProps & Pick<TooltipPositionerProps, "align" | "side" | "sideOffset" | "alignOffset">)

Label shown beside the entry while the sidebar is collapsed to icons, as a string or as TooltipContent props.

-
variant"default" | "outline" | null

`default` has no fill until hover; `outline` adds a background and a border.

default

SidebarMenuItem

No props of its own. Accepts the props of the element it renders.

SidebarMenuSkeleton

SidebarMenuSkeleton is the loading placeholder of a menu entry, with a text bar of random width.

PropTypeDefault
showIconboolean

Adds a square placeholder for the icon.

false

SidebarMenuSub

SidebarMenuSub is a nested list of entries under a SidebarMenuItem, indented along a guide line; it hides when the sidebar collapses to icons.

No props of its own. Accepts the props of the element it renders.

SidebarMenuSubButton

SidebarMenuSubButton is an entry of a SidebarMenuSub. It renders an <a>, so pass `href`, or `render` the router's link.

PropTypeDefault
isActiveboolean

Marks the entry as the current page: highlight and aria-current="page".

false
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, {}>

Allows you to replace the component's HTML element with a different tag, or compose it with another component.

-
size"sm" | "md"

Kept for compatibility with the shadcn API: `md` (default) and `sm` both render a 28px row with the 12.5px body text, since smaller text is hard to read in navigation. The value is exposed as `data-size`.

md

SidebarMenuSubItem

No props of its own. Accepts the props of the element it renders.

SidebarProvider

SidebarProvider holds the open state of the Sidebar and lays the sidebar and the page out side by side; wrap the whole app shell in it. It toggles the sidebar on Ctrl+B or Cmd+B, and saves the desktop state in the `sidebar_state` cookie for seven days: read that cookie on the server and pass it as `defaultOpen` to restore the state.

PropTypeDefault
defaultOpenboolean

Initial desktop state when uncontrolled. Defaults to true (open).

true
onOpenChange((open: boolean) => void)

Called with the new desktop state. The mobile sheet is not reported.

-
openboolean

Controlled desktop state; pair it with `onOpenChange`.

-

SidebarRail

SidebarRail is a thin strip along the sidebar's inner edge that toggles it on click, a pointer shortcut. It is out of the tab order, so keep a SidebarTrigger for keyboard users.

No props of its own. Accepts the props of the element it renders.

SidebarSeparator

PropTypeDefault
orientation"horizontal" | "vertical"

The orientation of the separator.

vertical
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, SeparatorState>

Allows you to replace the component's HTML element with a different tag, or compose it with another component.

-

SidebarTrigger

SidebarTrigger is the icon button that opens and closes the sidebar, on mobile too. Its accessible name is the English "Toggle sidebar"; pass a translated aria-label.

PropTypeDefault
focusableWhenDisabledboolean

Whether the button should be focusable when disabled.

false
nativeButtonboolean

Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`).

true
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, ButtonState>

Allows you to replace the component's HTML element with a different tag, or compose it with another component.

-
size"default" | "xs" | "sm" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg" | null

Height: `xs` 24px, `sm` 28px, `default` 32px, `lg` 36px. The `icon-*` sizes are square buttons of the same heights for a single icon. The icon follows the size name: 12px at `xs`, 14px at `sm`, 16px at `default` and `lg`.

-
variant"link" | "default" | "destructive" | "outline" | "secondary" | "ghost" | null

Visual weight. `default` is the filled primary action, one per view; `outline` and `secondary` are for the other actions; `ghost` has no fill until hover, for toolbars and icon buttons; `destructive` is for delete and other irreversible actions; `link` looks like inline text.

-

useSidebar

useSidebar returns the sidebar state and its setters (`open`, `toggleSidebar`, `isMobile`, `openMobile`); call it inside SidebarProvider.

No props of its own. Accepts the props of the element it renders.