Surfaces

app-shell-root

#existingsource

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

NameTypeDefaultDescription
aria_labelstringSingle landmark name for the application surface. Required, specific.
classstringOuter-wrapper CSS class, distinct per shell pattern (`ws-shell` vs `ces`) so each pattern's CSS scopes its grid to its own root. Required.
childrenarrayThe shell pattern's content composed onto this base. Required.

Examples

IDE base

An IDE surface composed on AppShellRoot (CodeEditorShell uses class=ces).

role=application · aria-label="Dev IDE" · class=ces
YAML
type: card
props:
  style: flat
  padding: none
slots:
  default:
  - type: text
    slots:
      default: role=application · aria-label="Dev IDE" · class=ces
Workspace base

A workspace surface composed on the same base (class=ws-shell).

role=application · aria-label="Library" · class=ws-shell
YAML
type: card
props:
  style: flat
  padding: none
slots:
  default:
  - type: text
    slots:
      default: role=application · aria-label="Library" · class=ws-shell
Edit YAML

browser-chrome

#existingsource

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

NameTypeDefaultDescription
tabsarrayOrdered tab list (active first by convention). Each item is a tab model.
addressstringThe address shown in the address bar (the active tab's URL).
modeenum: miniature | fullfullSurface scale. `full` = interactive opened panel; `miniature` = static scaled-down tile.
on_tab_selectstringFull mode: a tab was clicked — argument is the tab id.
on_tab_closestringFull mode: a tab's close affordance was clicked — argument is the tab id.
on_new_tabstringFull mode: the new-tab '+' was clicked.
on_backstringFull mode: the back nav button was clicked.
on_forwardstringFull mode: the forward nav button was clicked.
on_reloadstringFull mode: the reload nav button was clicked.

Examples

Full panel chrome

The opened browser panel chrome — multiple tabs + an address bar at full scale.

YAML
type: browser-chrome
props:
  mode: full
  address: https://anthropic.com/research
  tabs:
  - id: t1
    title: Anthropic — Research
    favicon_url: https://anthropic.com/favicon.ico
    active: true
    loading: false
  - id: t2
    title: GitHub
    favicon_url: https://github.com/favicon.ico
    active: false
    loading: false
  - id: t3
    title: Loading…
    favicon_url: ''
    active: false
    loading: true
Miniature tile chrome

The overview-grid tile chrome — one active tab + a couple background tabs, static and scaled down.

https://triform.wtf
YAML
type: browser-chrome
props:
  mode: miniature
  address: https://triform.wtf
  tabs:
  - id: t1
    title: triform.wtf
    favicon_url: ''
    active: true
    loading: false
  - id: t2
    title: Docs
    favicon_url: ''
    active: false
    loading: false
  - id: t3
    title: SigNoz
    favicon_url: ''
    active: false
    loading: false
Edit YAML

browser-viewport

#existingsource

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

NameTypeDefaultDescription
sourceenum: live_webrtc | cached_frame | standbystandbyWhat 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.
urlstringImage URL for the `cached_frame` source. Ignored for other sources.
modeenum: miniature | fullfullSurface scale. `miniature` locks 16:10; `full` fills the container.
loadingbooleanfalsePaint 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.

YAML
type: browser-viewport
props:
  mode: miniature
  source: cached_frame
  url: https://triform.dev/assets/showcase/browser-frame-sample.png
  loading: false
Standby

No frame has arrived yet — the standby placeholder.

Not started yet
YAML
type: browser-viewport
props:
  mode: miniature
  source: standby
  loading: false
Edit YAML

card

#existingsource

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

NameTypeDefaultDescription
titlestringOptional title rendered in the card header.
subtitlestringOptional subtitle rendered under the title.
styleenum: default | elevated | flat | interactivedefault
paddingenum: none | tight | default | relaxeddefault

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.
Style gallery

All four styles side-by-side.

Default

Elevated

Flat

Interactive

YAML
type: stack
props:
  gap: md
  direction: row
children:
- type: card
  props:
    style: default
    title: Default
- type: card
  props:
    style: elevated
    title: Elevated
- type: card
  props:
    style: flat
    title: Flat
- type: card
  props:
    style: interactive
    title: Interactive
Edit YAML

code-editor-shell

#existingsource

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

NameTypeDefaultDescription
aria_labelstringLabel for the `role=application` wrapper (e.g. "Dev IDE"). Required.
tree_collapsedbooleanfalseConsumer-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.
childrenarrayThe 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.

▌tree editor 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
Edit YAML

connection-management-card

#existingsource

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

NameTypeDefaultDescription
titlestringYour connectionsCard heading for the grouped connection list.
actionstringAddPrimary add action label.
groupsarrayConnection groups, each with a name, icon, and compact rows.

Examples

Your connections

Connections card with compact row actions.

Connections

Your connections

Email
support@ridango.comReady
billing@ridango.comDraft
YAML
type: connection-management-card
props:
  title: Your connections
  action: Add
  groups:
  - name: Email
    icon: mail
    rows:
    - name: support@ridango.com
      state: Ready
      action: Edit
    - name: billing@ridango.com
      state: Draft
      action: Edit
Edit YAML

counter-pill

#existingsource

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

NameTypeDefaultDescription
labelstringThe 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+
Edit YAML

editor-body

#existingsource

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

NameTypeDefaultDescription
childrenarrayThe 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.

main.rs ✕ │ lib.rs src › main.rs
text
fn main() {
    println!("hi");
}
YAML
type: stack
props:
  direction: column
  gap: sm
children:
- type: text
  slots:
    default: main.rs ✕ │ lib.rs
- type: text
  slots:
    default: src › main.rs
- type: code-block
  props:
    code: |-
      fn main() {
          println!("hi");
      }
No file open

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
Edit YAML

editor-palette

#existingsource

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

NameTypeDefaultDescription
showbooleanConsumer-owned visibility signal. Portal stays mounted; this toggles the visible/aria-hidden class. Required.
on_closestringHandler ref invoked on backdrop click. Required. Esc-to-close is the consumer's separate responsibility (ADR-12 Q5).
childrenarrayConsumer 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 Symbol Refactor: 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.rs tests/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
Edit YAML

editor-status

#existingsource

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.
  • Surfacing cursor/encoding/language/branch/diagnostics ambient state.
  • 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

NameTypeDefaultDescription
childrenarrayStatus 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 4 UTF-8 Rust 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
Minimal status bar

Minimal status — language + branch only.

Markdown main
YAML
type: stack
props:
  direction: row
  gap: md
children:
- type: text
  slots:
    default: Markdown
- type: text
  slots:
    default: ' main'
Edit YAML

editor-tree

#existingsource

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

NameTypeDefaultDescription
childrenarrayThe 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
Edit YAML

entity-detail-shell

#existingsource

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

NameTypeDefaultDescription
aria_labelledbystringId of the variant's header element, wired to the dialog's `aria-labelledby`. Required — the shell has no title of its own.
variant_classstringOptional 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_closeobjectCallback fired when the user clicks the backdrop, presses Esc, or clicks the close control. The parent clears its selection to unmount the modal.
op_errobjectSignal 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.
childrenarrayThe 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 Chen VP 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
Edit YAML

panel-body

#existingsource

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.
  • Panels whose child owns scroll itself (interactive canvas, code editor) — `scrollable: false`.

When not to use

  • 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

NameTypeDefaultDescription
scrollablebooleantrueWhether the body owns its own scroll container. `false` is the escape hatch for panels whose child (canvas, code editor) manages scroll internally.
full_bleedbooleanfalseZeros the body's padding for full-bleed media (browser screencast, monaco region). Header and footer padding from `<PanelShell>` are unaffected.
childrenarrayBody content. Lists, forms, sections, embedded canvases. Required; a body with no children should not be rendered.

Examples

Scrollable list body

Standard scrollable body with a list of items.

Connection · Email · Ready Connection · Slack · Draft Connection · GitHub · Ready
YAML
type: stack
props:
  direction: column
  gap: sm
children:
- type: text
  slots:
    default: Connection · Email · Ready
- type: text
  slots:
    default: Connection · Slack · Draft
- type: text
  slots:
    default: Connection · GitHub · Ready
Full-bleed shape

Full-bleed body shape — would host a canvas or monaco region with zero padding (illustrated via a card with no inner space).

[full-bleed child — canvas, monaco, screencast]
YAML
type: card
props:
  padding: none
  style: flat
slots:
  default:
  - type: text
    slots:
      default: '[full-bleed child — canvas, monaco, screencast]'
Edit YAML

panel-empty-state

#existingsource

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

NameTypeDefaultDescription
iconstringMaterial Symbols Outlined icon name (e.g. `inbox`, `cable`, `notifications_none`). Required. Should visually echo the absent-content category.
titlestringShort heading describing the empty state in the user's terms. "No connections yet", "No recent activity". Required.
descriptionstringOptional supporting copy explaining what would populate the list or how to get started. One sentence; longer copy belongs in documentation, not in-panel chrome.
childrenarrayOptional 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 yet Add 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 results Try 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.
Edit YAML

panel-error-banner

#existingsource

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

NameTypeDefaultDescription
toneenum: recoverable | fatal | noticerecoverableSeverity tone — selects icon + signal palette.
titlestringShort statement of what failed. Required.
bodystringOptional one-line why/how. Best populated from `ApiErrorResponse._suggestion`.
on_retrystringOptional retry handler ref. When present, renders a "Retry" button. Bound via the action registry (`${actions.retry}`).
on_dismissstringOptional 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 connections The 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
Edit YAML

panel-header

#existingsource

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

NameTypeDefaultDescription
titlestringPanel 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.
variantenum: floating | docked | modal | sheetLayout 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.
childrenarrayOptional 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.

Properties
YAML
type: stack
props:
  direction: row
  gap: md
children:
- type: text
  slots:
    default: Properties
- type: icon-button
  props:
    icon: open_in_new
    aria_label: Detach from sidebar
- type: close-button
  props:
    aria_label: Close panel
Edit YAML

panel-loading-state

#existingsource

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

NameTypeDefaultDescription
shapeenum: list | detail | chart | spinnerlistSkeleton shape — mirror the loaded content's layout.
captionstringOptional label, e.g. "Loading session…". Announced to SR users.
rowsinteger6Skeleton 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.

Loading connections…
YAML
type: stack
props:
  direction: column
  gap: sm
children:
- type: text
  slots:
    default: Loading connections…
- type: card
  props:
    padding: tight
    style: flat
- type: card
  props:
    padding: tight
    style: flat
- type: card
  props:
    padding: tight
    style: flat
Spinner fallback

Spinner fallback — explicit last-resort variant when no shaped skeleton fits.

Loading…
YAML
type: stack
props:
  direction: column
  gap: sm
children:
- type: spinner
- type: text
  slots:
    default: Loading…
Edit YAML

panel-section

#existingsource

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

NameTypeDefaultDescription
titlestringSection heading — short noun phrase. Required. Doubles as the SR landmark label.
collapsiblebooleanfalseWhether the section can collapse. `false` (default) renders a static titled block; `true` adds a disclosure chevron and click target.
expandedbooleantrueInitial expanded state when `collapsible: true`. Ignored when the section is not collapsible. Set `false` for "Advanced" blocks that should start closed.
actionsarrayOptional controls rendered in the section header (right side) — typically a single per-section action like "Reset" or "Add".
childrenarraySection 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)
Edit YAML

panel-sheet-snap

#existingsource

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

NameTypeDefaultDescription
stateenum: half | full | closedCurrent snap state, owned by the caller's state machine and passed as a reactive signal. `closed` corresponds to `None` in the impl.
thresholdinteger80Pixel drag distance past which a transition fires. Lower = more sensitive snapping; default 80px.
on_transitionstringRequired handler ref for emitted transitions (`expand` / `collapse` / `close`). Bound via the action registry; the caller advances its own snap state machine in response.
childrenarrayThe 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 / dismiss Sheet 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)
Edit YAML

panel-shell

#existingsource

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

NameTypeDefaultDescription
variantenum: floating | docked | modal | sheetLayout variant. **Set on `ctx.variant` (the `PanelShellContext`), not as a loose prop.** Determines outer chrome and default ARIA role.
densityenum: comfortable | compactSpacing scale applied as a class modifier. Set on `ctx.density`. Compact is for dense inspectors; comfortable for reading-oriented panels.
rolestringOptional explicit ARIA role override. Defaults to `region` (Floating/Docked) or `dialog` (Modal/Sheet). Only set this to correct a landmark conflict.
childrenarrayThe 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.

Properties Name · my-element Type · 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
Edit YAML

panel-toolbar

#existingsource

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

NameTypeDefaultDescription
gapstringvar(--spacing-sm)CSS gap value between toolbar items. Accepts any CSS length or custom property. Default is the small spacing token.
wrapbooleantrueWhether 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.
childrenarrayToolbar 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.

YAML
type: stack
props:
  direction: row
  gap: sm
children:
- type: icon-button
  props:
    icon: filter_list
    aria_label: Filter
- type: icon-button
  props:
    icon: sort
    aria_label: Sort
- type: icon-button
  props:
    icon: refresh
    aria_label: Refresh
Edit YAML

props-inspector-shell

#existingsource

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

NameTypeDefaultDescription
identityobjectFocused element identity summary rendered at the top of the inspector.
sectionsarrayInspector section cards shown below the identity summary.
openbooleantrueWhether 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.
Edit YAML

rail-connection-state

#existingsource

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.

Props

NameTypeDefaultDescription
stateenum: connected | streaming | disconnected | error | reconnectingdisconnectedConnection state — selects dot colour + pulse animation.
pulsebooleantrueWhether active states animate. Forced off under `prefers-reduced-motion`; colour still distinguishes all 5 states.

Examples

Streaming state

Streaming session — CTA-colour pulsing dot beside an active label.

● Browser session 1 (streaming)
YAML
type: stack
props:
  direction: row
  gap: sm
children:
- type: text
  slots:
    default: ● Browser session 1
- type: text
  slots:
    default: (streaming)
Error / disconnected

Error vs disconnected — the two solid non-active states side by side.

● failed ● offline
YAML
type: stack
props:
  direction: row
  gap: md
children:
- type: text
  slots:
    default: ● failed
- type: text
  slots:
    default: ● offline
Edit YAML

rail-filter

#existingsource

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

NameTypeDefaultDescription
querystringConsumer-owned filter value. The primitive writes user keystrokes into this signal; the consumer derives matches from it.
placeholderstringFilter…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.

YAML
type: input
props:
  placeholder: Filter sessions…
  value: ''
Default filter

Generic rail filter with default placeholder.

YAML
type: input
props:
  placeholder: Filter…
  value: lib
Edit YAML

rail-item

#existingsource

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

NameTypeDefaultDescription
iconstringMaterial Symbols Outlined icon name. Required.
labelstringVisible label text. Required.
variantenum: surface | launchersurfaceVisual + semantic variant.
is_activebooleanfalseWhether this is the active surface (drives `aria-current` + the left-edge accent). Reactive in the impl; ignored for launchers.
connection_stateenum: connected | streaming | disconnected | error | reconnectingOptional 4px connection dot (top-right). Omit for plain surface entries. See `rail-connection-state` for the state→colour table.
closeablebooleanfalseRenders a hover-revealed close affordance (paired with an `on_close` handler). Session/tab rails set true; surface rails leave false.

Examples

Active surface item

Active surface entry — icon + label with the you-are-here accent.

YAML
type: item-button
props:
  icon: folder_open
  active: true
slots:
  default: Library
Launcher item

Launcher row — the persistent '+ New session' create affordance.

YAML
type: item-button
props:
  icon: add_circle_outline
slots:
  default: New session
Edit YAML

rail-keymap

#existingsource

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

NameTypeDefaultDescription
bindingsarrayThe `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.
prefixstringgThe 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 → Board g p → Properties g m → Members g 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 1 g 2 → Session 2 g 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)
Edit YAML

rail-overflow-sentinel

#existingsource

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

NameTypeDefaultDescription
overflow_countintegerNumber of items hidden beyond the visible fold. The chip is hidden (via `--hidden`) when this is 0. Required.
on_clickstringOptional scroll handler ref. Present → keyboard-reachable `<button>`; absent → focus-skipped informational `<span>`. Bound via the action registry.

Examples

Scrollable overflow chip

Scrollable overflow — keyboard-reachable '+5 more' button.

YAML
type: button
props:
  variant: ghost
  size: small
slots:
  default: +5 more
Informational overflow

Informational overflow — non-interactive count when no scroll action.

+12 more
YAML
type: text
slots:
  default: +12 more
Edit YAML

sheet

#existingsource

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

NameTypeDefaultDescription
openbooleanfalseWhether the Sheet is open. Reactive and caller-owned.
sideenum: left | right | top | bottomright
titlestringOptional sticky-header heading; also the dialog's accessible name.
on_closestringAction ref invoked on Escape / backdrop / close button.

Examples

Filter drawer

A right-anchored filter sheet, open.

YAML
type: sheet
props:
  open: true
  side: right
  title: Filters
slots:
  default:
  - type: text
    slots:
      default: Status, owner, and date-range controls go here.
Nav drawer

A left-anchored navigation drawer.

YAML
type: sheet
props:
  open: true
  side: left
  title: Navigate
slots:
  default:
  - type: text
    slots:
      default: Workspace · Library · Settings
Action sheet

A bottom action sheet (no title).

YAML
type: sheet
props:
  open: true
  side: bottom
slots:
  default:
  - type: text
    slots:
      default: Share · Duplicate · Delete
Edit YAML

stat-card

#existingsource

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.

Props

NameTypeDefaultDescription
labelstringSupporting metric label ("Total Users"). Required.
valuestringPre-formatted metric value ("1,234", "$48.2k"). Required.
changestringOptional signed change indicator (\"+12%\", \"-3%\"). The sign carries the meaning so colour is redundant reinforcement.
change_positivebooleantrueWhether the change renders green (true) or red (false). Pair with a signed `change` string so SR/colour-blind users still get direction.
iconstringOptional icon (HTML/SVG) rendered top-right of the tile.

Examples

KPI with positive trend

Positive-trend KPI — the canonical dashboard tile.

Total Users

1,234 +12% vs last week
YAML
type: card
props:
  style: default
  padding: default
  title: Total Users
slots:
  default:
  - type: text
    slots:
      default: 1,234
  - type: text
    slots:
      default: +12% vs last week
KPI with negative trend

Negative-trend KPI — same shape, red change.

Error rate

0.82% -3% vs last week
YAML
type: card
props:
  style: default
  padding: default
  title: Error rate
slots:
  default:
  - type: text
    slots:
      default: 0.82%
  - type: text
    slots:
      default: -3% vs last week
Metric row

Metric row — three comparable StatCards sharing rhythm.

Users

Revenue

Sessions

YAML
type: stack
props:
  direction: row
  gap: md
children:
- type: card
  props:
    title: Users
    style: default
- type: card
  props:
    title: Revenue
    style: default
- type: card
  props:
    title: Sessions
    style: default
Edit YAML

status-bar-slot

#existingsource

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

NameTypeDefaultDescription
classstringPer-pattern CSS class (e.g. `ces__status`) so each consumer scopes its own status-bar layout. Required.
childrenarrayAmbient 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 4 Rust 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.

● Connected Synced 1m ago
YAML
type: stack
props:
  direction: row
  gap: md
children:
- type: text
  slots:
    default: ● Connected
- type: text
  slots:
    default: Synced 1m ago
Edit YAML

tile

#existingsource

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

NameTypeDefaultDescription
interactivebooleanfalsePaint a hover/active lift affordance (does not make the tile focusable).
selectedbooleanfalseMark the tile as the current selection (accent ring + aria-current).
classstringExtra classes appended after the computed base/modifier classes.

Examples

Grid cells

A row of dense element tiles, one selected.

python
sql
vector
YAML
type: stack
props:
  gap: sm
  direction: row
children:
- type: tile
  props:
    interactive: true
  slots:
    default: python
- type: tile
  props:
    interactive: true
    selected: true
  slots:
    default: sql
- type: tile
  props:
    interactive: true
  slots:
    default: vector
Metric tile

A static (non-interactive) metric tile.

1,284 runs today
YAML
type: tile
slots:
  default: 1,284 runs today
States

Interactive vs selected vs plain, side by side.

plain
interactive
selected
YAML
type: stack
props:
  gap: sm
  direction: row
  align: center
children:
- type: tile
  slots:
    default: plain
- type: tile
  props:
    interactive: true
  slots:
    default: interactive
- type: tile
  props:
    interactive: true
    selected: true
  slots:
    default: selected
Edit YAML

toolbar

#existingsource

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

NameTypeDefaultDescription
alignenum: start | center | end | betweenstart
aria_labelstringAccessible name announced for the toolbar group.
classstringExtra classes appended after the computed base/modifier classes.

Examples

Split actions

An editor action bar with primary + secondary split.

YAML
type: toolbar
props:
  align: between
  aria_label: Document actions
children:
- type: button
  props:
    variant: ghost
  slots:
    default: Discard
- type: button
  props:
    variant: primary
  slots:
    default: Save
Centred

A centred formatting strip.

YAML
type: toolbar
props:
  align: center
  aria_label: Formatting
children:
- type: button
  props:
    variant: ghost
  slots:
    default: Bold
- type: button
  props:
    variant: ghost
  slots:
    default: Italic
- type: button
  props:
    variant: ghost
  slots:
    default: Link
Alignment

All alignment options for comparison.

YAML
type: stack
props:
  gap: sm
  direction: column
children:
- type: toolbar
  props:
    align: start
    aria_label: Start aligned
  children:
  - type: button
    props:
      variant: ghost
    slots:
      default: A
  - type: button
    props:
      variant: ghost
    slots:
      default: B
- type: toolbar
  props:
    align: end
    aria_label: End aligned
  children:
  - type: button
    props:
      variant: ghost
    slots:
      default: A
  - type: button
    props:
      variant: ghost
    slots:
      default: B
Edit YAML

well

#existingsource

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

NameTypeDefaultDescription
paddingenum: compact | cozy | spaciouscozy
classstringExtra 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
Edit YAML

workspace-body

#existingsource

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

NameTypeDefaultDescription
aria_labelstringWorkspace`main` landmark label. Set to the active rail-entry label so SR users hear in-place surface changes.
childrenarrayThe active surface content. Required; exactly one body per shell.

Examples

Body with panel content

Body hosting a panel surface — the common case.

Library

Active rail entry's surface renders here.
YAML
type: card
props:
  style: flat
  padding: default
  title: Library
slots:
  default:
  - type: text
    slots:
      default: Active rail entry's surface renders here.
Body with content page

Body hosting a simple content page (docs viewer).

Platform Documentation Long-form reference content fills the main region.
YAML
type: stack
props:
  direction: column
  gap: sm
children:
- type: text
  slots:
    default: Platform Documentation
- type: text
  slots:
    default: Long-form reference content fills the main region.
Edit YAML

workspace-breadcrumb

#existingsource

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

NameTypeDefaultDescription
segments*arrayOrdered breadcrumb labels from root to leaf.
on_segment_activatearrayOptional 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.

YAML
type: workspace-breadcrumb
props:
  segments:
  - Browser
  - chromeless
Workspace path

Workspace shell path with a short current leaf.

YAML
type: workspace-breadcrumb
props:
  segments:
  - Library
  - Tools
  - Browser
Edit YAML

workspace-rail

#existingsource

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

NameTypeDefaultDescription
aria_labelstringWorkspace context menuNavigation-landmark label. Override to describe the rail's contents ("Browser sessions", "Phone tabs") rather than the generic default.
childrenarrayRail 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.

YAML
type: stack
props:
  direction: column
  gap: sm
children:
- type: item-button
  props:
    icon: tab
  slots:
    default: Session 1
- type: item-button
  props:
    icon: tab
  slots:
    default: Session 2
- type: item-button
  props:
    icon: add
  slots:
    default: New session
Icon strip rail

Icon-only affordance strip — compact rail variant.

YAML
type: stack
props:
  direction: column
  gap: sm
children:
- type: icon-button
  props:
    icon: home
    aria_label: Home
- type: icon-button
  props:
    icon: folder
    aria_label: Files
- type: icon-button
  props:
    icon: settings
    aria_label: Settings
Edit YAML

workspace-shell

#existingsource

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

NameTypeDefaultDescription
aria_labelstringLabel announced on the `role=application` wrapper when focus enters the shell (e.g. "Library Home Screen"). Required.
childrenarrayThe 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
▌rail main 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.
Edit YAML

workspace-status

#existingsource

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

NameTypeDefaultDescription
childrenarrayAmbient 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.

● Connected Synced 2m ago 3 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
Edit YAML

workspace-top

#existingsource

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.

Props

NameTypeDefaultDescription
childrenarrayTop-bar content in left-to-right order: avatar/circle control, `<WorkspaceBreadcrumb>`, search input, right-side action cluster. Required.

Examples

Full top band

Standard top band — avatar, breadcrumb, search, actions.

● circle
YAML
type: stack
props:
  direction: row
  gap: md
children:
- type: text
  slots:
    default: ● circle
- type: breadcrumb
  props:
    segments:
    - Workspace
    - Library
- type: input
  props:
    placeholder: Search…
- type: icon-button
  props:
    icon: notifications
    aria_label: Notifications
Minimal top band

Minimal top band — breadcrumb + single action only.

YAML
type: stack
props:
  direction: row
  gap: md
children:
- type: breadcrumb
  props:
    segments:
    - Docs
    - Elements
- type: icon-button
  props:
    icon: search
    aria_label: Search
Edit YAML