A wide, tone-coloured strip pinned to the top of a page or region that communicates a durable, page-level condition: a system incident, a maintenance window, a degraded-mode notice, or a successful bulk action. Banner persists until the user dismisses it or the condition resolves. Error and danger tones announce assertively (role=alert); calmer tones announce politely (role=status). For a message that lives beside the content it qualifies use Callout; for transient action feedback use Toast.
Readiness
complete
Preview
live
Props
3
Examples
4
Code
implementation mapped
When to use
System-wide incident or degraded-mode notice ('Search is running slow')
Scheduled-maintenance announcement at the top of the workspace
Confirmation of a page-level bulk action ('14 records imported')
Account / billing state that gates the whole surface ('Trial ends in 3 days')
When not to use
Transient feedback after a single action — use Toast
A note attached to specific content in document flow — use Callout
A validation error bound to one form field — use the field's error slot
A blocking decision that needs a response — use a Dialog
Accessibility
Role: alert
No keyboard shortcuts
Screen reader: Error and danger tones use role=alert so assistive tech interrupts and announces immediately; info / success / warning use role=status (announced politely at the next pause). The leading icon is decorative (aria-hidden) — the tone meaning lives in the message text. The dismiss control is a real button with an explicit aria-label.
Notes: Never rely on tone colour alone. Phrase the message so it carries the severity in words ('Error: upload failed' rather than a red strip saying 'upload failed').
Props
Name
Type
Default
Description
tone
enum: info | success | warning | error | danger
info
icon
string
—
Optional decorative leading icon key (e.g. info, warning, error).
dismissible
boolean
false
Render a dismiss control. Dismissal is local component state.
Examples
Incident
A degraded-mode incident notice at the top of the workspace.
Search is running slower than usual while we recover an index. Results may be incomplete.
YAML
type: banner
props:
tone: warning
icon: warning
dismissible: true
slots:
default: Search is running slower than usual while we recover an index. Results may be incomplete.
Inline contextual guidance inside document flow. Callout is for durable explanatory notes, warnings, and implementation gotchas that belong beside the content they qualify — it does not interrupt the reading position the way a viewport-pinned Banner does. It uses role=note so assistive tech treats it as supplementary rather than an interruption. For a page-level announcement use Banner; for transient feedback after an action use Toast.
Readiness
complete
Preview
live
Props
3
Examples
4
Code
implementation mapped
When to use
Notes or warnings inside generated docs and guides
Contextual implementation guidance that should not interrupt the workflow
Markdown blockquote-style asides that should render as first-class UI
A 'gotcha' beside a configuration field that needs explaining
When not to use
Page-level incidents or system announcements - use Banner
Transient feedback after an action - use Toast
Validation errors bound to a form field - use the field's error slot
A blocking decision that needs a response - use a Dialog
Accessibility
Role: note
No keyboard shortcuts
Screen reader: Rendered as an <aside role=note> so assistive tech announces it as a supplementary region in natural DOM order. The optional title is a real lead-in read before the body. The leading icon is decorative (aria-hidden).
Notes: Do not rely on tone colour alone. Include plain text such as Note, Warning, or Error (often as the title) when the semantic state matters.
Props
Name
Type
Default
Description
tone
enum: note | info | success | warning | error
note
title
string
—
Optional bold lead-in title.
icon
string
—
Optional decorative leading icon key (e.g. info, warning).
Examples
Warning note
A documentation gotcha beside the prose it qualifies.
YAML
type: callout
props:
tone: warning
title: Heads up
icon: warning
slots:
default: Regenerating overwrites every file under generated/. Commit the YAML edit and the regenerated output together.
Info tip
An informational tip inside a guide.
YAML
type: callout
props:
tone: info
title: Tip
icon: info
slots:
default: Element behaviour flows from chemistry YAML through generated code — never hardcode element kinds in view code.
Plain note
A plain neutral aside with no title.
YAML
type: callout
props:
tone: note
slots:
default: This view is read-only for guest sessions.
Tones
All tones for visual comparison.
YAML
type: stack
props:
gap: sm
direction: column
children:
- type: callout
props:
tone: note
title: Note
slots:
default: Neutral aside.
- type: callout
props:
tone: info
title: Info
slots:
default: Informational tip.
- type: callout
props:
tone: success
title: Done
slots:
default: Positive guidance.
- type: callout
props:
tone: warning
title: Caution
slots:
default: Watch out for this.
- type: callout
props:
tone: error
title: Error
slots:
default: Do not do this.
An empty-container affordance combining icon + title + body + optional CTA. EmptyState exists so "this list is empty" never reads as a dead-end — there is always a next action available (create something, invite someone, change the filter). For panel-bound empty states use PanelEmptyState (sibling, panel-density-aware).
Readiness
complete
Preview
live
Props
3
Examples
2
Code
implementation mapped
When to use
Empty list / table / search-result panel
First-run state for a feature ('No agents yet')
Filtered view with zero matches ('No agents match your filters')
Permission-gated views ('Sign in to see your projects')
When not to use
Loading state — use LoadingSpinner / LoadingSkeleton
Error state — use Banner / PanelErrorBanner
Inside a panel where panel-density-aware variant is needed — use PanelEmptyState
Accessibility
Role: status
No keyboard shortcuts
Screen reader: Reads heading + body via natural DOM order. CTA is announced as a button with its label. Keep heading concise — SR users hear it first and parse the rest in order.
Notes: The icon is decorative — set aria-hidden=true so SR users don't hear it announced. The textual content carries the meaning.
Props
Name
Type
Default
Description
title
string
—
Short heading explaining the empty state.
body
string
—
Longer prose describing what to do next.
icon
string
—
Icon name (decorative — aria-hidden).
Examples
Empty agents
Empty agent list with create CTA.
No agents yet
Create an agent to start automating tasks for this circle.
YAML
type: empty-state
props:
title: No agents yet
body: Create an agent to start automating tasks for this circle.
icon: agents
slots:
cta:
- type: button
props:
variant: primary
on_click: ${actions.create_agent}
slots:
default: Create agent
No matches
Filtered list with no matches and a clear-filter CTA.
A content-shaped placeholder that shimmers while the real content is loading. Use Skeleton (not Spinner) when the layout would otherwise jump as content lands, or when the user benefits from knowing the shape of what's coming. For specific common shapes, use the dedicated variants: CardSkeleton, ListSkeleton, TableRowSkeleton.
Readiness
complete
Preview
live
Props
3
Examples
2
Code
implementation mapped
When to use
Above-the-fold content where layout shift is jarring
Repeating list / card layouts (use specific variants)
Long-running fetches where shape signals to the user what to expect
Pre-rendering placeholders for SSR / hydration windows
When not to use
Brief loads (<200ms) — show nothing
Action affordances (button click, form submit) — use Spinner
Errors — use Banner / EmptyState
Content with unknown shape — Spinner is better than a misleading skeleton
Accessibility
Role: status
No keyboard shortcuts
Screen reader: Skeleton elements should declare aria-busy=true on their containing region so SR users know content is loading. The shimmer animation itself is not announced.
Notes: Don't use Skeleton for content that may never load (e.g. permission- gated). Use EmptyState instead.
Props
Name
Type
Default
Description
width
string
—
CSS width (e.g. '100%', '120px').
height
string
—
CSS height (e.g. '1rem', '24px').
rounded
boolean
false
Whether to round corners (for avatar / pill placeholders).
The non-visual coordination layer for transient notifications: a single bus that `<Toast>`, ambient-notification (`unified_bar/ambient_renderer`), and optimistic-error-toast (`optimistic/components`) should all publish into and render from, instead of three parallel systems. It owns the notification model — kind (info/success/warning/error), tier, optional celebration/confirm payloads, and actions — while visual treatment lives in the renderers. It lives in `portal/src/canvas/notification_bus.rs` and is catalogued so future visual work routes through it.
Readiness
complete
Preview
live
Props
3
Examples
2
Code
implementation mapped
Defects
When to use
Emitting any transient notification — publish to the bus, let a renderer (Toast / ambient) display it.
Building a new notification surface — subscribe to the bus, do NOT invent a parallel queue.
Coordinating dedupe / tiering across notification sources.
When not to use
Rendering — this is coordination only; visual treatment is `<Toast>` / ambient renderers.
Persistent state or audit logs — the bus is for transient notifications, not durable records.
A one-off in-component message with no cross-system concern — a local signal is simpler.
Accessibility
Role: presentation
No keyboard shortcuts
Screen reader: Not rendered — no direct AT surface. Its accessibility value is indirect: by funnelling all transient notifications through one bus, the renderers can own exactly one well-behaved `aria-live` region instead of three competing ones talking over each other.
Notes: Coordination layer — visual + aria-live treatment lives in `<Toast>` / `<ToastStack>` / ambient renderers. Catalog records the shared contract for `portal::canvas::notification_bus::NotificationBus`.
Props
Name
Type
Default
Description
kind
enum: info | success | warning | error
—
Notification semantic kind. Renderers map this to tone/icon/ aria-live politeness. Required on publish.
tier
enum: ambient | toast | celebration | confirm
—
Which renderer/treatment the notification routes to — ambient bar, transient toast, celebration, or a confirm prompt.
actions
array
—
Optional action descriptors carried with the notification so the renderer can offer buttons (Retry, Undo, View).
Examples
Publish: success → toast
Publishing a success notification routed to the toast tier (data shape, not a render).
A linear progress indicator for a single quantifiable task: an upload, an import, a quota fill, a multi-file operation. In determinate mode the fill width tracks value/max and the rounded percentage is announced via aria-valuenow. In indeterminate mode the bar shows a travelling segment and drops aria-valuenow so assistive tech says "busy" instead of a misleading number. For multi-step flow position use ProgressSteps; for a tiny liveness marker use StatusDot.
Readiness
complete
Preview
live
Props
6
Examples
4
Code
implementation mapped
When to use
Upload / download / import progress with a known total
A quota or usage meter (value out of max)
A long operation whose completion is unknown (indeterminate mode)
Batch operation progress ('312 of 500 processed')
When not to use
Discrete steps in a wizard — use ProgressSteps
A tiny on/off liveness marker — use StatusDot
A radial / dial visual — use Gauge or DonutChart
An indeterminate spinner with no bar semantics — use LoadingSpinner
Accessibility
Role: progressbar
No keyboard shortcuts
Screen reader: The track is role=progressbar with aria-valuemin=0 and aria-valuemax=max. In determinate mode aria-valuenow carries the clamped current value; in indeterminate mode aria-valuenow is omitted so assistive tech announces a busy state rather than a false percentage. The visible label also backs the accessible name.
Notes: Always pass a label so the bar has an accessible name. The numeric percentage is rendered as text (show_value) so it is not colour- or width-only.
Props
Name
Type
Default
Description
value
number
0
Current value, clamped to 0..=max. Ignored when indeterminate.
max
number
100
Upper bound. Non-positive values fall back to 100.
A horizontal indicator of position within a discrete, ordered flow: an onboarding wizard, a multi-page form, a setup checklist. Steps before the current one render complete (check marker); the current step is emphasised; later steps are upcoming. State is conveyed by marker glyph plus colour plus visually-hidden text, never colour alone. For a continuous quantity use ProgressBar; for a single liveness marker use StatusDot.
Readiness
complete
Preview
live
Props
2
Examples
3
Code
implementation mapped
When to use
An onboarding or setup wizard ('Account → Profile → Confirm')
A multi-page form where the user should see how far they are
A checkout / submission flow with discrete stages
Any bounded sequence where step position matters
When not to use
Continuous / percentage progress — use ProgressBar
A single liveness marker — use StatusDot
Free navigation between unrelated sections — use Tabs
An unbounded activity feed — use a Timeline
Accessibility
Role: navigation
No keyboard shortcuts
Screen reader: Wrapped in <nav aria-label="Progress"> with an ordered list. The in-progress step carries aria-current="step". Each step appends visually-hidden text ('(complete)' / '(current)' / '(upcoming)') and a 'Step N of M' summary so the position is conveyed without relying on the marker colour. Markers are aria-hidden decoration.
Notes: State is conveyed by glyph (check vs number), colour, and screen-reader text together — never colour alone.
Props
Name
Type
Default
Description
steps
array
—
Ordered step labels.
current
integer
0
Zero-based index of the in-progress step. Earlier steps are complete; later steps are upcoming.
Examples
Onboarding
A three-step onboarding wizard on the middle step.
Visible spinner plus a polite live region for the status text. Always pair with a label slot — even decorative spinners benefit from a label hidden to sighted users but read by SR. Use `inline` when the spinner sits next to text in a row, `block` when it owns its own line.
Readiness
complete
Preview
live
Props
2
Examples
3
Code
implementation mapped
When to use
Known operation in progress (>250ms wait)
User-initiated action awaiting server response
Inline next to a button label (`layout: inline`)
Block-level loading screen for a section (`layout: block`)
Background refresh of visible data (with subtle inline placement)
When not to use
Skeleton-shaped placeholders for content layout — use LoadingSkeleton
Brief flashes (<100ms) — no indicator at all
Page navigation — use a route progress bar
Indeterminate operations >10s — switch to a progress bar with cancel
Accessibility
Role: status
No keyboard shortcuts
Screen reader: `role=status` + `aria-live=polite` ensures SR announces the label text when it changes (e.g. "Verifying molecular signature..." → "Almost there..."). Decorative spinners SHOULD still have a label hidden via visually-hidden CSS so SR users get an announcement.
Notes: The label is the load-bearing accessibility input. A spinner without a label is invisible to SR users entirely.
Props
Name
Type
Default
Description
size
enum: small | medium | large
medium
layout
enum: inline | block
block
`inline` keeps the spinner on the baseline of its row (label sits next to it). `block` centres the spinner above its label.
Examples
Block spinner with status
Login overlay loading state with announced status text.
YAML
type: spinner
props:
size: medium
layout: block
slots:
label:
- type: text
props:
kind: muted
slots:
default: ${state.loading_phase}
Inline spinner
Inline spinner in a button's loading state (handled by button itself, here for reference).
YAML
type: spinner
props:
size: small
layout: inline
Sizes gallery
All sizes side-by-side.
small
medium
large
YAML
type: stack
props:
gap: lg
direction: row
align: center
children:
- type: spinner
props:
size: small
layout: block
slots:
label:
- type: text
slots:
default: small
- type: spinner
props:
size: medium
layout: block
slots:
label:
- type: text
slots:
default: medium
- type: spinner
props:
size: large
layout: block
slots:
label:
- type: text
slots:
default: large
A small coloured dot communicating connection / liveness state at a glance: online, offline, error, idle, busy, or unknown. Colour is never the sole signal — the component always supplies an accessible name from the tone, and an optional visible label is encouraged. The pulse option draws attention to an active state and is suppressed under reduced motion. For a labelled status use Badge; for numeric progress use ProgressBar.
Readiness
complete
Preview
live
Props
3
Examples
4
Code
implementation mapped
When to use
Agent / service liveness next to a name ('● Online')
Connection state in a header or rail item
Row-level health indicator in a dense list
A pulsing 'live' marker on an actively streaming resource
When not to use
A textual status label or count — use Badge
Linear completion progress — use ProgressBar
Multi-step flow position — use ProgressSteps
An interactive toggle — use Switch
Accessibility
Role: status
No keyboard shortcuts
Screen reader: The wrapper is role=status with an aria-label drawn from the tone (e.g. 'Online') when no visible label is given, so the state is announced even though the dot itself is aria-hidden decoration. A supplied visible label backs the accessible name instead.
Notes: Pair with a text label — colour alone fails for colourblind users. The default per-tone label exists precisely so a dot-only usage is still announced.
A floating, auto-dismissing transient notification. Call sites push via `ToastContext` — `push(msg, level)`, `push_icon_only(level)`, `push_with_action(msg, level, …)`, `dismiss(id)` — and the toast renders + auto-expires in the `<ToastStack>` mounted at app root. `level` is the shared tone enum (info / success / warning / error).
Readiness
complete
Preview
live
Props
4
Examples
2
Code
implementation mapped
When to use
Confirming a completed action that doesn't need acknowledgement — "Changes saved".
Non-blocking warnings/errors the user should notice but not be interrupted by.
Icon-only success pings for high-frequency low-importance actions (`push_icon_only`).
When not to use
Errors that must be acted on before continuing — use a `<PanelErrorBanner>` or modal.
Persistent status — use `<WorkspaceStatus>` / `<PanelFooter>` status slot.
A new parallel notification path — route through `<NotificationBus>`; don't add a 4th system.
Accessibility
Role: status
Escape — Dismiss the focused/last toast.
Screen reader: `aria-live=polite` for info/success/warning so the toast is announced without interrupting; `aria-live=assertive` for errors. Auto-dismiss timing must be long enough for SR users to hear it — pair critical content with `push_with_action` so it isn't lost to a timeout.
Notes: `portal::components::toast::Toast` provides the presentational row; `ToastContext` remains the app-facing push API.
Props
Name
Type
Default
Description
message
string
—
Toast body text. Required (except `push_icon_only`).
level
enum: info | success | warning | error
info
Shared tone enum (`ToastLevel`). Drives colour + aria-live politeness.
icon_only
boolean
false
Render just the level icon (high-frequency low-importance pings).
action
string
—
Optional action label (`push_with_action`) — renders a button so the toast survives long enough to be acted on.
Examples
Success toast
Success confirmation — the canonical 'Changes saved' ping.
The singleton container that makes `<Toast>` work: mounted once at the app root, it subscribes to the toast bus, renders queued toasts with stacking rules and exit transitions, and owns the `aria-live` region toasts announce into. Call sites never render it directly — they `push` via `ToastContext` and ToastStack does the rest.
Readiness
complete
Preview
live
Props
2
Examples
2
Code
implementation mapped
Defects
When to use
Exactly once, at the application root, so every surface's toasts have a renderer.
As the single mount point — never per-panel; multiple stacks fight over the same queue.
When wiring a new app shell that needs toast support.
When not to use
Per-surface or per-panel — the stack is a singleton by contract.
To push a toast — use `ToastContext::push`, not a direct render.
As a general overlay container — it is toast-queue-specific.
Accessibility
Role: region
No keyboard shortcuts
Screen reader: Owns the `aria-live` region; individual toasts set polite/assertive per level. As a singleton it ensures there is exactly one live region for transient notifications — multiple stacks would create competing live regions that talk over each other.
Notes: `portal::components::toast::ToastStack` is the singleton mount point for ambient toast rendering.
Inline cost badge — surfaces operation cost before the user triggers it (three trust tiers).
Readiness
incomplete
Preview
static
Props
3
Examples
0
Code
implementation unmapped
Props
Name
Type
Default
Description
cost_microau
string
—
(optional<uint>) Estimated cost in micro-AU. Absent (None) = the estimate is still loading; the chip renders the "…" ellipsis label and stays Hidden. `uint` (u64): micro-AU is a non-negative quantity, matching the body.rs signature — declaring `int` would fail the body-registry typecheck.
balance_microau*
string
—
(uint) Current wallet balance in micro-AU. A zero balance with a non-zero cost forces the Insistent tier regardless of cost magnitude.
hint
string
—
(optional<string>) Optional tooltip extra, appended to the title as ' · {hint}'.