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: nonewould 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
hrefis an<a>. Rendering nav as a<div onclick>costs middle-click, ⌘-click and "copy link address" — a real cost for zero gain. currentsetsaria-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
| Prop | Type | Default | Description |
|---|---|---|---|
collapsed | boolean | false | Icons only. Labels stay in the accessibility tree. |
overlay / open | boolean | — | Phone drawer, and its visibility (bindable). |
width / collapsedWidth | string | '256px' / '64px' | The two rail widths. |
header / footer | Snippet | — | Pinned above and below the scrolling nav. |
SidebarItem
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | — | Required — it is the accessible name in both states. |
href | string | — | Present renders an `<a>`; absent, a `<button>`. |
current | boolean | false | Sets `aria-current="page"` and the accent bar. |
badge | number | boolean | — | A count, or `true` for a dot. Collapses to a marker on the icon. |
icon / trailing | Snippet | — | Glyph, and a trailing control. |
SidebarSection
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | — | A real heading, so a screen reader can skip the group rather than walk it. |
collapsed | boolean | false | Swaps the heading for a hairline. |
action | Snippet | — | A "+" 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.