App shell
Sidebar
App navigation: header, sections of links, a keyboard-navigable page tree, footer and bottom bar. Inside an AppFrame it docks, floats when collapsed, and becomes the phone drawer.
Usage
- Use it as the
sidebarof anAppFramefor app-level navigation between pages and sections. - Use
hrefitems for navigation and button items (onClick) for actions such as “New page”; mark the current pageactive. - Use
Sidebar.Treefor nested, user-created pages; useSidebar.Section+Sidebar.Itemfor a fixed menu. - Don’t put forms or long content in the sidebar; keep rows to one line (labels truncate).
- Don’t use it for in-page navigation (a table of contents); use
PageTocRailorTabs.
Installation
Install the package (React 19):
Import the styles once, in your app entry or root layout:
Use it anywhere inside your app's <Theme> (see Installation):
Examples
Sections, links and footer
The contract parts: Header, Section, Item (href, active, trailing) and Footer.
Page tree
Hover actions are opt-in: "+" with onAdd, "…" with onMore (or moreSlot); rename them for other content with addLabel / moreLabel.
Item tones
Bottom bar
Composition
API Reference
Sidebar.Root
Based on <nav>. Adds:
| Prop | Type | Default |
|---|---|---|
width | number | 270 |
onWidthChange | (width: number) => void | |
minWidth | number | 200 |
maxWidth | number | 480 |
header | ReactNode | |
footer | ReactNode | |
bottomBar | ReactNode | |
floating | boolean | |
onCollapse | () => void | |
label | string | "Sidebar" |
children | ReactNode |
Sidebar.Header
Based on <div>. Pinned top region (does not scroll).
| Prop | Type | Default |
|---|---|---|
...props | HTMLAttributes<HTMLDivElement> |
Sidebar.Footer
Based on <div>. Pinned bottom region with a top hairline.
| Prop | Type | Default |
|---|---|---|
...props | HTMLAttributes<HTMLDivElement> |
Sidebar.Search
Based on <button>.
| Prop | Type | Default |
|---|---|---|
label | string | "Search or ask" |
shortcut | string | "⌘K" |
Sidebar.Tabs
Based on <div> (except onChange).
| Prop | Type | Default |
|---|---|---|
tabs | SidebarTab[] | Home, Chat, Meetings |
value | string | |
defaultValue | string | first tab |
onValueChange | (id: string) => void | |
trailing | ReactNode | « collapse button |
onCollapse | () => void |
ShellGlyph
The Home / Chat / Meetings glyphs used by Sidebar.Tabs (an Icon; other SVG props pass through). Use it to draw the same glyph elsewhere, e.g. a custom tab trigger.
| Prop | Type | Default |
|---|---|---|
name | 'home' | 'chat' | 'calendar' | |
size | number | 20 |
Sidebar.Item
A row whose control is <a> (with href), <button>, or your element (asChild). Other props go to the control.
| Prop | Type | Default |
|---|---|---|
children | ReactNode | |
icon | IconName | string | ReactElement | |
iconSize | number | 22 |
href | string | |
asChild | boolean | false |
onClick | (e: MouseEvent) => void | |
active | boolean | false |
trailing | ReactNode | |
tone | "default""muted""secondary" | "default" |
actions | ReactNode | |
rowClassName | string |
Sidebar.Action
Based on <button>. 20x20 hover affordance.
| Prop | Type | Default |
|---|---|---|
icon | IconName | |
label | string |
Sidebar.Section
Based on <div> (except title).
| Prop | Type | Default |
|---|---|---|
title | ReactNode | |
actions | ReactNode | |
defaultOpen | boolean | true |
open | boolean | |
onOpenChange | (open: boolean) => void |
Sidebar.Tree
Based on <div role="tree">.
| Prop | Type | Default |
|---|---|---|
label | string |
Sidebar.TreeItem
Based on <div> (except children / onSelect).
| Prop | Type | Default |
|---|---|---|
label | ReactNode | |
icon | IconName | ReactElement | "page" |
emoji | string | |
active | boolean | false |
defaultExpanded | boolean | false |
expanded | boolean | |
onExpandedChange | (expanded: boolean) => void | |
children | ReactNode | |
emptyLabel | ReactNode | "No pages inside" |
onSelect | () => void | |
onAdd | () => void | |
addLabel | string | "Add a page inside" |
onMore | (e: MouseEvent<HTMLButtonElement>) => void | |
moreLabel | string | "Delete, duplicate, and more…" |
moreSlot | ReactNode |
Sidebar.InviteCard
Based on <button> (except title), plus a dismiss button.
| Prop | Type | Default |
|---|---|---|
title | ReactNode | "Invite members" |
description | ReactNode | "Collaborate with your team." |
icon | IconName | "inviteMember" |
onDismiss | () => void |
Sidebar.WorkspaceSwitcher
Based on <button>.
| Prop | Type | Default |
|---|---|---|
name | string | |
avatar | ReactNode | letter avatar |
Sidebar.BarButton
Based on <button>. 28x28.
| Prop | Type | Default |
|---|---|---|
icon | IconName | |
label | string | |
dot | boolean | false |
Sidebar.Group / Sections / ScrollFooter / BottomBar
Layout wrappers based on <div>: rows with 1px gap, sections with 4px gap, bottom-of-scroller footer, 44px bottom bar.
| Prop | Type | Default |
|---|---|---|
...props | HTMLAttributes<HTMLDivElement> |
Accessibility
navlandmark labelled "Sidebar" (label). Items are real links (href) or buttons; the current page hasaria-current="page".- Sections are labelled groups; the heading is a button with
aria-expandedthat collapses the content (animated height). - The page tree follows the WAI-ARIA tree pattern: one tab stop; ↑/↓ move, → expands or moves to the first child, ← collapses or moves to the parent, Home/End jump, Enter opens. Items expose
aria-level,aria-expanded,aria-selected. - Hover actions (+, …) of the focused row are reachable with Tab and appear on focus; on touch screens they are always visible and targets grow to 24px+.
- The resize handle is a "Sidebar width" slider (a native range input announcing the width): →/↑ widen, ←/↓ narrow by 10px, Page Up/Down by 50px, Home/End jump to
minWidth/maxWidth; clicking the strip collapses the sidebar. - The tabs are a
tablist(←/→, Home/End move and select).