Sidebar
betaThe collapsible navigation column of an application shell.
@zenoxai/ui/components/sidebar Markdown<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,iconshrinks it to a column of icons,nonekeeps it fixed.variant:sidebar(default),floatingorinset.SidebarMenuButtonrenders a<button>. For navigation, pass a link with its icon and label torender, as above. Settooltipso the label stays available when the sidebar collapses to icons.isActivemarks the current page with the selection colors, a primary-soft fill and text. Hover and press use the neutralsurface-mutedfill, so a hovered entry never looks like the current one (zx/selected-statein Layout and craft).sizeonSidebarMenuButtonsets the row height:sm28px,default32px,lg48px for two-line entries. The text stays at the 12.5px body size in every size, and in both sizes ofSidebarMenuSubButton.SidebarProvidertakesdefaultOpen, oropenandonOpenChangeto control the state. It stores the state in thesidebar_statecookie; 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.
<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
SidebarTriggertoggles 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.isActivehighlights the entry and setsaria-current="page": set it on the current page.- The labels of
SidebarTrigger,SidebarRailand the mobile sheet are fixed English strings. Pass a translatedaria-labeltoSidebarTriggerandSidebarRailwhen 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.
| Prop | Type | Default |
|---|---|---|
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.
| Prop | Type | Default |
|---|---|---|
render | ReactElement<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.
| Prop | Type | Default |
|---|---|---|
render | ReactElement<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.
| Prop | Type | Default |
|---|---|---|
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.
| Prop | Type | Default |
|---|---|---|
render | ReactElement<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. | - |
showOnHover | booleanOn 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".
| Prop | Type | Default |
|---|---|---|
isActive | booleanMarks the entry as the current page: highlight and aria-current="page". | false |
render | ReactElement<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" | nullHeight: `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 |
tooltip | string | (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.
| Prop | Type | Default |
|---|---|---|
showIcon | booleanAdds 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.
| Prop | Type | Default |
|---|---|---|
isActive | booleanMarks the entry as the current page: highlight and aria-current="page". | false |
render | ReactElement<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.
| Prop | Type | Default |
|---|---|---|
defaultOpen | booleanInitial 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. | - |
open | booleanControlled 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
| Prop | Type | Default |
|---|---|---|
orientation | "horizontal" | "vertical"The orientation of the separator. | vertical |
render | ReactElement<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.
| Prop | Type | Default |
|---|---|---|
focusableWhenDisabled | booleanWhether the button should be focusable when disabled. | false |
nativeButton | booleanWhether 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 |
render | ReactElement<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" | nullHeight: `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" | nullVisual 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.