Sidebar
The dashboard rail. It collapses to icons and becomes a drawer on small screens.
import SidebarRoot from "swshd/Sidebar/Root";Sidebar is the dashboard rail: a floating rounded surface of pill rows. This site uses it — the navigation on the left is the component.
Web only. On native the parts render nothing; SidebarProvider and SidebarPage pass their children through.
SidebarProvider → SidebarDrawerOpener + SidebarRoot + SidebarPage. The parts:
SidebarProvider
State (collapsed / onCollapsedChange); renders no DOM.
SidebarDrawerOpener
The fixed button that opens the drawer under drawerBelow.
SidebarRoot
The rail — an <aside aria-label> fixed to the viewport's left edge.
SidebarBrand
Logo link and the collapse toggle.
SidebarNav
A <nav aria-label> group of items.
SidebarItem
A link or button pill: Icon or icon slot, label, active, external, notification.
SidebarSpacer
Pushes the second group to the bottom.
SidebarFooter
The slot below the groups — one control (a SidebarTrigger).
SidebarTrigger
A disclosure row for the footer: label, optional meta line, the built-in glyph, and expanded (aria-expanded). It opens something and closes nothing itself.
SidebarLink
An identity link using the trigger's layout: icon, label, optional meta, and an external-link glyph for new-tab destinations.
SidebarPage
The page host: the offset content column and its block <main>.
SidebarPage paints the canvas full width and offsets the page by the rail's current width, animating that offset over motion.duration.slow; inside it a 1200 px block <main> centers the page. It adds no header — pages keep their own — and clips horizontal overflow at the document. The page frame is pageInsetY = 32 px; in drawer mode the top frame is SIDEBAR_PAGE_INSET_TOP_DRAWER = 56 px.
Expanded 256 px, collapsed 64 px, rows 44 px — 48 px in the drawer. The user toggles with the brand toggle or [ (ignored while typing). Under autoCollapseBelow (1100 px) the rail starts collapsed; under drawerBelow (900 px) it is an off-canvas drawer. The icon axis is identical in both states, so glyphs do not move; collapsed items show a Tooltip. Reduced motion and automation snap every transition.
Exactly one SidebarBrand, first. Items go only inside SidebarNav; each Nav needs a label. SidebarFooter holds one control (a SidebarTrigger); nothing else renders below it.
SidebarItem, SidebarLink, and SidebarTrigger take Icon (a glyph) or icon (any node, e.g. an Avatar); icon wins when both are passed. No part accepts style: sizes come from SIDEBAR_SIZES, colors from SIDEBAR_TOKENS. Labels are strings the app translates — swshd holds no copy.
Use SidebarLink inside SidebarNav for an entity destination with the same layout as the organization selector. Pass a decorative Avatar, the translated entity name, and href. With external, pass anaccessibilityLabel that announces the destination and new-tab behavior. For a two-line name, pass the upper line as label and the lower line as a string meta; both share typography and truncate separately.
<SidebarProvider collapsed={pref} onCollapsedChange={setPref}>
<SidebarDrawerOpener label="Open navigation" />
<SidebarRoot label="Organization navigation">
<SidebarBrand href="/home" label="SWSH"
toggleLabels={{ collapse: "Collapse sidebar", expand: "Expand sidebar" }} />
<SidebarNav label="Primary">
<SidebarItem href="/home" label="Home" Icon={IconHome} active />
</SidebarNav>
</SidebarRoot>
<SidebarPage>{children}</SidebarPage>
</SidebarProvider>Root is an <aside aria-label>; each group a <nav aria-label>; the active item carries aria-current="page"; external items are real anchors with rel="noopener noreferrer"; the trigger is a button named by its label. The closed drawer is inert; Escape or the scrim closes an open one, moving focus back to the opener.
No part accepts a style prop; all are configured through props.
SidebarProvider
| Prop | Type | Default | Description |
|---|---|---|---|
collapsed | boolean | null | — | Controlled collapse preference; null = no preference (breakpoint default applies). |
onCollapsedChange | (collapsed: boolean) => void | — | Fired on collapse changes. |
children | ReactNode | — | The SidebarRoot tree (with its SidebarDrawerOpener) and the SidebarPage. |
SidebarRoot
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | — | Accessible name of the navigation landmark. |
children | ReactNode | — | Brand, navs, spacer, footer. |
testID | string | — | data-testid on web. |
SidebarBrand
| Prop | Type | Default | Description |
|---|---|---|---|
href | string | — | Where the logo goes (the app's home). |
Logo | IconComponent | — | Defaults to the SWSH glyph. |
label | string | — | Accessible name of the logo link. |
toggleLabels | { collapse: string; expand: string } | — | Toggle copy, translated by the app. |
testID | string | — | data-testid on web. |
toggleTestID | string | — | data-testid on the collapse toggle. |
SidebarItem
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | — | The row's text. |
Icon | IconComponent | — | A glyph sized by SIDEBAR_SIZES.icon. |
icon | ReactNode | — | Any node for the icon slot instead (e.g. an Avatar); wins over Icon. |
href | string | — | Makes the item a link. |
external | boolean | — | Requires href. Opens in a new tab with the external glyph trailing; never active. |
onPress | () => void | — | Required when href is unset. |
active | boolean | — | The current page: fill.selected backdrop and aria-current="page". |
notification | number | — | A Notification count on the icon's corner. |
notificationLabel | string | — | What the count means ("2 pending invites"); folded into the row's accessible name. |
disabled | boolean | — | Blocks interaction. |
testID | string | — | data-testid on web. |
SidebarNav
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | — | aria-label of this <nav> group. |
children | ReactNode | — | The group's SidebarItem rows. |
SidebarSpacer
No props.
SidebarFooter
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | One control (a SidebarTrigger). |
SidebarTrigger
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | — | Primary text; string meta joins the default accessible name and collapsed tooltip. |
Icon | IconComponent | — | A glyph sized by SIDEBAR_SIZES.icon. |
icon | ReactNode | — | Any node for the icon slot instead; wins over Icon. |
meta | ReactNode | — | A second line: strings match the label's typography and truncate separately; nodes render as-is. |
expanded | boolean | — | The disclosure it controls is open: the open fill and aria-expanded. |
onPress | () => void | — | Toggles the disclosure it opens. |
disabled | boolean | — | Blocks interaction. |
testID | string | — | data-testid on web. |
SidebarLink
Import from swshd/Sidebar/Link. Shares label, Icon,icon, meta, disabled, and testID with the trigger; renders an anchor and closes the drawer on activation.
| Prop | Type | Default | Description |
|---|---|---|---|
href | string | — | Required destination URL. |
external | boolean | — | Opens a new tab with noopener noreferrer and the external-link glyph. |
accessibilityLabel | string | — | Accessible name and collapsed tooltip; defaults to label plus string meta, if provided. |
onPress | () => void | — | Optional activation callback. |
SidebarPage
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | The page, rendered untouched inside a centered container. |
inset | boolean | "dashboard" | — | Opt into page gutters; "dashboard" preserves the app's content gutters. |
testID | string | — | data-testid on web. |
SidebarDrawerOpener
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | — | Accessible name ("Open navigation"), translated by the app. |
testID | string | — | data-testid on web. |