Skip to content

Layout

Sidebar

The app's primary rail: expanded, collapsed to icons, or a drawer on a phone.

Sidebar SidebarSection SidebarItem
import { Sidebar, SidebarSection, SidebarItem } from '@anomalia/ui';

Notes

  • Collapsing is a width change on one CSS variable, not a different tree. Nothing unmounts, focus survives it, and a single animated property is what keeps it smooth on machines that are not yours.
  • Collapsed, labels are clipped rather than removed. display: none would strip each item’s accessible name and leave a rail of anonymous glyphs; the label also comes back as a tooltip for sighted users.
  • A SidebarItem with href is an <a>. Rendering nav as a <div onclick> costs middle-click, ⌘-click and "copy link address" — a real cost for zero gain.
  • current sets aria-current="page". The accent bar is the visual half of that statement, never the whole of it.
  • The rail sits on --an-surface-rail, a step back from the content surface. That recession is what says “this is chrome”, and it means the hairline can stay a hairline instead of carrying the separation alone. As a drawer it flips to the raised surface — a recessed panel floating over content reads as a hole, not a layer.
  • The current item lifts *forward* onto the content surface rather than being filled with a tint. On a recessed rail the page you are on is the one part that is not recessed, which explains the tint instead of fighting it — and it is joined by weight and an accent bar, because fill alone is easy to miss.
  • Rows are 32px with 8px of padding and 16px icons: the app’s own rail metrics. A 36px row beside a 36px icon button read as two different systems.
  • On a phone use overlay — the rail becomes a drawer with a scrim, Escape, a focus trap and a scroll lock. A collapsed rail on a 390px screen is neither readable nor worth the space it saves.

Examples

Rail

Collapse is a width change on one variable, so nothing unmounts and focus survives it. Collapsed, the labels are clipped rather than removed — a rail of unnamed buttons is what `display: none` would leave behind.

Current

Content

API

Sidebar

PropTypeDefaultDescription
collapsedbooleanfalseIcons only. Labels stay in the accessibility tree.
overlay / openbooleanPhone drawer, and its visibility (bindable).
width / collapsedWidthstring'256px' / '64px'The two rail widths.
header / footerSnippetPinned above and below the scrolling nav.

SidebarItem

PropTypeDefaultDescription
labelstringRequired — it is the accessible name in both states.
hrefstringPresent renders an `<a>`; absent, a `<button>`.
currentbooleanfalseSets `aria-current="page"` and the accent bar.
badgenumber | booleanA count, or `true` for a dot. Collapses to a marker on the icon.
icon / trailingSnippetGlyph, and a trailing control.

SidebarSection

PropTypeDefaultDescription
labelstringA real heading, so a screen reader can skip the group rather than walk it.
collapsedbooleanfalseSwaps the heading for a hairline.
actionSnippetA "+" or overflow menu beside the heading.

Every component also forwards the native attributes of its root element, plus class, so anything not listed here still works.