Sidebar

The main navigation of an app, with groups, nested items, an icon-only mode and a drawer on phones.

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.

  • SidebarProvider wraps the app layout. It holds whether the sidebar is open, and handles the keyboard shortcut.
  • Sidebar is the panel. Inside it, SidebarHeader and SidebarFooter stay in place, and SidebarContent scrolls between them.
  • SidebarGroup gathers related items under an optional SidebarGroupLabel.
  • SidebarMenu is the list, SidebarMenuItem is a row in it, and SidebarMenuButton is the button inside the row.
  • SidebarMenuSub makes an item that opens a nested list. Its trigger, content, items and buttons come in the same four parts.
  • SidebarInset is the page next to the sidebar. SidebarTrigger is 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.

A SidebarMenuButton takes a label as its children, and these props:

  • icon shows an icon before the label. leading puts any element there instead, such as an avatar.
  • badge shows a count at the end, such as unread orders.
  • kbd shows a shortcut hint. It is only a label. hotkey shows the hint too, and makes the shortcut work from anywhere on the page.
  • isActive marks the current page, with the active look and aria-current.
  • tooltip is 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

PropTypeDefault
defaultOpenbooleantrue
open / onOpenChangeboolean / (open: boolean) => voidnone
shortcutstring | false"b"
widthstring"288px"
iconWidthstring"60px"
containedbooleanfalse
mobileBreakpointnumber768
PropTypeDefault
variant"default" | "floating" | "inset""default"
side"start" | "end" | "left" | "right""start"
collapsible"offcanvas" | "icon" | "none""offcanvas"
gap"xs" to "3xl""md"

SidebarMenuButton

PropTypeDefault
icon / leadingLucideIcon / ReactNodenone
badgeReactNodenone
kbdReactNodenone
hotkeystringnone
isActivebooleanfalse
tooltipReactNodenone
renderReactElementA button

SidebarMenuSubTrigger

PropTypeDefault
iconLucideIconnone
isActivebooleanfalse
tooltipReactNodenone

SidebarMenuSub takes defaultOpen, open and disabled. isActive on a sub trigger keeps the section highlighted while the sidebar is folded to icons.