Northwind Shop
The page content goes here.
import { Flex } from "@hel/ui/flex"
import { SidebarInset, SidebarProvider, SidebarTrigger } from "@hel/ui/sidebar"
import { Text } from "@hel/ui/text"When to use it#
Use a sidebar as the main navigation of an app with many areas, such as an admin panel, a dashboard or a back office. It stays on screen, shows where people are, and can fold away to give the page more room.
A website with a handful of pages is usually better served by a top bar. For navigation inside one page, use Tabs.
Import#
import {
SidebarProvider,
Sidebar,
SidebarTrigger,
SidebarInset,
SidebarHeader,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuItem,
SidebarMenuButton,
SidebarMenuSub,
SidebarMenuSubTrigger,
SidebarMenuSubContent,
SidebarMenuSubItem,
SidebarMenuSubButton,
useSidebar,
} from "@hel/ui/sidebar"Anatomy#
A sidebar is built from small parts, so you decide what goes in it.
SidebarProviderwraps the app layout. It holds whether the sidebar is open, and handles the keyboard shortcut.Sidebaris the panel. Inside it,SidebarHeaderandSidebarFooterstay in place, andSidebarContentscrolls between them.SidebarGroupgathers related items under an optionalSidebarGroupLabel.SidebarMenuis the list,SidebarMenuItemis a row in it, andSidebarMenuButtonis the button inside the row.SidebarMenuSubmakes an item that opens a nested list. Its trigger, content, items and buttons come in the same four parts.SidebarInsetis the page next to the sidebar.SidebarTriggeris the button that folds the sidebar.
<SidebarProvider>
<Sidebar>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Sales</SidebarGroupLabel>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton icon={ShoppingCart} badge="12" isActive>
Orders
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroup>
</SidebarContent>
</Sidebar>
<SidebarInset>{children}</SidebarInset>
</SidebarProvider>The examples on this page add contained to the provider. That lays the sidebar out inside a box of fixed height, instead of the whole window, so it fits in the preview. In a real app, leave it off.
Menu rows#
A SidebarMenuButton takes a label as its children, and these props:
iconshows an icon before the label.leadingputs any element there instead, such as an avatar.badgeshows a count at the end, such as unread orders.kbdshows a shortcut hint. It is only a label.hotkeyshows the hint too, and makes the shortcut work from anywhere on the page.isActivemarks the current page, with the active look andaria-current.tooltipis the text shown beside the row when the sidebar is folded to icons.
import { MessageSquare, Package } from "lucide-react"
import {Examples#
Folding to icons#
collapsible decides how the sidebar folds. "offcanvas", the default, slides it out of view. "icon" shrinks it to a narrow strip of icons, and each row shows its tooltip. "none" never folds.
The layout of a folded sidebar is your call. Classes such as group-data-[collapsible=icon]/sidebar:hidden hide a part, such as the shop name, when the strip is narrow, as the example does.
Northwind Shop
Collapses to icons. Hover an icon for its name.
import { Flex } from "@hel/ui/flex"
import { SidebarInset, SidebarProvider, SidebarTrigger } from "@hel/ui/sidebar"
import { Text } from "@hel/ui/text"Controlling the sidebar#
SidebarTrigger folds and opens the sidebar, and Ctrl or Cmd with B does the same. useSidebar gives your own components the state: state is "expanded" or "collapsed", and setOpen and toggleSidebar change it. Use open and onOpenChange on the provider to keep the state yourself, for example to remember it between visits. shortcut changes the key, or turns it off with false.
Northwind Shop
Ctrl or Cmd with B toggles it as well.
The sidebar is expanded.
import { Button } from "@hel/ui/button"
import { Flex } from "@hel/ui/flex"
import {On phones#
Below mobileBreakpoint, 768px by default, the sidebar is not shown beside the page. It opens as a drawer over it, and SidebarTrigger opens it. useSidebar gives you isMobile and openMobile to react to it. Resize the window narrower than 768px to see it.
Looks#
variant changes how the panel sits on the page. "floating" keeps it away from the screen edges, with a small margin. "inset" puts the sidebar on the page background and turns the page into a raised card, using SidebarInset. side puts it at the "start" or the "end" of the page, which follows the reading direction, or on a physical "left" or "right". gap sets the space between groups.
With your router#
Rows are buttons by default. To make a row a link, pass your framework's link to render, and the styling stays.
import Link from "next/link"
<SidebarMenuButton render={<Link href="/orders" />} icon={ShoppingCart}>
Orders
</SidebarMenuButton>Props#
SidebarProvider#
| Prop | Type | Default |
|---|---|---|
defaultOpen | boolean | true |
open / onOpenChange | boolean / (open: boolean) => void | none |
shortcut | string | false | "b" |
width | string | "288px" |
iconWidth | string | "60px" |
contained | boolean | false |
mobileBreakpoint | number | 768 |
Sidebar#
| Prop | Type | Default |
|---|---|---|
variant | "default" | "floating" | "inset" | "default" |
side | "start" | "end" | "left" | "right" | "start" |
collapsible | "offcanvas" | "icon" | "none" | "offcanvas" |
gap | "xs" to "3xl" | "md" |
SidebarMenuButton#
| Prop | Type | Default |
|---|---|---|
icon / leading | LucideIcon / ReactNode | none |
badge | ReactNode | none |
kbd | ReactNode | none |
hotkey | string | none |
isActive | boolean | false |
tooltip | ReactNode | none |
render | ReactElement | A button |
SidebarMenuSubTrigger#
| Prop | Type | Default |
|---|---|---|
icon | LucideIcon | none |
isActive | boolean | false |
tooltip | ReactNode | none |
SidebarMenuSub takes defaultOpen, open and disabled. isActive on a sub trigger keeps the section highlighted while the sidebar is folded to icons.