The shared semantic base for top-level shell patterns. Renders one `<div role=application aria-label=…>` with a caller-supplied CSS class so each shell pattern (`ws-shell`, `ces`) scopes its own grid math to its own root class while sharing the application-landmark aria convention. Compose `<WorkspaceShell>` / `<CodeEditorShell>` on top of it rather than re-declaring `role=application` per shell.
Readiness
complete
Preview
live
Props
3
Examples
2
Code
implementation mapped
When to use
Building a new top-level shell pattern that needs the application landmark — wrap it in AppShellRoot.
Anywhere a single `role=application` boundary with a scoped CSS root class is needed.
As the base `<CodeEditorShell>` (and, post Phase-9, `<WorkspaceShell>`) composes onto.
When not to use
Regular content regions — `role=application` suppresses normal AT navigation; only true app surfaces qualify.
A panel or card — those are not application landmarks; use the Panel/Card families.
Marketing/document pages — plain semantic HTML, not an application role.
Accessibility
Role: application
Tab — Moves through focusable descendants; `role=application` hands key events to the app, so the shell pattern must provide full keyboard navigation.
Screen reader: `role=application` tells AT to pass keystrokes through rather than use browse mode — so the composing shell MUST implement complete keyboard navigation. `aria_label` is the single landmark name for the whole surface; make it specific ("Dev IDE", "Library").
Notes: `role=application` is heavy — it disables AT browse mode. Only use it for genuine application surfaces that own their keyboard model.
Props
Name
Type
Default
Description
aria_label
string
—
Single landmark name for the application surface. Required, specific.
class
string
—
Outer-wrapper CSS class, distinct per shell pattern (`ws-shell` vs `ces`) so each pattern's CSS scopes its grid to its own root. Required.
children
array
—
The shell pattern's content composed onto this base. Required.
Examples
IDE base
An IDE surface composed on AppShellRoot (CodeEditorShell uses class=ces).
The canonical tab strip + address row for any browser surface in the product. `mode: full` renders the interactive chrome (back/forward/ reload nav buttons, a real address display, per-tab close, a new-tab affordance) for the opened browser panel; `mode: miniature` renders a static, non-interactive, scaled-down variant for the overview-grid tile. Rendering the identical DOM shape at both scales is what lets the View Transitions API morph a tile into the full panel — the matching `ui-browser-*` shapes interpolate cleanly. Presentational only: the portal owns all streaming + BrowserManager state and simply composes this primitive with the prop structs it builds.
Readiness
complete
Preview
live
Props
9
Examples
2
Code
implementation mapped
When to use
The opened browser panel's chrome (Full mode, callbacks wired)
The browser overview-grid tile's chrome (Miniature mode, static)
Any future 'pick a browser' surface that wants real browser-shape previews
When not to use
You need the content frame — that is BrowserViewport, composed below the chrome
A generic tab control unrelated to a browser surface — use SimpleTabs / VerticalTabs
You need a JPEG/DOM transport picker — there is none; WebRTC is the only viewer mode
Accessibility
Role: tablist
Tab — Moves focus across tabs and (Full mode) the nav / new-tab buttons
Enter — Activates the focused tab or nav button (Full mode)
Screen reader: The tab strip is a `role=tablist` (`aria-label=\"Browser tabs\"`); each tab is `role=tab` with `aria-selected` mirrored from the active flag, and a `title=` carrying the full tab title. Full-mode controls expose `aria-label`s ("Back", "Forward", "Reload", "New tab", "Close tab"). The address row's leading icon is `aria-hidden`; the address text is a plain readable string. In Miniature mode the chrome is decorative chrome around the tile button — the calling tile surfaces the accessible name.
Notes: Empty `favicon_url` renders the Triform-mark fallback (`/assets/favicon.svg`). Miniature mode emits no interactive controls at all, so it adds no extra tab stops to the grid.
Props
Name
Type
Default
Description
tabs
array
—
Ordered tab list (active first by convention). Each item is a tab model.
address
string
The address shown in the address bar (the active tab's URL).
The canonical content frame for any browser surface. In `full` mode it frames the caller-injected live WebRTC streamer element (passed as children) or the standby placeholder, filling its container. In `miniature` mode it renders a cheap cached still frame (the tile thumbnail) or standby, locked to 16:10 so it reads as a real page and the tile→panel View Transition morph stays shape-consistent. A `loading` flag paints the indeterminate top-edge progress sweep. Presentational only — the portal resolves which source applies and owns the WebRTC/BrowserManager lifecycle.
Readiness
complete
Preview
live
Props
4
Examples
2
Code
implementation mapped
When to use
The opened browser panel's live frame (Full mode, live_webrtc + injected streamer)
The overview-grid tile's thumbnail (Miniature mode, cached_frame)
Any browser surface that needs a standby placeholder before the first frame
When not to use
You need the tab strip / address bar — that is BrowserChrome, composed above
You want a JPEG or DOM-iframe transport — there is none; WebRTC is the only live mode
A generic image / video frame unrelated to a browser surface — use Image / MediaPlayer
Accessibility
Role: img
No keyboard shortcuts
Screen reader: The cached-frame `<img>` is decorative (`alt=\"\"`) — the meaningful state is surfaced by the enclosing chrome / tile accessible name. The standby placeholder is `role=img` with `aria-label=\"No preview yet\"`. While `loading` is true the frame sets `aria-busy=\"true\"`. The live WebRTC element is provided by the caller, which is responsible for its own labelling.
Notes: Locked to a 16:10 aspect ratio in Miniature so tiles tile uniformly; Full mode releases the ratio (`aspect-ratio: auto`) so the opened panel fills its container. The loading sweep is suppressed under `prefers-reduced-motion: reduce`.
Props
Name
Type
Default
Description
source
enum: live_webrtc | cached_frame | standby
standby
What the frame shows. `live_webrtc` frames the caller's injected streamer (Full mode); `cached_frame` renders a still <img> from `url`; `standby` renders the placeholder glyph + label.
url
string
—
Image URL for the `cached_frame` source. Ignored for other sources.
mode
enum: miniature | full
full
Surface scale. `miniature` locks 16:10; `full` fills the container.
loading
boolean
false
Paint the indeterminate top-edge loading sweep.
Examples
Cached tile thumbnail
The overview-grid tile thumbnail — a cached still frame, scaled down and 16:10-locked.
An elevated bordered surface that groups related content. Use Card for "a unit on a dashboard" (a metric, a project entry, a chat thread snippet). Pair `CardHeader` + `CardBody` + `CardFooter` when the unit has chrome around its content. Variant determines emphasis: Default for most lists, Elevated for floating cards, Flat for embedded surfaces, Interactive for clickable rows.
Readiness
complete
Preview
live
Props
4
Examples
3
Code
implementation mapped
When to use
Grouped content that reads as one unit (project tile, agent card)
Repeating list items that need consistent border + padding
Dashboard metrics — pair with `StatCard` for the metric variant
Form sections that benefit from a visual frame
Clickable list rows — use `style: interactive`
When not to use
Modal/overlay surfaces — use `Dialog` (focus trap + backdrop)
Side panels with sticky chrome — use `PanelShell` family
Bare lists where padding/borders would over-emphasise the content
Single-line key/value rows — use `KeyValue` or `DataRow`
Accessibility
Role: region
No keyboard shortcuts
Screen reader: A standalone card sets `role=region` so SR users can navigate by landmark. Set `aria-labelledby` to the card title id when a visual title is present. Interactive cards (style=interactive) MUST be wrapped in a button or link element — visual interactivity alone is not focusable.
Notes: Avoid stacking cards-inside-cards more than one level deep — visual nesting beyond that fails most readability checks.
Props
Name
Type
Default
Description
title
string
—
Optional title rendered in the card header.
subtitle
string
—
Optional subtitle rendered under the title.
style
enum: default | elevated | flat | interactive
default
padding
enum: none | tight | default | relaxed
default
Examples
Project tile
Project tile with title + body + footer.
Acme Onboarding
Last activity 2h ago
12 elements · 4 agents · 1 active flow
YAML
type: card
props:
title: Acme Onboarding
subtitle: Last activity 2h ago
style: default
slots:
default:
- type: text
slots:
default: 12 elements · 4 agents · 1 active flow
Interactive row
Hover-interactive list row.
Click anywhere on this card to drill in.
YAML
type: card
props:
style: interactive
padding: tight
slots:
default:
- type: text
slots:
default: Click anywhere on this card to drill in.
The umbrella container for an IDE surface. Wraps `<AppShellRoot>` and composes the three zone wrappers (`<EditorTree>` | `<EditorBody>` | `<EditorStatus>`) into a stable grid, plus an `<EditorPalette>` portal slot. `tree_collapsed` is a consumer-owned signal that CSS-collapses the tree column to zero width without a remount. Debug builds enforce a singleton invariant: a second mount while the first is still alive fires `debug_assert!` (multi-instance deferred to Phase 9).
Readiness
complete
Preview
live
Props
3
Examples
2
Code
implementation mapped
When to use
A code/IDE surface that needs the canonical tree | body | status layout (dev_panel, element editor).
Any editor that wants a ⌘P palette overlay wired through the standard portal slot.
Surfaces needing a CSS-collapsible file tree without remounting the editor body.
When not to use
A plain text input or single-file edit — use `<CodeInput>` / `<CodeBlock>`.
More than one editor on the same page at once — the singleton invariant forbids it (Phase 9).
A generic app frame with no editor zones — use `<WorkspaceShell>` or `<AppShellRoot>` directly.
Accessibility
Role: application
Tab — Moves between tree, body, and status zones.
Escape — Closes the palette overlay when open (consumer-wired per ADR-12 Q5).
Screen reader: `aria_label` names the `role=application` wrapper (via `<AppShellRoot>`), e.g. "Dev IDE". Each zone child sets its own landmark (navigation/main/status); the palette is a `role=dialog aria-modal`.
Notes: Shell owns layout; consumer owns interaction (keyboard, palette content, tree data). The singleton `debug_assert!` is intentional — do not work around it; fix the double-mount.
Props
Name
Type
Default
Description
aria_label
string
—
Label for the `role=application` wrapper (e.g. "Dev IDE"). Required.
tree_collapsed
boolean
false
Consumer-owned signal. When true, CSS collapses the tree column to zero width — no remount, the body keeps its state. Wire to your own collapse affordance.
children
array
—
The three zone wrappers in order — `<EditorTree>`, `<EditorBody>`, `<EditorStatus>` — plus an optional `<EditorPalette>`. Required.
Examples
IDE 3-zone composition
3-zone IDE shape — tree column, editor body, status bar. Real shell uses a CSS grid; this preview stacks primitives to show the regions.
▌treeeditor body (Monaco)
▁ status: Ln 12 · UTF-8 · Rust
YAML
type: stack
props:
direction: column
gap: sm
children:
- type: stack
props:
direction: row
gap: md
children:
- type: text
slots:
default: ▌tree
- type: text
slots:
default: editor body (Monaco)
- type: text
slots:
default: '▁ status: Ln 12 · UTF-8 · Rust'
Tree collapsed
Tree-collapsed state — body + status only, tree at zero width.
editor body (full width)▁ status
YAML
type: stack
props:
direction: column
gap: sm
children:
- type: text
slots:
default: editor body (full width)
- type: text
slots:
default: ▁ status
Keeps connection management compact inside the props panel: paired browsers get a visual extension install card, and non-browser connections are grouped by type with names, lifecycle pills, and edit/disconnect actions that do not fight for horizontal space.
Readiness
complete
Preview
live
Props
3
Examples
1
Code
implementation mapped
When to use
Circle Connect section in the generated props panel.
Grouped external account lists with a small add flow.
When not to use
Raw connector catalogs where discovery matters more than management.
Accessibility
Role: region
Tab — Moves through Add, row edit links, and disconnect buttons.
Screen reader: N/A
Props
Name
Type
Default
Description
title
string
Your connections
Card heading for the grouped connection list.
action
string
Add
Primary add action label.
groups
array
—
Connection groups, each with a name, icon, and compact rows.
A small reactive count chip whose `label` is a `Signal<String>`, so it re-renders in place as the underlying count changes (no remount). Use beside a rail item, a nav label, or a section heading to surface "how many" at a glance. It follows the same compact rectangular label grammar as Badge and Tag. It is the standalone reusable count affordance — distinct from `<Badge>` (decorative status) and the rail-item-internal tab-count chip.
Readiness
complete
Preview
live
Props
1
Examples
2
Code
implementation mapped
When to use
An unread / item / pending count beside a navigation or rail label.
A live count that updates reactively without rebuilding its container.
A compact 'N' affordance where a full `<Badge>` would be visually heavy.
When not to use
Decorative status or category tags (no number) — use `<Badge>`.
A rail-item's own tab count — that is `<RailItem>`'s `tab_count` prop, rendered internally.
Large or primary metrics — use `<StatCard>`.
Accessibility
Role: status
No keyboard shortcuts
Screen reader: Renders as a `status` so count changes are announced politely. Keep the surrounding label meaningful ("Notifications 3") — a bare "3" chip with no labelled context is meaningless to SR users.
Notes: Pair with a textual label; the chip is a visual count, not a self-describing control.
Props
Name
Type
Default
Description
label
string
—
The count text, supplied as a reactive signal so the chip updates in place. Usually a number ("3", "12", "99+"). Required.
Examples
Nav count
Unread count beside a nav label — the common shape.
Notifications
YAML
type: stack
props:
direction: row
gap: sm
children:
- type: text
slots:
default: Notifications
- type: badge
props:
label: '3'
Capped count
Overflow-capped count (99+) beside a rail item.
Inbox
YAML
type: stack
props:
direction: row
gap: sm
children:
- type: text
slots:
default: Inbox
- type: badge
props:
label: 99+
The center editor zone of `<CodeEditorShell>` — a `<main role=main>` landmark hosting the consumer's editor unit. Per ADR-12 Q2 the editor sub-module owns its own tabs, breadcrumb, and Monaco mount as one cohesive unit, so this wrapper deliberately does not slot a separate tabs component — it is purely the main landmark + grid cell.
Readiness
complete
Preview
live
Props
1
Examples
2
Code
implementation mapped
When to use
The primary editing surface of a `<CodeEditorShell>` — exactly one per shell.
Hosting a consumer's cohesive editor unit (tabs + breadcrumb + Monaco) as a single child.
Any IDE body that needs the standard `main` landmark wired correctly.
When not to use
Slotting tabs/breadcrumb separately — the editor unit owns those internally by design (ADR-12 Q2).
Non-editor main content — use `<WorkspaceBody>`.
The tree or status zones — those are siblings.
Accessibility
Role: main
Tab — Moves focus into the editor unit from the tree/status zones.
Screen reader: `<main role=main aria-label=\"Editor\">`. Exactly one per shell — `main` is a singleton landmark. The consumer's editor unit provides finer structure (tab list, document region) inside it.
Notes: One `<EditorBody>` per shell. Don't emit a second main landmark from the consumer's editor unit.
Props
Name
Type
Default
Description
children
array
—
The consumer's cohesive editor unit — tabs + breadcrumb + Monaco mount as one child. Required.
Examples
Editor unit
Editor body content shape — tab strip, breadcrumb, editor area as one unit.
Welcome / no-file-open state inside the body zone.
Open a file from the tree to start editing
YAML
type: stack
props:
direction: column
gap: sm
children:
- type: icon
props:
name: code
- type: text
slots:
default: Open a file from the tree to start editing
The ⌘P / Ctrl+P palette overlay slot for `<CodeEditorShell>`. Rendered to `<body>` via Leptos `<Portal>` and mounted-once — visibility is toggled by the consumer-owned `show: Signal<bool>`, NOT mounted-on-show (that would re-create child state every open — a disposal hazard). The shell provides only the portal + backdrop + dialog frame; the actual palette content (Commands / QuickOpen / ElementSearch modes) lives in the consumer crate as children. Backdrop-click calls `on_close`; Esc-handling is the consumer's job (ADR-12 Q5) to avoid double-handling.
Readiness
complete
Preview
live
Props
3
Examples
2
Code
implementation mapped
When to use
A ⌘P command/quick-open palette for a `<CodeEditorShell>` IDE surface.
Any editor overlay that must survive open/close without re-creating its content state.
Wiring a consumer-owned palette UI through the standard portal+dialog frame.
When not to use
A non-modal dropdown or inline menu — use `<ContextMenu>` / `<CommandPalette>` primitive.
Mount-on-show overlays — this is deliberately mounted-once; don't gate the whole `<EditorPalette>` behind a `<Show>`.
Palettes outside an editor — use the standalone command-palette primitive.
Accessibility
Role: dialog
Escape — Close the palette (consumer-installed listener per ADR-12 Q5).
Enter — Activate the selected entry (consumer palette content).
ArrowDown — Move selection down (consumer palette content).
ArrowUp — Move selection up (consumer palette content).
Screen reader: Frame is `role=dialog aria-modal=true aria-label=\"Command palette\"`, `aria-hidden` toggled with `show`. Consumer content must manage focus-trap + active-descendant for the result list. Because children live inside a Portal (a `<Show>`/`<For>`-class boundary), consumer state MUST use `ArcRwSignal::new` per portal/CLAUDE.md trigger 1.
Notes: Shell owns layout + backdrop-click→on_close; consumer owns Esc + content + focus management. Esc is intentionally NOT shell-handled to avoid double-close.
Props
Name
Type
Default
Description
show
boolean
—
Consumer-owned visibility signal. Portal stays mounted; this toggles the visible/aria-hidden class. Required.
on_close
string
—
Handler ref invoked on backdrop click. Required. Esc-to-close is the consumer's separate responsibility (ADR-12 Q5).
children
array
—
Consumer palette content — the Commands / QuickOpen / ElementSearch UI. Required. Must use Arc* reactive primitives (Portal is a disposal-hazard boundary).
Examples
Command palette (open)
Open command-palette shape — input + result list inside the dialog frame.
Refactor: Rename SymbolRefactor: Extract Function
YAML
type: stack
props:
direction: column
gap: sm
children:
- type: input
props:
placeholder: Type a command…
value: '>refac'
- type: text
slots:
default: 'Refactor: Rename Symbol'
- type: text
slots:
default: 'Refactor: Extract Function'
Quick open mode
Quick-open mode — file fuzzy-find inside the same frame.
src/main.rstests/main_test.rs
YAML
type: stack
props:
direction: column
gap: sm
children:
- type: input
props:
placeholder: Go to file…
value: main
- type: text
slots:
default: src/main.rs
- type: text
slots:
default: tests/main_test.rs
The bottom status-bar zone of `<CodeEditorShell>`. Wraps the shared `<StatusBarSlot>` with the IDE-specific `ces__status` class so per-pattern CSS can scope its layout. Hosts ambient editor state — cursor position, encoding, language mode, git branch, diagnostics count. Read-only ambient indicators, not actions.
Readiness
complete
Preview
live
Props
1
Examples
2
Code
implementation mapped
When to use
The status bar of a `<CodeEditorShell>` — exactly one per shell.
Any IDE footer that should use the shared StatusBarSlot layout with IDE scoping.
When not to use
Primary actions (save, run) — those live in the editor unit's toolbar, not the status bar.
Non-editor ambient status — use `<WorkspaceStatus>`.
Urgent errors that need attention — use `<PanelErrorBanner>`; the status bar is calm/ambient.
Accessibility
Role: status
No keyboard shortcuts
Screen reader: Inherits `<StatusBarSlot>` semantics (status region). Keep segments terse — long status text is fatiguing when announced on every cursor move. Diagnostics counts should also be reachable from the editor unit, not status-bar-only.
Notes: Ambient and calm by design. Segment order convention: position → selection → encoding → language → branch → diagnostics.
Props
Name
Type
Default
Description
children
array
—
Status segments — short indicators (Ln/Col, encoding, language, branch, diagnostics). Required; keep terse.
Examples
Full status bar
Full editor status bar — position, encoding, language, branch, diagnostics.
Ln 12, Col 4UTF-8Rust dev⚠ 2 ✕ 0
YAML
type: stack
props:
direction: row
gap: md
children:
- type: text
slots:
default: Ln 12, Col 4
- type: text
slots:
default: UTF-8
- type: text
slots:
default: Rust
- type: text
slots:
default: ' dev'
- type: text
slots:
default: ⚠ 2 ✕ 0
The left file-tree zone of `<CodeEditorShell>` — an `<aside role=navigation>` that hosts the consumer's tree widget. It is a passive wrapper: no drag handle, no resize logic. Column width is controlled by the consumer via the `--code-editor-tree-width` CSS custom property on the shell root, and the shell's `tree_collapsed` signal CSS-collapses it to zero. The tree widget itself (nodes, expand/collapse, selection) is the consumer's content.
Readiness
complete
Preview
live
Props
1
Examples
2
Code
implementation mapped
When to use
The file/element tree column of a `<CodeEditorShell>` — exactly one per shell.
Hosting a consumer-owned tree widget that needs the standard editor navigation landmark.
Any IDE surface where the tree should CSS-collapse with the shell, not remount.
When not to use
A standalone tree outside an editor — use a tree primitive directly, not this zone wrapper.
Resize/drag-handle behavior here — width lives on the shell root CSS prop, not in this wrapper.
The body or status zones — those are `<EditorBody>` / `<EditorStatus>`.
Accessibility
Role: tree
ArrowUp — Move focus to previous tree node (consumer tree widget).
ArrowDown — Move focus to next tree node.
ArrowRight — Expand collapsed node.
ArrowLeft — Collapse expanded node.
Screen reader: Rendered as `<aside role=navigation aria-label=\"File tree\">`. The consumer's tree widget should use proper `role=tree` / `treeitem` semantics inside this landmark so SR users get full tree navigation.
Notes: The wrapper provides the landmark; the consumer's widget provides the `tree`/`treeitem` roles + roving focus. Don't duplicate the landmark inside the widget.
Props
Name
Type
Default
Description
children
array
—
The consumer's tree widget — nodes, expand/collapse controls, selection state. Required; an empty tree zone should render an empty-state, not nothing.
Examples
File tree zone
File tree content shape — nested files/folders inside the zone.
▸ src/ main.rs lib.rs▸ tests/
YAML
type: stack
props:
direction: column
gap: sm
children:
- type: text
slots:
default: ▸ src/
- type: text
slots:
default: ' main.rs'
- type: text
slots:
default: ' lib.rs'
- type: text
slots:
default: ▸ tests/
Empty tree zone
Empty tree zone — a calm empty-state rather than a blank column.
No files yet
YAML
type: stack
props:
direction: column
gap: sm
children:
- type: icon
props:
name: folder_off
- type: text
slots:
default: No files yet
The structural wrapper for every entity-detail modal — Contact, Organization, and (future) Pursuit. It owns the full-viewport backdrop, the centered glass panel, the keyboard focus trap, and a close-row that carries an optional dismissible op-error banner and the close control. Backdrop click, the close button, and Esc all route to one `on_close` callback. The detail variants render their own body zones (header / decide / define / context) as children — the shell is intentionally element-agnostic and does not know what entity kind it is wrapping.
Readiness
complete
Preview
live
Props
5
Examples
2
Code
implementation mapped
When to use
Any entity-detail modal that needs the shared backdrop + focus-trap + close-row chrome.
Contact, Organization, or pursuit detail surfaces opened from a list row.
A new detail variant that should match the established entity-detail modal behaviour.
When not to use
Generic side panels that are not modal — use `<PanelShell>`.
A transient overlay with no boundary or close-row — use `<Drawer>` or `<Popover>`.
A confirm or alert dialog — use `<Modal>` / `<ModalShell>` directly.
Accessibility
Role: dialog
Escape — Routes to on_close — closes the modal.
Tab — Tab / Shift+Tab cycle focusable controls inside the panel; the focus trap keeps focus from escaping to the page behind.
Screen reader: The inner panel is `role=dialog` with `aria-modal=true`. The caller must place the `aria_labelledby` id on its own header element (usually the `<h2>` in the HEADER zone) so assistive tech announces the dialog with a meaningful name. The close control is labelled "Close"; the op-error banner is `role=alert` so failures are announced when they appear.
Notes: The shell does not generate a title — it relies on the variant's header element via `aria_labelledby`. A variant that omits that id leaves the dialog unnamed for assistive tech.
Props
Name
Type
Default
Description
aria_labelledby
string
—
Id of the variant's header element, wired to the dialog's `aria-labelledby`. Required — the shell has no title of its own.
variant_class
string
—
Optional extra CSS class on the inner panel (e.g. `org-detail`) so a variant can add rules without duplicating the base class tree. Empty by default.
on_close
object
—
Callback fired when the user clicks the backdrop, presses Esc, or clicks the close control. The parent clears its selection to unmount the modal.
op_err
object
—
Signal of an optional op-error string. The variant writes `Some(msg)` when a mutation fails; the shell renders it as a dismissible `role=alert` banner in the close-row and writes `None` back on dismiss.
children
array
—
The variant's body zones (header / decide / define / context), rendered inside the focus-trapped panel below the close-row.
Examples
Entity-detail modal
Entity-detail modal shape — a centered panel with the variant's identity header and body. The real shell is callback-driven; this preview approximates the composed chrome.
Sarah ChenVP Engineering · Northwind Traders
YAML
type: card
props:
style: elevated
padding: relaxed
slots:
default:
- type: text
slots:
default: Sarah Chen
- type: text
slots:
default: VP Engineering · Northwind Traders
- type: button
props:
variant: ghost
slots:
default: Close
With op-error banner
Op-error state — a failed mutation surfaces a dismissible alert banner in the close-row above the body.
Save failed
Sarah Chen — VP Engineering
YAML
type: stack
props:
direction: column
gap: sm
children:
- type: stack
props:
direction: row
gap: sm
align: center
children:
- type: badge
props:
variant: danger
slots:
default: Save failed
- type: button
props:
variant: ghost
size: small
slots:
default: Dismiss
- type: text
slots:
default: Sarah Chen — VP Engineering
A modal surface: backdrop, centred container, dialog semantics, with a stable id for ModalStack registration. Today it delegates to `<PanelShell variant=Modal>`; Sprint 2 PR-8 fills in the ModalStack + focus-trap integration. The props shape (`panel_id`, `title`, `density`, `on_close`) is locked so callers adopt the API now without a second migration when the full implementation lands.
Readiness
complete
Preview
live
Props
5
Examples
2
Code
implementation mapped
When to use
A focus-trapped modal surface (confirm, form, detail) that should register with the ModalStack.
Adopting the stable modal API ahead of the Sprint-2 focus-trap fill-in.
Any modal that wants the unified Panel chrome via the Modal variant.
When not to use
A non-modal panel — use `<PanelShell>` with floating/docked variant.
A transient toast / popover — use the feedback / overlay families.
A bare visual modal with no dialog semantics at all — that anti-pattern is exactly what this replaces.
Accessibility
Role: dialog
Escape — Invokes on_close (close).
Tab — Cycles focusable controls within the modal (focus-trap arrives in Sprint 2 PR-8).
Screen reader: Backed by `<PanelShell variant=Modal>` → `role=dialog aria-modal=true`, labelled by the title. Until the Sprint-2 focus-trap lands, callers should still avoid background-interactive content behind the backdrop.
Notes: `dock_locked=true` and fullscreen are stubbed off for modals. Esc / backdrop both route to `on_close`.
Props
Name
Type
Default
Description
panel_id
string
—
Stable id for ModalStack registration (Sprint 2 PR-8). Required.
title
string
—
Display title, names the dialog for screen readers. Required.
density
enum: comfortable | compact
comfortable
Spacing scale (inherited from PanelShell density).
on_close
string
—
Handler ref invoked by Esc, backdrop click, and the X button. Required.
children
array
—
Modal body content. Required.
Examples
Confirm modal
Confirm-destructive modal — title + body + cancel/delete.
Delete element?
This permanently removes the element and its data.
YAML
type: card
props:
style: elevated
padding: relaxed
title: Delete element?
slots:
default:
- type: text
slots:
default: This permanently removes the element and its data.
- type: stack
props:
direction: row
gap: sm
children:
- type: button
props:
variant: ghost
slots:
default: Cancel
- type: button
props:
variant: danger
slots:
default: Delete
Form modal (compact)
Compact form modal — density=compact, single primary action.
Rename
YAML
type: card
props:
style: elevated
padding: default
title: Rename
slots:
default:
- type: input
props:
placeholder: New name
value: my-element
- type: button
props:
variant: primary
slots:
default: Save
The middle scroll region of `<PanelShell>`. Owns its own scrollbar by default; `scrollable: false` is the escape hatch for panels whose child (canvas, monaco, browser viewport) owns scroll itself. `full_bleed: true` zeros body padding for full-bleed media while PanelShell's header / footer keep their standard padding.
Readiness
complete
Preview
live
Props
3
Examples
2
Code
implementation mapped
When to use
Standard scrolling panel content (lists, forms, settings sections) — leave both props default.
Browser screencast or dev-panel monaco region — `full_bleed: true` so the canvas reaches the edges.
Outside `<PanelShell>` — body has no meaning without a panel boundary; use `<ScrollArea>` for standalone scroll containers.
Header-only or footer-only panels — omit the body entirely.
Body that needs a sticky inner header — that is `<PanelToolbar>` inside the body, not a different `<PanelBody>` shape.
Accessibility
Role: region
Tab — Moves through focusable children in DOM order.
PageUp — Native scroll up by one page (when scrollable).
PageDown — Native scroll down by one page (when scrollable).
Screen reader: N/A
Notes: When the body owns scroll, ensure the panel's title element has an `id` that the body region's `aria-labelledby` can point at. Full-bleed bodies should still surface a logical title for SR users via `<PanelHeader>` content.
Props
Name
Type
Default
Description
scrollable
boolean
true
Whether the body owns its own scroll container. `false` is the escape hatch for panels whose child (canvas, code editor) manages scroll internally.
full_bleed
boolean
false
Zeros the body's padding for full-bleed media (browser screencast, monaco region). Header and footer padding from `<PanelShell>` are unaffected.
children
array
—
Body content. Lists, forms, sections, embedded canvases. Required; a body with no children should not be rendered.
The empty-state shape sized for a panel — calm, scoped, and densely spaced rather than full-screen-amplified. Icon + title are required; description and action (typically a single `<Button>`) are optional. Use when a list inside a panel has zero items and the panel itself is fine. Use `<EmptyState>` (unwrapped) for full-screen empty surfaces outside the panel family.
Readiness
complete
Preview
live
Props
4
Examples
2
Code
implementation mapped
When to use
Empty list inside a `<PanelBody>` — Connections, Notifications, Recent activity, etc.
Empty filter result inside a panel — `Title: "No results"`, optional action: "Clear filters".
Pre-data state — `Title: "No connections yet"`, action: "Add connection".
When not to use
Full-screen empty states (route-level 'No projects yet' splash) — use `<EmptyState>` directly.
Error states (network failed, permission denied) — use `<PanelErrorBanner>` instead; empty != error.
Loading states — use `<PanelLoadingState>` (skeleton or spinner) instead.
Accessibility
Role: status
Tab — Moves to the optional action button if present.
Screen reader: The container is `role=status` so screen readers announce the empty state without interrupting the user. Icon is `aria-hidden=true`; the title and description carry the meaning. The optional action button gets standard `<Button>` accessibility.
Notes: Title should describe the state ("No connections yet"), not the component ("Empty state"). Description, when present, should explain what would populate the list ("Add a connection to see it here.").
Props
Name
Type
Default
Description
icon
string
—
Material Symbols Outlined icon name (e.g. `inbox`, `cable`, `notifications_none`). Required. Should visually echo the absent-content category.
title
string
—
Short heading describing the empty state in the user's terms. "No connections yet", "No recent activity". Required.
description
string
—
Optional supporting copy explaining what would populate the list or how to get started. One sentence; longer copy belongs in documentation, not in-panel chrome.
children
array
—
Optional action slot — typically a single `<Button variant=primary>` ("Add connection", "Connect account"). For zero actions, leave the slot empty.
Examples
No connections yet
Pre-data empty state with primary action — the most common shape.
No connections yetAdd a connection to see it here.
YAML
type: stack
props:
direction: column
gap: sm
children:
- type: icon
props:
name: cable
- type: text
slots:
default: No connections yet
- type: text
slots:
default: Add a connection to see it here.
- type: button
props:
variant: primary
size: small
slots:
default: Add connection
Filtered to nothing
Filter-result empty state — no action, just calm acknowledgement.
No resultsTry clearing one of the active filters.
YAML
type: stack
props:
direction: column
gap: sm
children:
- type: icon
props:
name: filter_alt_off
- type: text
slots:
default: No results
- type: text
slots:
default: Try clearing one of the active filters.
A calm, panel-scoped error surface. Renders a tone icon, a title, an optional body (good place for `ApiErrorResponse._suggestion`), and optional Retry / Dismiss actions. Three tones: `recoverable` (caution — default), `fatal` (bad), `notice` (informational). Deliberately calm — no shake, no full-width red bar — so a recoverable error inside a panel does not read as "the whole app broke".
Readiness
complete
Preview
live
Props
5
Examples
2
Code
implementation mapped
When to use
A panel operation failed but the panel itself is intact (fetch error, save rejected) — `tone: recoverable` + a Retry callback.
Unrecoverable panel state (auth lost, resource deleted) — `tone: fatal`, usually with Dismiss but no Retry.
A soft informational notice scoped to the panel — `tone: notice`.
When not to use
Empty (zero-item) state — use `<PanelEmptyState>`; empty is not an error.
Loading state — use `<PanelLoadingState>`.
App-level fatal errors (route crash) — use a full-screen error boundary, not a panel banner.
Accessibility
Role: alert
Tab — Moves to Retry then Dismiss when those callbacks are provided.
Enter — Activates the focused Retry / Dismiss action.
Screen reader: `role=alert` so the error is announced immediately when it appears. The tone icon is `aria-hidden`; title + body carry the meaning. Keep the title a short statement of what failed, body a one-line why/how.
Notes: Body should compose the typed `_suggestion` from `ApiErrorResponse` where available — it is written to be actionable enough for an agent or user to self-correct.
Props
Name
Type
Default
Description
tone
enum: recoverable | fatal | notice
recoverable
Severity tone — selects icon + signal palette.
title
string
—
Short statement of what failed. Required.
body
string
—
Optional one-line why/how. Best populated from `ApiErrorResponse._suggestion`.
on_retry
string
—
Optional retry handler ref. When present, renders a "Retry" button. Bound via the action registry (`${actions.retry}`).
on_dismiss
string
—
Optional dismiss handler ref. When present, renders a close (×). Bound via the action registry (`${actions.dismiss}`).
Examples
Recoverable error + retry
Recoverable fetch failure with a Retry — the most common shape.
Couldn't load connectionsThe connections service didn't respond. Check your network and retry.
YAML
type: stack
props:
direction: column
gap: sm
children:
- type: icon
props:
name: warning_amber
- type: text
slots:
default: Couldn't load connections
- type: text
slots:
default: The connections service didn't respond. Check your network and retry.
- type: button
props:
variant: ghost
size: small
slots:
default: Retry
Fatal error
Fatal, non-retryable state — dismiss only.
This panel's resource was deleted
YAML
type: stack
props:
direction: column
gap: sm
children:
- type: icon
props:
name: error_outline
- type: text
slots:
default: This panel's resource was deleted
- type: button
props:
variant: ghost
size: small
slots:
default: Dismiss
The sticky bottom of a `<PanelShell>` for primary panel actions (Cancel / Save, status + action, single CTA). `align` controls the three observed shapes: `End` for two-button confirms, `Between` for a status pill paired with actions, `Center` for single-CTA panels. Children are the action buttons; the optional `status` slot only renders in `Between` alignment.
Readiness
complete
Preview
live
Props
3
Examples
2
Code
implementation mapped
When to use
Confirm/cancel pairs at the bottom of a settings panel — `align: end`.
Status pill on the left + actions on the right (sync status + manual sync button) — `align: between`.
Single CTA panels (Sign in, Connect…) — `align: center`.
Any panel where the action row should stay visible as the body scrolls.
When not to use
Inline form actions that should scroll with the form — use `<FormActions>` instead.
Toolbar with non-action chips (filters, view toggles) — use `<PanelToolbar>` (a header sibling).
Footer that needs >2 alignment regions — escalate to a new component shape rather than fork.
Accessibility
Role: contentinfo
Tab — Moves through action buttons in DOM order (status slot first if present, then actions left-to-right).
Enter — Activates the focused action button.
Screen reader: N/A
Notes: Buttons inside the actions slot inherit standard `<Button>` accessibility (aria-busy on loading, removal from focus order on disabled). Status slot content should be a plain text or `<Badge>` — not interactive.
Props
Name
Type
Default
Description
align
enum: end | between | center
end
Alignment shape of the footer row.
status
array
—
Optional left-side status content — text, `<Badge>`, or other inert primitive. Only renders in `align: between`. Pass as a named slot.
children
array
—
The action buttons. Typically one or two `<Button>` primitives. Required; a footer with zero actions should not be rendered.
Examples
End-aligned confirm row
Cancel + Save action pair — the default end-aligned shape.
The shared, variant-aware top region for every Panel. Renders the title (from `PanelShellContext`), an optional inline children slot for in-header content (search inputs, badges), and the variant-specific window controls: Floating gets drag-handle + dock-menu + fullscreen + close; Docked gets detach + close; Modal gets close only; Sheet gets a drag-handle visual hint. Authors do not configure these directly — they configure `<PanelShell>` and the header adapts.
Readiness
complete
Preview
live
Props
3
Examples
2
Code
implementation mapped
When to use
Inside any `<PanelShell>` — every Panel variant needs PanelHeader for its variant-appropriate chrome.
When you want the panel title and window controls to stay sticky as the body scrolls.
When you need to add inline header content (search box, status badge) between the title and the controls.
When not to use
Outside a `<PanelShell>` — PanelHeader expects `PanelShellContext` and will panic without it.
For a secondary toolbar row beneath the header — that is `<PanelToolbar>`, a sibling slot.
For modal dialog headers with no panel boundary — use `<Dialog>` chrome instead.
Accessibility
Role: heading
Tab — Moves through dock-menu trigger, fullscreen, and close in tab order.
Escape — Closes the dock-menu popover when it is open (Floating variant only).
Enter — Activates the focused window control (dock / fullscreen / close).
Screen reader: Title element carries `id={panel_id}-title` so the panel surface can set `aria-labelledby`. Window-control buttons each carry both `title` and `aria-label`. The dock menu uses `aria-expanded` / `aria-haspopup` so SR users know it is a popover trigger.
Notes: Icons follow the locked Material Symbols Outlined contract (no inline SVGs); legacy `ICON_DOCK` / `ICON_UNDOCK` / fullscreen SVGs were retired during Phase 8.A of the design harmonization arc.
Props
Name
Type
Default
Description
title
string
—
Panel title text. **Set on `<PanelShell title=…>`, not on `<PanelHeader>` directly** — the header reads it from `PanelShellContext`. Documented here because it is the most important author-visible knob.
variant
enum: floating | docked | modal | sheet
—
Layout variant — determines which window controls render. **Set on `<PanelShell variant=…>`, not on `<PanelHeader>`.** Listed here so the variant matrix is discoverable from the showcase page.
children
array
—
Optional inline header content rendered between the title and the window controls. Common content: in-panel search input, status pill, agent presence badge. Pass as `<PanelHeader>…</PanelHeader>` children. Distinct from `<PanelShell>`'s `toolbar` slot, which is a full secondary row below the header.
Examples
Floating header shape
Visual approximation of a floating panel header — title left, controls right. The real header pulls title/variant from PanelShellContext; this preview uses primitives to show the shape.
Panel title
YAML
type: stack
props:
direction: row
gap: md
children:
- type: text
slots:
default: Panel title
- type: icon-button
props:
icon: fullscreen
aria_label: Fullscreen
- type: close-button
props:
aria_label: Close panel
Docked header shape
Docked variant approximation — title + detach + close, no fullscreen affordance.
The loading placeholder for a `<PanelBody>`, sized to mirror the shape of the content that's loading so the panel does not reflow when data arrives. `shape: list` for row lists, `detail` for two-column grids, `chart` for time-series, `spinner` as the explicit last resort. `rows` tunes the List skeleton count; `caption` adds an optional "Loading…" label.
Readiness
complete
Preview
live
Props
3
Examples
2
Code
implementation mapped
When to use
A panel list/detail/chart is fetching its first data — pick the `shape` that matches the loaded layout.
Re-fetch of an already-shaped panel where preserving layout height matters more than a spinner.
Any panel where a centered spinner would cause a visible reflow when content lands.
When not to use
Zero-data (loaded but empty) — use `<PanelEmptyState>`.
A failed load — use `<PanelErrorBanner>`.
Sub-second operations where a skeleton would flash — render nothing or keep prior content.
Accessibility
Role: status
No keyboard shortcuts
Screen reader: `role=status` + `aria-live=polite` so SR users hear the loading state without it stealing focus. Skeleton rows are `aria-hidden`; the optional caption carries the announced text.
Notes: Prefer a shaped skeleton over `shape: spinner` — spinners kill perceived performance and tell SR users nothing about what is coming.
Props
Name
Type
Default
Description
shape
enum: list | detail | chart | spinner
list
Skeleton shape — mirror the loaded content's layout.
caption
string
—
Optional label, e.g. "Loading session…". Announced to SR users.
rows
integer
6
Skeleton row count for `shape: list`. Ignored by other shapes.
Examples
List skeleton
List skeleton with caption — the default first-load shape for a list panel.
A titled subdivision inside `<PanelBody>` for grouping related controls (a settings panel's "Appearance" / "Permissions" / "Advanced" blocks). `collapsible` adds a disclosure chevron; `expanded` sets the initial state. The optional `actions` slot renders controls in the section header (a per-section "Reset" or "Add"). Panel-density spacing is what distinguishes it from a bare `<Disclosure>`.
Readiness
complete
Preview
live
Props
5
Examples
2
Code
implementation mapped
When to use
Grouping a long settings/properties panel into labelled blocks.
Collapsible 'Advanced' sections that default closed (`collapsible: true`, `expanded: false`).
A section that needs its own header action (per-section Reset / Add) via the actions slot.
When not to use
A standalone collapsible outside a panel — use `<Disclosure>` / `<CollapsibleSection>` directly.
Top-level panel chrome (title bar) — that is `<PanelHeader>`.
Single key/value rows — use `<DataRow>` / `<DefinitionList>`, not a section per row.
Accessibility
Role: region
Tab — Moves to the section header (and toggle, if collapsible) then into content.
Enter — Toggles the section when collapsible (header is the click target).
Space — Toggles the section when the header has focus and is collapsible.
Screen reader: The section is a `region` labelled by its title. When collapsible, the header carries the expanded/collapsed state so SR users know the content can be toggled and its current state.
Notes: Title is required and should be a short noun phrase ("Permissions"), not a sentence. Non-collapsible sections still render the title as a landmark — useful for SR navigation even without toggle behavior.
Props
Name
Type
Default
Description
title
string
—
Section heading — short noun phrase. Required. Doubles as the SR landmark label.
collapsible
boolean
false
Whether the section can collapse. `false` (default) renders a static titled block; `true` adds a disclosure chevron and click target.
expanded
boolean
true
Initial expanded state when `collapsible: true`. Ignored when the section is not collapsible. Set `false` for "Advanced" blocks that should start closed.
actions
array
—
Optional controls rendered in the section header (right side) — typically a single per-section action like "Reset" or "Add".
children
array
—
Section content — the grouped controls or rows. Required.
Examples
Static section
Static titled section grouping two settings rows.
Appearance
Dark mode
Compact density
YAML
type: stack
props:
direction: column
gap: sm
children:
- type: text
slots:
default: Appearance
- type: labeled-switch
props:
label: Dark mode
- type: labeled-switch
props:
label: Compact density
Collapsible advanced section
Collapsible 'Advanced' section, closed by default, with a header Reset action.
Advanced
(collapsed content — timeout, retry policy, raw overrides)
YAML
type: stack
props:
direction: column
gap: sm
children:
- type: stack
props:
direction: row
gap: md
children:
- type: text
slots:
default: Advanced
- type: button
props:
variant: link
size: small
slots:
default: Reset
- type: text
slots:
default: (collapsed content — timeout, retry policy, raw overrides)
The drag-to-snap behavior for a mobile bottom-sheet panel, extracted so only sheet panels carry pointer-event code. The caller holds the snap state (`Half` / `Full` / closed = `None`) and its own state machine; this wrapper watches pointer drag and emits a `SheetSnapTransition` (`Expand` Half→Full, `Collapse` Full→Half, `Close` Half→dismiss) when a drag crosses `threshold` pixels, firing a 10ms haptic on transition.
Readiness
complete
Preview
live
Props
4
Examples
2
Code
implementation mapped
When to use
Wrapping the body of a `variant: sheet` panel on mobile (≤768px) so it is drag-snappable.
Any bottom-sheet surface that needs Half/Full snap points with swipe-to-dismiss.
Replacing a hand-rolled pointer-drag snap implementation with the shared one.
When not to use
Floating/docked/modal panels — they have no snap points; this just adds dead pointer handlers.
Desktop panels — snap-to is a mobile gesture; use dock/undock instead.
A simple show/hide drawer with no intermediate snap state — use `<Drawer>`.
Accessibility
Role: region
Tab — Focus moves through the sheet's content; snapping itself is a pointer gesture.
Screen reader: This is a behavioral wrapper — it adds no visible chrome and no ARIA of its own beyond the region. The owning `<PanelShell variant=sheet>` provides the dialog role and label; ensure a non-gesture affordance (a visible close control in the header) exists since drag-to-dismiss is not keyboard/SR-reachable.
Notes: Drag-to-dismiss is pointer-only by nature. Always pair with a keyboard/SR-reachable close in `<PanelHeader>` — never make dismiss gesture-exclusive.
Props
Name
Type
Default
Description
state
enum: half | full | closed
—
Current snap state, owned by the caller's state machine and passed as a reactive signal. `closed` corresponds to `None` in the impl.
threshold
integer
80
Pixel drag distance past which a transition fires. Lower = more sensitive snapping; default 80px.
on_transition
string
—
Required handler ref for emitted transitions (`expand` / `collapse` / `close`). Bound via the action registry; the caller advances its own snap state machine in response.
children
array
—
The sheet content the drag handlers wrap. Required.
Examples
Half-snap sheet shape
Half-snap mobile sheet shape — drag handle hint + content. The real component wires pointer drag; this preview shows the visual shell only.
▬ drag to expand / dismissSheet content (Half snap point)
YAML
type: stack
props:
direction: column
gap: sm
children:
- type: text
slots:
default: ▬ drag to expand / dismiss
- type: text
slots:
default: Sheet content (Half snap point)
Full-snap with close affordance
Full-snap state — expanded sheet with a keyboard-reachable close (gesture is not SR-reachable, so this must exist).
Details
Sheet content (Full snap point)
YAML
type: stack
props:
direction: column
gap: sm
children:
- type: stack
props:
direction: row
gap: md
children:
- type: text
slots:
default: Details
- type: close-button
props:
aria_label: Close sheet
- type: text
slots:
default: Sheet content (Full snap point)
The structural wrapper for every Panel surface — floating, docked, modal, or sheet. It owns the boundary, density, and ARIA role, provides `PanelShellContext` so a nested `<PanelHeader>` renders the correct variant chrome, and composes the header / optional toolbar / required body / optional footer slots into a layout-stable shell. Behavior (title, variant, density, close/dock/undock callbacks) is passed via the `ctx` struct, not as loose props.
Readiness
complete
Preview
live
Props
4
Examples
2
Code
implementation mapped
When to use
Any persistent or dockable side region — left nav rail, right inspector, properties panel.
Modal and bottom-sheet surfaces that still want the unified Panel chrome (variant: modal / sheet).
Generated documentation or admin tools that need sticky secondary chrome with stable layout.
When not to use
Transient non-panel overlays with no boundary — use `<Drawer>`, `<Popover>`, or `<Dialog>` directly.
Content cards inside the main document flow — use `<Card>`.
A bare toolbar or button strip with no panel body — use `<PanelToolbar>` standalone or `<ButtonGroup>`.
Accessibility
Role: complementary
Tab — Moves through focusable controls inside the panel.
Escape — Collapses / closes the panel when the owning shell wires that behavior (modal & sheet).
Screen reader: Role defaults to `region` for Floating/Docked and `dialog` for Modal/Sheet (overridable via `role`). Modal/Sheet variants set `aria-modal=true`. `aria-labelledby` points at `{panel_id}-title` so the title element inside `<PanelHeader>` names the panel.
Notes: Provide a meaningful title in `ctx.title` when multiple complementary panels are visible — it is the SR landmark name.
Props
Name
Type
Default
Description
variant
enum: floating | docked | modal | sheet
—
Layout variant. **Set on `ctx.variant` (the `PanelShellContext`), not as a loose prop.** Determines outer chrome and default ARIA role.
density
enum: comfortable | compact
—
Spacing scale applied as a class modifier. Set on `ctx.density`. Compact is for dense inspectors; comfortable for reading-oriented panels.
role
string
—
Optional explicit ARIA role override. Defaults to `region` (Floating/Docked) or `dialog` (Modal/Sheet). Only set this to correct a landmark conflict.
children
array
—
The body — scrollable content region. Required. Header defaults to a context-driven `<PanelHeader>` when its slot is omitted; toolbar and footer slots are optional.
Examples
Docked shell composition
Approximation of a docked inspector shell — title bar + body + footer actions. Real shell is ctx-driven; this preview uses primitives to show the composed layout.
PropertiesName · my-elementType · python
YAML
type: stack
props:
direction: column
gap: sm
children:
- type: text
slots:
default: Properties
- type: text
slots:
default: Name · my-element
- type: text
slots:
default: Type · python
- type: stack
props:
direction: row
gap: sm
children:
- type: button
props:
variant: ghost
slots:
default: Reset
- type: button
props:
variant: primary
slots:
default: Apply
Modal shell shape
Modal-variant shell shape — title + body + single confirm, focus-trapped in the real component.
Confirm action
This cannot be undone.
YAML
type: card
props:
style: elevated
padding: relaxed
title: Confirm action
slots:
default:
- type: text
slots:
default: This cannot be undone.
- type: button
props:
variant: danger
slots:
default: Delete
The horizontal action row that mounts in `<PanelShell>`'s toolbar slot, directly below the header. Use it for non-primary panel controls: filter chips, view-mode toggles, search-scope selectors, contextual actions. `gap` tunes inter-item spacing; `wrap` controls overflow behavior (wrap to a second line vs. horizontal scroll).
Readiness
complete
Preview
live
Props
3
Examples
2
Code
implementation mapped
When to use
Filter / sort / view-toggle controls that belong to the whole panel, not a single row.
A search-scope segmented control above a list body.
Contextual actions that are secondary to the footer's primary confirm (e.g. 'Select all', 'Export').
When not to use
Primary confirm/cancel actions — those belong in `<PanelFooter>`.
Inline content next to the title (search box, badge) — use `<PanelHeader>` children.
A standalone button cluster outside a panel — use `<ButtonGroup>`.
Accessibility
Role: toolbar
Tab — Enters the toolbar; arrow-key roving is the caller's responsibility if desired.
Enter — Activates the focused control.
Screen reader: N/A
Notes: `role=toolbar` groups the controls for SR users. When the toolbar holds a single logical control group (e.g. a segmented view toggle), prefer a nested `<SegmentedControl>` which manages its own roving tabindex.
Props
Name
Type
Default
Description
gap
string
var(--spacing-sm)
CSS gap value between toolbar items. Accepts any CSS length or custom property. Default is the small spacing token.
wrap
boolean
true
Whether items wrap to a second line on overflow (`true`, default) or stay on one line with horizontal scroll (`false`). Use `false` for narrow docked panels where a wrapped toolbar would jump height.
children
array
—
Toolbar controls — chips, toggles, icon-buttons, a segmented control. Required; an empty toolbar should not be rendered.
Examples
Filter + view toggle
Filter + view-toggle toolbar — the common shape above a list body.
YAML
type: stack
props:
direction: row
gap: sm
children:
- type: button
props:
variant: ghost
size: small
slots:
default: All
- type: button
props:
variant: ghost
size: small
slots:
default: Active
- type: segmented-control
props:
options:
- List
- Grid
No-wrap dense toolbar
No-wrap toolbar for a narrow docked panel — horizontal scroll instead of height jump.
Hosts generated element sections without an outer panel backdrop. The focused element identity floats as its own card, and every section below is a bordered card with small yellow affordances so the canvas remains visible around it.
Readiness
complete
Preview
live
Props
3
Examples
1
Code
implementation mapped
When to use
Right-side generated props/settings panel.
Inspectable objects where identity and sections should feel floating over a canvas.
When not to use
Full-page forms or dashboards.
Panels that need a continuous opaque background for dense tables.
Accessibility
Role: complementary
Tab — Moves from identity controls into section cards.
Escape — Closes the hosting panel when wired by the shell.
Screen reader: N/A
Props
Name
Type
Default
Description
identity
object
—
Focused element identity summary rendered at the top of the inspector.
sections
array
—
Inspector section cards shown below the identity summary.
open
boolean
true
Whether the inspector is visible in the preview.
Examples
Floating props panel
Circle props panel with floating identity and section cards.
YAML
type: props-inspector-shell
props:
identity:
name: ridango-fi
kind: Circle
intention: Add a short intention...
icon: orb
sections:
- title: Presentation
body: Click to write a presentation...
- title: Personality
body: No personality yet. Add the voice, preferences, and working style this circle should follow.
The connection-state indicator rendered as a 4px dot in the top-right of a `<RailItem>` (via its `connection_state` prop). Five states: `connected` (data colour, solid), `streaming` (CTA colour, 1.5s pulse), `disconnected` (muted, solid — the safe default), `error` (bad colour, solid), `reconnecting` (CTA colour, 1.0s pulse). Colour is the primary encoding so `prefers-reduced-motion` can drop the pulse without losing state distinction.
Readiness
complete
Preview
live
Props
2
Examples
2
Code
implementation mapped
When to use
A rail entry whose underlying resource has a live connection worth surfacing (Browser CDP target, agent stream).
Session/tab rails where 'is this thing alive?' is a per-entry question.
Any rail item that should show reconnecting/error without a separate status row.
When not to use
Plain navigation entries with no connection (Library/Settings surfaces) — omit the dot entirely.
Detailed connection diagnostics — that belongs in the body, not a 4px dot.
Non-rail status — use `<WorkspaceStatus>` or a `<Badge>`.
Accessibility
Role: status
No keyboard shortcuts
Screen reader: The dot is `aria-hidden` — it is a visual affordance only. SR users must get connection state from the rail item's label or an accompanying text node; never make the dot the sole carrier of a state a screen-reader user needs.
Notes: Default is `disconnected` — the safe zero-state for a brand-new rail entry that has not yet wired its connection. Pulsing is decorative; colour is load-bearing.
An in-rail filter that narrows visible rail items by typed query. Wraps `<SearchInput>` in rail-tuned spacing. The `query` signal is consumer-owned: the primitive writes keystrokes into it; the consumer reads `query.get()`, derives a memo of matching ids, and renders only those rows. Per spec §11.2 it shows/hides — it never reorders — and the filter primitive deliberately does not know what is being filtered.
Readiness
complete
Preview
live
Props
2
Examples
2
Code
implementation mapped
When to use
A WorkspaceRail with enough items that scanning is slow (above the spec §9/§11.2 threshold).
Session/tab rails where the user knows the name and wants to jump.
As the rail's second child, immediately after the launcher row.
When not to use
Small rails below the count threshold — the filter is chrome-noise there; the consumer should omit it.
Global search across the whole workspace — that is the top-bar search in `<WorkspaceTop>`.
Reordering/sorting — out of scope by design; this only shows/hides.
Accessibility
Role: searchbox
Tab — Focuses the filter input.
Escape — Consumer convention — typically clears the query and restores all rows.
Screen reader: Inherits `<SearchInput>` semantics. Pair filtering with a polite live count ("3 of 24 shown") in the consumer so SR users know the result set changed; the input alone does not announce match counts.
Notes: Disposal-safe by construction: no internal RwSignal/StoredValue — state lives in the consumer's `query` prop, so it survives the `<Show when=n>m>` gate the consumer wraps it in.
Props
Name
Type
Default
Description
query
string
—
Consumer-owned filter value. The primitive writes user keystrokes into this signal; the consumer derives matches from it.
placeholder
string
Filter…
Input placeholder. Override per context ("Filter sessions…", "Filter tabs…") so the affordance reads in the rail's domain.
Examples
Session filter
Session filter — the common shape, contextual placeholder.
One entry in a `<WorkspaceRail>` — a vertical-stacked icon + label `<button>` with hover/focus/active states and the canonical left-edge "you are here" accent when `is_active`. `variant: surface` (default) is a navigable destination; `variant: launcher` is the persistent "+ New X" row whose `aria-current` is locked false (clicking it transfers active state to the freshly-created entry). Optional `connection_state` dot, `tab_count` pill, and hover-revealed close (`closeable` + `on_close`) layer on for session-style rails.
Readiness
complete
Preview
live
Props
6
Examples
2
Code
implementation mapped
When to use
A navigable destination in a rail (surface entry) — `variant: surface`, `is_active` bound to the surface state.
The persistent create row at the top of a dynamic-N rail — `variant: launcher`.
Session entries that need a live connection dot, tab-count, or a close affordance (Browser/Phone rails).
When not to use
A free-standing button outside a rail — use `<Button variant=item>` or `<ItemButton>`.
A route link (semantic navigation) — wrap an `<a>`; rail-item is a button bound to a state machine.
Horizontal toolbars — use `<PanelToolbar>`.
Accessibility
Role: button
Enter — Activate the rail item (native button).
Space — Activate the rail item (native button).
Tab — Moves focus to the item, then to the sibling close button when closeable.
Screen reader: Surface items assert `aria-current=true` when active; launchers lock it to `false`. The close affordance is a SIBLING `<button>` (not nested — invalid HTML) with its own focus stop and a contextual `close_label` ("Close session" / "Close tab").
Notes: The `g <letter>` / `g <number>` vim-style jump is handled by the consumer's keyboard handler via `<RailKeymap>` context, not by the item itself.
Props
Name
Type
Default
Description
icon
string
—
Material Symbols Outlined icon name. Required.
label
string
—
Visible label text. Required.
variant
enum: surface | launcher
surface
Visual + semantic variant.
is_active
boolean
false
Whether this is the active surface (drives `aria-current` + the left-edge accent). Reactive in the impl; ignored for launchers.
A data carrier that binds `g <key>` vim-style shortcuts to rail surfaces for one Home Screen. Each Home Screen provides its own `RailKeymap` via Leptos context on mount; the global `g` dispatcher resolves the nearest one and looks up the second keystroke. This is the element-agnostic seam: the dispatcher never knows surface kinds upfront — Library binds `b/p/m/a`, Browser binds `1..9` + `n`, future screens bind whatever they need. Single-centered invariant means the most-recently-mounted provider wins; no global registry.
Readiness
complete
Preview
live
Props
2
Examples
2
Code
implementation mapped
When to use
A Home Screen with a rail that wants `g <letter>` / `g <number>` jump shortcuts.
Binding rail positions to keys without coupling the global dispatcher to surface enums.
Any surface that mounts a rail and wants keyboard power-user navigation.
When not to use
Global app shortcuts unrelated to the rail (command palette, save) — those live in their own handler.
A single non-repeating shortcut — a plain keydown handler is simpler than a keymap context.
Rendering anything — this carries no visual; it is consumed by `<RailItem>` / `<WorkspaceRail>`.
Accessibility
Role: presentation
g — Prefix key — enters rail-jump mode; the next key is resolved against this keymap.
Screen reader: Not a rendered element — nothing to announce. The accessibility value is indirect: it gives keyboard/power users a fast path to rail surfaces. Always pair with visible focus states on the rail items the keymap targets so the jump destination is perceivable.
Notes: This is a configuration carrier, not a component. The keymap is consumed by RailItem / WorkspaceRail to wire shortcuts; it renders no DOM of its own.
Props
Name
Type
Default
Description
bindings
array
—
The `g <key>` → surface bindings for this Home Screen. Each entry maps a single second-keystroke to a rail surface identifier the consumer's state machine understands.
prefix
string
g
The prefix key that enters rail-jump mode. Defaults to `g` (vim-style). Rarely overridden.
Examples
Library keymap (data shape)
Library Home Screen keymap — surface-letter bindings (b/p/m/a).
g b → Boardg p → Propertiesg m → Membersg a → Activity
YAML
type: stack
props:
direction: column
gap: sm
children:
- type: text
slots:
default: g b → Board
- type: text
slots:
default: g p → Properties
- type: text
slots:
default: g m → Members
- type: text
slots:
default: g a → Activity
Browser keymap (data shape)
Browser Home Screen keymap — session-position bindings (1..9 + n launcher).
g 1 → Session 1g 2 → Session 2g n → New session (launcher)
YAML
type: stack
props:
direction: column
gap: sm
children:
- type: text
slots:
default: g 1 → Session 1
- type: text
slots:
default: g 2 → Session 2
- type: text
slots:
default: g n → New session (launcher)
The overflow indicator at the bottom fold of a `<WorkspaceRail>`. Shows `+N more` when items exceed the visible height. When `on_click` is provided it renders a keyboard-reachable `<button>` that scrolls the next page of items into view; when omitted it renders a focus-skipped informational `<span>` (Library has no scroll-overflow today and passes no handler). Hidden via a `--hidden` class when `overflow_count` is 0, so the DOM stays stable across reactive count changes.
Readiness
complete
Preview
live
Props
2
Examples
2
Code
implementation mapped
When to use
A rail whose item count can exceed the viewport and needs a 'more' affordance (Browser sessions).
Surfacing the hidden-count so users know the list is truncated.
As the last child of `<WorkspaceRail>`, below the item list.
When not to use
Rails that never overflow (small fixed surface lists) — omit the sentinel entirely.
Paginated data tables — use a real pager, not a rail sentinel.
A generic 'load more' button in body content — use `<Button>`.
Accessibility
Role: button
Tab — Focuses the sentinel only when it is a button (on_click provided); the informational span is skipped.
Enter — Scrolls the next page into view (button form).
Screen reader: Button form carries `aria-label=\"Show N more items\"`. Informational span form is `aria-hidden` — when there is no scroll action, the hidden count is decorative and SR users should learn truncation from a polite count elsewhere, not an inert chip.
Notes: Presentational only — all state (count, click) lives in props per the workspace_shell module contract.
Props
Name
Type
Default
Description
overflow_count
integer
—
Number of items hidden beyond the visible fold. The chip is hidden (via `--hidden`) when this is 0. Required.
on_click
string
—
Optional scroll handler ref. Present → keyboard-reachable `<button>`; absent → focus-skipped informational `<span>`. Bound via the action registry.
A modal overlay anchored to one screen edge that slides in when opened. `open` is reactive and caller-owned: the Sheet stays mounted and toggles a CSS modifier (always-mounted + CSS-visibility) so the slide has a stable element and the children mount once. Escape, a backdrop click, and the close button all invoke `on_close`; a Sheet with no `on_close` is intentionally non-dismissable. The overlay fills its nearest positioned ancestor, so wrap it in a fixed inset-0 container for a viewport-level drawer.
Readiness
complete
Preview
live
Props
4
Examples
3
Code
implementation mapped
When to use
A filter / detail inspector that slides in from the right
A mobile navigation drawer from the left
A bottom action sheet on small viewports
Any temporary panel that should overlay rather than reflow content
When not to use
A centred confirmation / form modal — use Dialog (it focus-traps)
Persistent side chrome that never dismisses — use the PanelShell family
Inline expand/collapse — use Disclosure or CollapsibleSection
A non-modal popover anchored to a trigger — use Popover
Accessibility
Role: dialog
Escape — Invoke on_close (only while open)
Screen reader: The panel is `role=dialog` + `aria-modal=true` and takes its accessible name from `title` (falling back to 'Sheet'). The close button has an explicit aria-label. While closed the overlay is `aria-hidden=true` and pointer-events are suppressed so it is not reachable.
Notes: A full focus trap is intentionally out of scope — the panel is programmatically focusable (tabindex=-1); compose with the use_focus_trap hook when the consuming surface needs a hard trap.
Props
Name
Type
Default
Description
open
boolean
false
Whether the Sheet is open. Reactive and caller-owned.
side
enum: left | right | top | bottom
right
title
string
—
Optional sticky-header heading; also the dialog's accessible name.
on_close
string
—
Action ref invoked on Escape / backdrop / close button.
Examples
Filter drawer
A right-anchored filter sheet, open.
Filters
Status, owner, and date-range controls go here.
YAML
type: sheet
props:
open: true
side: right
title: Filters
slots:
default:
- type: text
slots:
default: Status, owner, and date-range controls go here.
A specialized card for a single numeric KPI: a large value, a small supporting label, an optional change indicator (coloured green/red via `change_positive`), and an optional icon. Use it when "the unit on the dashboard" IS a metric — it enforces a consistent metric rhythm so a row of StatCards reads as one coherent set rather than mismatched tiles.
Readiness
complete
Preview
live
Props
5
Examples
3
Code
implementation mapped
When to use
A dashboard KPI tile — total users, revenue, active sessions, error rate.
A metric with a period-over-period trend ("+12%" green, "-3%" red).
A row/grid of comparable metrics that should share visual rhythm.
When not to use
Grouped non-metric content — use plain `<Card>`.
A tiny inline count beside a label — use `<CounterPill>` / `<Badge>`.
A chart or sparkline — use the data-display chart components; StatCard is the single-number tile.
Accessibility
Role: region
No keyboard shortcuts
Screen reader: The card is a `region`; label + value should read as a unit ("Total Users, 1,234, up 12%"). The trend's meaning must not rely on colour alone — `change` carries a sign ("+12%" / "-3%") so red/green is redundant reinforcement, not the sole signal.
Notes: Keep `value` pre-formatted (thousands separators, units). The card does not format numbers — that is the caller's responsibility so locale/format stays consistent across a dashboard.
The shared status-footer base: a `<footer role=status aria-live=polite>` with a caller-supplied CSS class so each consuming pattern (`ces__status`, and post Phase-9 the workspace status) scopes its own layout while sharing one polite-live-region aria contract. `<EditorStatus>` composes it today; compose it for any new pattern that needs an ambient status footer rather than re-declaring the aria.
Readiness
complete
Preview
live
Props
2
Examples
2
Code
implementation mapped
When to use
Building a pattern that needs an ambient status footer — wrap its status row in StatusBarSlot.
Anywhere a `role=status aria-live=polite` footer with a scoped class is needed.
As the base `<EditorStatus>` (and, post Phase-9, `<WorkspaceStatus>`) composes onto.
When not to use
Primary actions — a status region is read-only/ambient; actions belong in a footer action bar.
Assertive alerts that must interrupt — use an `alert` region; this is deliberately polite.
Per-panel status pills — use `<PanelFooter align=between>`'s status slot.
Accessibility
Role: status
No keyboard shortcuts
Screen reader: `role=status aria-live=polite` — updates are announced after the user's current action, not interrupting. Keep content terse; long/frequently-changing status text is fatiguing read aloud. Don't put focusable controls here — status regions are not action surfaces.
Notes: Polite by construction. This is the single place the status-footer aria contract lives; consumers add only the per-pattern class + content.
Props
Name
Type
Default
Description
class
string
—
Per-pattern CSS class (e.g. `ces__status`) so each consumer scopes its own status-bar layout. Required.
children
array
—
Ambient status segments — short indicators. Required; keep terse, non-interactive.
Examples
Editor status composition
Editor status composed on the slot (class=ces__status).
Ln 12, Col 4Rust main
YAML
type: stack
props:
direction: row
gap: md
children:
- type: text
slots:
default: Ln 12, Col 4
- type: text
slots:
default: Rust
- type: text
slots:
default: ' main'
Generic status composition
Generic ambient status row composed on the same base.
● ConnectedSynced 1m ago
YAML
type: stack
props:
direction: row
gap: md
children:
- type: text
slots:
default: ● Connected
- type: text
slots:
default: Synced 1m ago
A compact bordered surface for one unit inside a dense grid. Tile keeps Card's surface language (raised background, subtle border, calm radius) but with tight padding so a wall of them stays scannable. Set `interactive` to paint a hover/active lift for clickable cells, and `selected` to mark the active cell with an accent ring. Tile only paints the affordance — wrap it in a button or link for real keyboard focus.
Readiness
complete
Preview
live
Props
3
Examples
3
Code
implementation mapped
When to use
A cell in a grid of many small units (element tiles, connector logos)
Compact metric squares on a dense dashboard
Selectable grid options where one cell is the current choice
Repeating thumbnails that need a consistent frame without Card's bulk
When not to use
A content unit that needs header / footer chrome — use Card
A single full-width grouped section — use Card or Well
Modal / overlay surfaces — use Dialog or Sheet
A real button: Tile is a surface; wrap it in a button/link for clicks
Accessibility
Role: region
No keyboard shortcuts
Screen reader: A plain Tile is `role=region`. When `selected` is set it emits `aria-current=true` so assistive tech announces the active cell. `interactive` paints visual affordance only — the consumer MUST wrap the Tile in a `<button>` or `<a>` for it to be keyboard focusable; visual interactivity alone is never focusable.
Notes: Don't convey selection by colour alone — the accent ring is paired with `aria-current` so non-sighted users get the same signal.
Props
Name
Type
Default
Description
interactive
boolean
false
Paint a hover/active lift affordance (does not make the tile focusable).
selected
boolean
false
Mark the tile as the current selection (accent ring + aria-current).
class
string
—
Extra classes appended after the computed base/modifier classes.
A raised bordered strip that groups a row of controls not tied to a panel. Toolbar owns the gap, alignment, and `role=toolbar` semantics so the consumer just drops buttons in. `align` distributes the controls along the bar — `between` pushes the first and last groups to opposite edges (e.g. destructive action far from the primary one).
Readiness
complete
Preview
live
Props
3
Examples
3
Code
implementation mapped
When to use
An action bar above a free-standing list or editor surface
A grouped row of buttons that should read as one control surface
A formatting / command strip independent of any panel chrome
Primary and secondary actions split to opposite edges (`between`)
When not to use
A toolbar bound to a panel header/footer — use PanelToolbar
A single primary action — just use Button
Navigation between views — use Tabs
A vertical control rail — Toolbar is horizontal by contract
Accessibility
Role: toolbar
Tab — Move through the controls in DOM order (native)
Screen reader: Emits `role=toolbar` + `aria-orientation=horizontal`, and accepts an `aria_label` so the control group is announced. Tab order is the native DOM order of the children.
Notes: Arrow-key roving tabindex is intentionally NOT claimed — it requires the child controls to cooperate, and promising it without that wiring would be a false accessibility guarantee. Always pass `aria_label` when the toolbar's purpose isn't obvious from context.
Props
Name
Type
Default
Description
align
enum: start | center | end | between
start
aria_label
string
—
Accessible name announced for the toolbar group.
class
string
—
Extra classes appended after the computed base/modifier classes.
Examples
Split actions
An editor action bar with primary + secondary split.
A recessed surface that visually demotes the content nested inside it. Use Well for output blocks, read-only recaps, and quiet metadata strips that sit inside a louder surface and should read as secondary. The three padding steps (`compact | cozy | spacious`) keep the inset rhythm consistent so two wells on the same surface look identical.
Readiness
complete
Preview
live
Props
2
Examples
3
Code
implementation mapped
When to use
A command / log output block inside a panel
A 'before you continue' recap nested inside a form
A quiet metadata strip demoted below primary content
Any nested block that should read as secondary to its container
When not to use
Primary grouped content — use Card (it should be lifted, not sunk)
A dense grid cell — use Tile
A slide-in overlay — use Sheet
An interactive surface — Well is a static, non-interactive frame
Accessibility
Role: region
No keyboard shortcuts
Screen reader: Well is `role=region`. It is purely presentational chrome; the nested content keeps its own semantics. Set `aria-label` on the content (not the Well) when the region needs a name.
Notes: The recessed look is decorative — never use the sunken background as the only signal that content is read-only; say so in the content.
Props
Name
Type
Default
Description
padding
enum: compact | cozy | spacious
cozy
class
string
—
Extra classes appended after the computed base/modifier classes.
Examples
Output block
A build-output recap demoted inside a panel.
stdout — build finished in 1.2s, 0 warnings
YAML
type: well
props:
padding: cozy
slots:
default:
- type: text
slots:
default: stdout — build finished in 1.2s, 0 warnings
Compact
A compact metadata strip.
Last deployed 2h ago · commit a1b2c3d
YAML
type: well
props:
padding: compact
slots:
default:
- type: text
slots:
default: Last deployed 2h ago · commit a1b2c3d
Padding scale
All three padding steps for comparison.
compact
cozy
spacious
YAML
type: stack
props:
gap: sm
direction: column
children:
- type: well
props:
padding: compact
slots:
default: compact
- type: well
props:
padding: cozy
slots:
default: cozy
- type: well
props:
padding: spacious
slots:
default: spacious
The primary content region — `role=main` — of `<WorkspaceShell>`. Holds whatever the active rail entry resolves to (a panel surface, a canvas, a docs page). Set `aria_label` to the active rail-entry label so screen readers announce the surface change when the user switches rail items without a full route navigation.
Readiness
complete
Preview
live
Props
2
Examples
2
Code
implementation mapped
When to use
The main work area of any WorkspaceShell — exactly one per shell.
Surfaces where the rail swaps body content in-place (SR needs the label to track).
Any shell that needs a single, correctly-labelled `main` landmark.
When not to use
Secondary panels alongside the main area — use the Panel family.
The rail or top regions — those are `<WorkspaceRail>` / `<WorkspaceTop>`.
Multiple co-equal content panes — compose them inside one body, don't emit two.
Accessibility
Role: main
Tab — Moves focus from chrome into the body content.
Screen reader: `role=main` with `aria_label` (default "Workspace") tracking the active surface. When the rail swaps content in-place, updating `aria_label` to the new entry's label makes SR users hear the surface change without a route transition.
Notes: Exactly one `<WorkspaceBody>` per shell — `main` is a singleton landmark. Keep the label in sync with the active rail entry.
Props
Name
Type
Default
Description
aria_label
string
Workspace
`main` landmark label. Set to the active rail-entry label so SR users hear in-place surface changes.
children
array
—
The active surface content. Required; exactly one body per shell.
WorkspaceBreadcrumb is the top-bar path contract for reusable workspace shells. It keeps the current leaf readable while allowing ancestors to become explicit navigation actions when the host surface owns a return path, such as Browser returning to its overview from a focused chromeless session.
Readiness
complete
Preview
live
Props
2
Examples
2
Code
implementation mapped
When to use
WorkspaceShell top bars where the path is part of the chrome, not the content body
Focused surfaces that need a stable parent breadcrumb, for example Browser / ridanq / chromeless
Paths where the current leaf must remain visible even if middle segments collapse
Reusable panels that should expose parent navigation without inventing local back buttons
When not to use
Global route navigation; use app navigation, tabs, or the magnetic rail
Linear progress or onboarding flows; use a stepper
Long filesystem-style paths where every segment must remain individually visible
Accessibility
Role: navigation
Tab — Moves through actionable ancestor segments in visual order
Enter — Activates the focused ancestor segment when it has an action
Space — Activates the focused ancestor segment when it has an action
Screen reader: The breadcrumb uses a Path navigation landmark. The final segment is marked with aria-current=page; actionable ancestors are native buttons with labels such as "Open Browser".
Notes: Leaf segments are never clickable. Keep labels short enough for a top bar; if a label is user-authored, shorten it before passing it to the component.
Props
Name
Type
Default
Description
segments*
array
—
Ordered breadcrumb labels from root to leaf.
on_segment_activate
array
—
Optional host callbacks by segment index. Non-leaf entries with a callback render as buttons; the leaf stays aria-current text.
Examples
Browser focus path
Focused chromeless browser session with Browser as the return ancestor.
The persistent left-rail navigation region of `<WorkspaceShell>`. Renders an `<aside role=navigation>` with a scrollable item list. Compose rail entries (`<RailItem>`, filters, keymap hints) as children. `aria_label` should describe the rail's contents so screen readers announce a meaningful region on entry.
Readiness
complete
Preview
live
Props
2
Examples
2
Code
implementation mapped
When to use
Primary persistent navigation for a workspace surface — session list, tab strip, section nav.
A vertical affordance strip that stays put while the body content changes.
Any WorkspaceShell that needs a labelled navigation landmark.
When not to use
A dockable/closable secondary inspector — that is the Panel family (`<PanelShell variant=docked>`).
A horizontal action row — use `<PanelToolbar>` or `<ButtonGroup>`.
Transient nav (command palette, popover menu) — use the overlay family.
Accessibility
Role: navigation
ArrowUp — Move focus to previous rail item.
ArrowDown — Move focus to next rail item.
Tab — Enters/leaves the rail region.
Screen reader: `aria_label` (default "Workspace context menu") names the navigation landmark. Sprint 4+ callers should override to describe contents ("Browser sessions", "Phone tabs") so the region announcement is meaningful rather than generic.
Notes: Roving arrow-key focus across rail items is the expected interaction; keep the item list a flat focusable sequence so Up/Down is predictable.
Props
Name
Type
Default
Description
aria_label
string
Workspace context menu
Navigation-landmark label. Override to describe the rail's contents ("Browser sessions", "Phone tabs") rather than the generic default.
children
array
—
Rail entries — `<RailItem>`s, filters, keymap hints, overflow sentinel. Rendered in a scrollable list. Required.
Examples
Session rail
Session-list rail — the common shape, labelled by contents.
The outer application frame for authenticated work surfaces: persistent navigation, top chrome, primary content, and an optional status region. Compose `<WorkspaceTop>`, `<WorkspaceRail>`, `<WorkspaceBody>`, `<WorkspaceStatus>` inline as children — the shell lays them into a stable grid and owns the single `role=application` wrapper. Use it when the experience is a tool people return to repeatedly, not a marketing page or a single isolated card.
Readiness
complete
Preview
live
Props
2
Examples
2
Code
implementation mapped
When to use
Full-height operational tools with rail navigation and a main work area.
Generated apps that need predictable chrome across routes.
Reference browsers such as the platform docs viewer.
Surfaces that need stable scroll ownership split between rail, top, and content.
When not to use
Standalone marketing or hero pages.
Small embedded widgets inside an existing shell.
Modal, drawer, or popover content — use the Panel family.
Accessibility
Role: application
Tab — Moves through persistent chrome and into the active content region.
Escape — May close temporary overlays owned by the shell.
Screen reader: `aria_label` names the `role=application` wrapper (e.g. "Library Home Screen"). Pair with labelled landmark children: navigation for the rail, banner/header for top chrome, main for body, status for footer — each marker component sets its own role.
Notes: Provide a meaningful `aria_label` — it is the first thing announced on entry and the only landmark name for the whole application surface.
Props
Name
Type
Default
Description
aria_label
string
—
Label announced on the `role=application` wrapper when focus enters the shell (e.g. "Library Home Screen"). Required.
children
array
—
The four marker children, composed inline in order: `<WorkspaceTop>`, `<WorkspaceRail>`, `<WorkspaceBody>`, `<WorkspaceStatus>`. Top/rail/status are optional; body is the primary surface.
Examples
Workspace frame composition
Workspace frame shape — top band, rail, main body, status footer. Real shell uses grid cells from marker children; this preview stacks primitives to show the regions.
▔ top: avatar · breadcrumb · search · actions
▌railmain work area
▁ status
YAML
type: stack
props:
direction: column
gap: sm
children:
- type: text
slots:
default: '▔ top: avatar · breadcrumb · search · actions'
- type: stack
props:
direction: row
gap: md
children:
- type: text
slots:
default: ▌rail
- type: text
slots:
default: main work area
- type: text
slots:
default: ▁ status
Body-only shell
Minimal shell — body only, no rail/top (docs viewer shape).
Reference content fills the body region; no rail.
YAML
type: card
props:
style: flat
padding: relaxed
slots:
default:
- type: text
slots:
default: Reference content fills the body region; no rail.
The bottom status band of `<WorkspaceShell>` — a `<footer role=status aria-live=polite>`. Holds ambient, non-actionable workspace state: connection health, sync progress, agent activity, build status. Unlike `<PanelFooter>` (per-panel primary actions), this is application-wide ambient status — read-only, announced politely.
Readiness
complete
Preview
live
Props
1
Examples
2
Code
implementation mapped
When to use
Ambient workspace state — connection/sync/agent-activity/build indicators.
A persistent status strip whose updates should reach SR users without stealing focus.
Exactly one per WorkspaceShell, placed last among the marker children.
When not to use
Primary confirm/cancel actions — those belong in `<PanelFooter>`.
Urgent/assertive alerts that must interrupt — use an alert region, not the polite status band.
Per-panel status pills — use `<PanelFooter align=between>`'s status slot.
Accessibility
Role: status
No keyboard shortcuts
Screen reader: `role=status` + `aria-live=polite` so updates are announced after the user finishes their current action rather than interrupting. Keep content terse — long status text is fatiguing when read aloud on every change.
Notes: Polite by design — do not use for errors that need immediate attention; route those to `<PanelErrorBanner>` (alert) instead.
Props
Name
Type
Default
Description
children
array
—
Ambient status content — short indicators (connection dot + label, sync progress, agent-activity count). Required; keep terse.
Examples
Connection + sync status
Connection + sync ambient status — the common shape.
● ConnectedSynced 2m ago3 agents active
YAML
type: stack
props:
direction: row
gap: md
children:
- type: text
slots:
default: ● Connected
- type: text
slots:
default: Synced 2m ago
- type: text
slots:
default: 3 agents active
Build status only
Single build-status indicator — minimal status band.
● Build green · dev @ a5f1565
YAML
type: stack
props:
direction: row
gap: sm
children:
- type: text
slots:
default: ● Build green · dev @ a5f1565
The full-width top band of `<WorkspaceShell>` — a `<header>` placed first among the shell's children. Hosts application-wide chrome: the circle/avatar control, the breadcrumb path, global search, and right-side actions. Unlike `<PanelHeader>` (per-panel, variant-aware), this is the single top bar shared across every rail surface.
Readiness
complete
Preview
live
Props
1
Examples
2
Code
implementation mapped
When to use
The application-wide top bar of a WorkspaceShell — exactly one per shell.
Hosting the breadcrumb path, global search, and account/avatar control.
Right-side global actions (notifications, command palette trigger).
When not to use
Per-panel title + window controls — that is `<PanelHeader>`.
A secondary action row scoped to one surface — use `<PanelToolbar>`.
The rail or body regions — those are sibling marker components.
Accessibility
Role: banner
Tab — Moves through avatar, breadcrumb actions, search, and right-side actions in order.
Screen reader: Rendered as a `<header>` so it maps to the `banner` landmark when it is the top-level header of the application surface. Keep interactive children individually labelled (search input, action buttons).
Notes: Exactly one `<WorkspaceTop>` per shell, placed first so the header landmark is the first thing in DOM/tab order.