Skip to content
buttery

v1.0.0

Source on GitHub

Version 1.0.0

Sidebar

An app's navigation down the side: a workspace switcher, items, titled sections, groups that fold, and the pages visited lately.

import { Sidebar } from "@butteryui/react";

Demo

When to use it

Use it for

  • A dashboard's navigation, in an AppShell layout="sidebar"
  • An app with more pages than a top bar holds, grouped into sections

Not for

  • A marketing site's menus: use NavBar
  • Sections of one long page: use SubNav

Examples

Dashboard nav

A dashboard's navigation: top pages, Recent, a titled section and a reports group that folds.

Roastery dashboard

A dashboard shell: the sidebar beside the top bar and the page, which follows the item chosen.

Home

Twelve orders to roast and pack today.

Router nav

A router takes over every item through onNavigate; the current page follows what it shows beside the nav.

Home

Twelve orders to roast and pack today.

Variants and states

Choose a page inside a group and it shows up under Recent; open and closed groups are remembered.

Props

Sidebar

PropTypeDefaultDescription
labelstring"Main"Names the navigation for screen readers. Defaults to "Main".
switcherReactNodeThe workspace switcher at the top, such as an AccountMenu.
storageKeystringRemembers in local storage which groups are open and the pages visited lately, under this key.
onNavigate((href: string, event: MouseEvent<HTMLAnchorElement>) => void)Called when an item is chosen, with its href, unless the click opens a new tab. A router takes over here: call event.preventDefault() and go to href.

It also takes Omit<ComponentPropsWithRef<"nav">, "aria-label">.

Sidebar.Group

PropTypeDefaultDescription
label

required

stringThe group's name, on its header. In a rail it's the header's tooltip and name.
iconComponentType<IconComponentProps>A 16px icon before the label.
defaultOpenbooleanWhether it starts open. Defaults to whether it holds the page shown; once someone opens or closes it, the sidebar remembers.
childrenReactNodeIts items, Sidebar.Items. Written straight in the group (or in fragments), they count for Recent, the rail's current mark and opening by default even while the group is closed; an item wrapped in a component of your own counts once it's shown.

Sidebar.Item

PropTypeDefaultDescription
label

required

stringThe page's name. In a rail it's the item's tooltip and name.
href

required

stringWhere it goes.
iconComponentType<IconComponentProps>A 16px icon before the label. Items at the top of the nav have one; items in a group don't show it.
currentbooleanWhether it's the page shown.

It also takes Omit<ComponentPropsWithRef<"a">, "children" | "href">.

Sidebar.Recent

PropTypeDefaultDescription
titlestring"Recent"The section's heading. Defaults to "Recent".

Sidebar.Section

PropTypeDefaultDescription
title

required

stringThe section's heading, which names it.

It also takes ComponentPropsWithRef<"div">.

Also exported

ExportDescription
useSidebarMode(): Exclude<SidebarMode, "sheet"> | undefinedHow the Sidebar around a component shows: "full", or "rail" while the shell has made it an icon rail; undefined outside a sidebar. A workspace switcher reads it to show its tile alone in a rail.

Accessibility

A navigation named Main (or its label). Items are links with aria-current="page" on the page shown; sections are groups named by their headings; a group's header is a button with aria-expanded and aria-controls. In a rail each item keeps its name (a visually hidden label) and shows it as a tooltip, and a group's header opens a menu of links.

KeysWhat they do
EnterFollows the focused item's link, through onNavigate when it's given
Enter or SpaceOpens or closes the focused group
Enter, Space or DownOpens a group's flyout menu in a rail, where Up and Down move, Enter follows a link and Escape closes

Motion

It changes shape as one surface, on `spring.smooth`, or `spring.morph` for a big change, rather than closing and reopening.

With reduced motion: A group opens and closes at once, its chevron turning without animating.

Layout

It fills its parent's height: the switcher stays at the top while the nav scrolls under it, 12px in from the sides, items 2px apart. Each item is a --bt-h-sm (28px) row whose whole width is its hit area, its label truncating and shown in full on hover. In an AppShell it's 240px wide, a 56px icon rail under 1100px of shell and a sheet under 640.

See it in the roastery demo.