# Components reference

> All 44 semantic components: props, when to use them, accessibility, rendering rules and platform mappings.

Source: https://polyxd.com/docs/reference/components/

Generated from `packages/spec/components/*.json`, the same source the schema, validator and renderer use. Every component also accepts `id`, `component`, `key`, `accessibility` and `visible`. For how these map onto the components of 13 design systems, see [design-system coverage](https://polyxd.com/docs/reference/coverage/).

The five **shell** components (`Frame`, `AppBar`, `Footer`, `Outlet`, `Custom`) are the product's frame around its screens. They belong only in a shell document (`surface.kind` `"shell"`, `surface.origin` `"authored"`), written once per product; the validator refuses them anywhere else, and a generator is never shown them. See [Generated or authored](https://polyxd.com/docs/authored-screens/#the-shell).

| Component | Web | iOS | Android |
| --- | --- | --- | --- |
| [FilterPanel](#filterpanel) | Sidebar + Sheet (shadcn) | List with a filter sheet | Side sheet / modal bottom sheet |
| [Navigation](#navigation) | Sidebar navigation (shadcn Sidebar) | TabView or a sidebar on iPad | NavigationRail / NavigationDrawer |
| [AppBar](#appbar) | Polaris TopBar, Carbon Header, Fluent Nav header, shadcn sidebar header | Navigation bar with toolbar items | TopAppBar |
| [Custom](#custom) | the host's own component | the host's own view | the host's own composable |
| [Footer](#footer) | GOV.UK footer, Polaris FooterHelp, Bootstrap footer | n/a (settings screen) | n/a |
| [Frame](#frame) | Polaris Frame, Carbon UI Shell, Fluent App layout, shadcn Sidebar layout | NavigationSplitView / TabView with a NavigationStack | Scaffold with TopAppBar, NavigationRail or NavigationBar, and content |
| [Outlet](#outlet) | the router's outlet | NavigationStack content | NavHost |
| [Card](#card) | shadcn Card (CardHeader/CardTitle/CardContent/CardFooter) | GroupBox, or a Button/NavigationLink with card styling in lists | Card / ElevatedCard / OutlinedCard (onClick variant when actionable) |
| [Columns](#columns) | GOV.UK grid row, Polaris Layout, Carbon Grid, Bootstrap row/col | HStack with .layoutPriority | Row with weights |
| [Disclosure](#disclosure) | shadcn Collapsible (or Accordion for several) | DisclosureGroup | Expandable ListItem / AnimatedVisibility with a toggle row |
| [Group](#group) | <div role=group> with flex/grid and semantic spacing tokens | VStack / HStack / Grid (ViewThatFits for inline) | Column / Row / FlowRow |
| [Panel](#panel) | shadcn Dialog, Sheet, Drawer, Popover | .sheet, .popover, NavigationStack push | ModalBottomSheet, AlertDialog, DropdownMenu |
| [Section](#section) | <section> + heading; shadcn has no Section primitive (plain markup with semantic tokens) | Section(header:) inside List/Form, or VStack with .accessibilityAddTraits(.isHeader) on the title | Column with a Text heading marked Modifier.semantics { heading() } |
| [Split](#split) | Fluent SplitView, Carbon side panel, shadcn Resizable | NavigationSplitView | ListDetailPaneScaffold |
| [Views](#views) | shadcn Tabs | Picker(.segmented) switching content, or TabView for top-level | PrimaryTabRow + content |
| [Chart](#chart) | shadcn Chart (Recharts) | Swift Charts (Chart) | Third-party (e.g. Vico) or Canvas; no built-in M3 chart |
| [Code](#code) | pre > code with a Copy button | Text with monospaced design in a rounded rectangle | Text with FontFamily.Monospace in a Surface |
| [Collection](#collection) | List markup with Card or row template; ScrollArea for long lists | List + ForEach | LazyColumn + items |
| [DetailList](#detaillist) | <dl> with semantic tokens | LabeledContent rows in a Form/List section | ListItem(headlineContent, trailingContent) rows |
| [Identity](#identity) | shadcn Avatar | AsyncImage in a Circle + Text | AsyncImage with CircleShape + Text |
| [Media](#media) | <img> / <video> / <audio> / shadcn AspectRatio; QR on <canvas> | AsyncImage / VideoPlayer / CoreImage CIQRCodeGenerator | AsyncImage (Coil) / Media3 PlayerView / ZXing |
| [Metric](#metric) | Card-like block with semantic tokens (no dedicated shadcn primitive) | LabeledContent or a VStack; Gauge for bounded values | Column with Text styles |
| [Progress](#progress) | shadcn Progress; meter as a native <meter> | ProgressView, Gauge | LinearProgressIndicator, CircularProgressIndicator |
| [Table](#table) | shadcn Table (optionally Data Table with TanStack) | Table on regular width; List of rows on compact width | LazyColumn of rows with a header row (no built-in M3 table) |
| [Tag](#tag) | shadcn Badge | Text with capsule background | AssistChip / Badge |
| [Text](#text) | <p> with type tokens | Text | Text |
| [Tree](#tree) | Radix Accordion primitives (no shadcn tree) | OutlineGroup / List with children | LazyColumn with expandable rows |
| [Status](#status) | shadcn Alert / Skeleton / Sonner toast | ContentUnavailableView / ProgressView / inline Label | Snackbar / Card with status color / CircularProgressIndicator |
| [Choice](#choice) | shadcn ToggleGroup / RadioGroup / Select / Combobox (Command + Popover) / Checkbox | Picker (.segmented / .inline / .menu) or multi-select List | SegmentedButton / RadioButton rows / ExposedDropdownMenuBox / Checkbox rows |
| [CodeInput](#codeinput) | shadcn InputOTP | TextField with .oneTimeCode content type | BasicTextField with KeyboardType.NumberPassword |
| [ColorInput](#colorinput) | input type=color + Radix RadioGroup for swatches | ColorPicker | Custom (no Material picker) |
| [DateInput](#dateinput) | shadcn Calendar + Popover (Date Picker) | DatePicker | DatePicker / DateRangePicker |
| [FileInput](#fileinput) | shadcn Input type=file with a drop zone | fileImporter / PhotosPicker | ActivityResultContracts.GetContent |
| [Form](#form) | shadcn Form (react-hook-form + zod) | Form | Column of fields + Button (no Form primitive) |
| [RangeInput](#rangeinput) | shadcn Slider | Slider | Slider |
| [Rating](#rating) | Radix RadioGroup (no shadcn rating) | HStack of Image(systemName: star) buttons | Row of IconButton |
| [TextInput](#textinput) | shadcn Input / Textarea with Label and FormMessage | TextField / TextEditor with .keyboardType and .textContentType | OutlinedTextField with KeyboardOptions |
| [Toggle](#toggle) | shadcn Switch / Checkbox | Toggle | Switch / Checkbox |
| [Action](#action) | shadcn Button (default / secondary / ghost / destructive) | Button with .borderedProminent / .bordered / .borderless; role: .destructive | Button / FilledTonalButton / OutlinedButton / TextButton |
| [ActionBar](#actionbar) | Flex row of shadcn Buttons (DialogFooter-style) | .toolbar or .safeAreaInset(edge: .bottom) | BottomAppBar or Row of Buttons |
| [ActionMenu](#actionmenu) | shadcn DropdownMenu, ContextMenu; split as a ButtonGroup | Menu, contextMenu | DropdownMenu, combinedClickable for context |
| [Comparison](#comparison) | shadcn Table or Cards grid | Grid / Table on regular width; List of GroupBox on compact | LazyRow of Cards or Column of Cards |
| [Confirm](#confirm) | shadcn AlertDialog | .confirmationDialog / .alert with role: .destructive | AlertDialog |
| [Steps](#steps) | Progress + step content (no shadcn stepper primitive) | NavigationStack pushes or a paged view with a ProgressView | Custom stepper Row + content; LinearProgressIndicator |

## FilterPanellayout

Filters for a list of results, with the result count.

| Prop | Type | Notes |
| --- | --- | --- |
| `label` | text or binding | Defaults to 'Filters' |
| `children` required | component ids | The filter inputs (Choice, RangeInput, Toggle, DateInput, TextInput) |
| `results` required | component id | The Collection or Table the filters narrow |
| `resultCount` | binding | Number of results with the current filters, from host data |
| `clear` | action | Optional action that resets the filters on the host |

### Use when

- Browsing results people narrow by several attributes
- More than two filters, or filters that take space (ranges, long lists)

### Don't use when

- One or two quick filters: put a Choice (chips) above the results
- Filling in information: use Form

### Accessibility region (filters) plus the results

- The result count is announced politely when it changes
- Each active filter can be removed with a named button ('Remove filter: Desk')

**Agents:** Set filter inputs by label; read the result count; remove filters by their Remove buttons.

### Rendering rules

- Wide: filters in a sidebar beside the results
- Compact: a 'Filters · n' button opens a bottom sheet whose action says 'Show n results'
- Active filters show as removable chips above the results
- Filters apply as they change (no separate Apply on wide)

**A2UI:** Column

## Navigationlayout

The product's main navigation.

| Prop | Type | Notes |
| --- | --- | --- |
| `label` | text or binding | Names the navigation, e.g. 'Main' |
| `items` required | list of objects |  |
| `current` required | binding | Key of the current item in host data |
| `kind` | "main" \| "breadcrumb" \| "nested" \| "toc" \| "local" | 'main' is the product's sections; 'breadcrumb' the ordered trail to here; 'nested' groups that expand; 'toc' the sections of this page (items link to anchors named by their keys); 'local' the sections of one area, as tabs |
| `placement` | "auto" \| "side" \| "rail" \| "bar" \| "drawer" | For kind 'main' inside a Frame: where it lives. 'auto' is a side column on wide layouts, a rail on medium and a bottom bar (≤ 5 items) or a drawer on compact; the others fix it |

### Use when

- Software with sections people move between (B2B apps, dashboards)
- Only when the host doesn't already provide navigation

### Don't use when

- Steps of one task: use Steps
- Sections of one record: use Views
- A surface embedded in a host that has its own navigation

### Accessibility navigation

- The current item carries aria-current="page"
- Badges say what they count ('12 overdue')

**Agents:** Move between sections by activating items by their labels.

### Rendering rules

- Wide: a side navigation with grouped items
- Compact: a menu button that opens the navigation
- Badges sit at the end of their item
- 'breadcrumb' is an ordered trail ending with the current item (aria-current="page"), never behind a menu button
- 'nested' shows each group as an expandable section; the group holding the current item starts open
- 'toc' lists the page's sections as anchors, the section in view highlighted
- 'local' lays the items out as tabs, the current one marked
- 'placement' fixes the layout a Frame gives the main navigation: side (240px column), rail (80px, icons with labels), bar (bottom, up to 5 items), drawer (behind the AppBar's menu button)

**A2UI:** Column

## AppBarshell

The bar at the top of the product: brand or title, a leading action, search, trailing actions and the account.

| Prop | Type | Notes |
| --- | --- | --- |
| `title` required | text or binding | The product's name, or the current screen's title on compact layouts |
| `brand` | text or binding | A media reference for the logo, or initials; rendered before the title |
| `leading` | component id | An Action at the start: back, or the menu button the renderer adds itself when the navigation is a drawer |
| `search` | component id | A TextInput of kind 'search' (or a command palette opener: an Action with a shortcut) |
| `actions` | component id | An ActionBar at the end: notifications, help, the primary action of the product |
| `account` | component id | An Identity (the signed-in person) or an ActionMenu opened from it |
| `variant` | "standard" \| "large" \| "compact" | 'large' puts the title under the bar on its own line on wide screens; 'compact' is 48px for dense tools |
| `sticky` | boolean |  |

### Use when

- The header region of a Frame
- A product that needs a persistent place for search, notifications and the account

### Don't use when

- Inside a generated surface: the frame owns the header
- A page's own title and actions: that is the surface header (surface.title, surface.actions)

### Accessibility banner

- The title is the product's name, not a heading; the screen's h1 stays in main
- The menu button (when present) names the navigation it opens and reports expanded state
- Search is a labelled search landmark when present
- Trailing icon actions have visible tooltips and accessible names

**Agents:** Finds the product's search and account here; opens the navigation on compact layouts through the menu button.

### Rendering rules

- Height 64px (standard), 48px (compact); large variant adds a 56px title row on wide screens
- Background color.surface.default with a bottom border; elevation.raised once content scrolls under it
- Leading action, brand and title at the start; search grows in the middle on wide layouts and becomes an icon on compact; actions and account at the end
- On compact layouts the title shows the current screen's title when the Frame provides one

**A2UI:** custom

## Customshell

A slot for a component the host implements itself: a logo, a brand moment, a bespoke widget. The document names it and gives it props; the product renders it, and a fallback stands in when it can't.

| Prop | Type | Notes |
| --- | --- | --- |
| `name` required | string | The host's name for the component, namespaced: 'brand.logo', 'store.map' |
| `props` | object | What the host component takes; values may be bindings |
| `fallback` required | component id | What renders when the host has no component of that name: a Text, a Media, a Status. Required, so the document stands on its own |
| `label` | text or binding | An accessible name for the slot when the host component is decorative or complex |

### Use when

- The parts of a product that are truly its own: a logo, an illustration system, a map, a chart type the spec doesn't have
- Only in authored documents: a shell or a screen a person wrote for that product

### Don't use when

- Anything a spec component can express: a Custom is invisible to the verifier beyond its fallback
- In a generated document: a generator can't know a host's components (the validator refuses it)

### Accessibility whatever the host component provides; the fallback carries its own

- The host component is responsible for its own accessibility; the verifier audits the rendered result
- A decorative Custom is hidden from assistive tech; a meaningful one has a name

**Agents:** Reads the host component if it exposes a role and name; otherwise reads the fallback.

### Rendering rules

- The renderer looks the name up in the host's registry (the same one that overrides components); found, it renders it with the resolved props
- Not found, it renders the fallback and notes the missing name in development
- Never renders the raw props

**A2UI:** custom

## Footershell

The bar at the bottom of the product: link groups, the legal line, locale or status.

| Prop | Type | Notes |
| --- | --- | --- |
| `groups` | list of objects | Columns of links, each under a heading |
| `legal` required | text or binding | The line at the very bottom: copyright, licence, company details |
| `aside` | component id | Something at the end of the legal row: a locale Choice, a status Tag, a Text |

### Use when

- The footer region of a Frame
- Public services and marketing-adjacent products where legal and help links belong at the bottom

### Don't use when

- Dense tools where nothing lives below the fold: leave the Frame's footer out
- Inside a generated surface

### Accessibility contentinfo

- Groups are navigation regions named by their headings
- Links are links, not buttons, when they leave the current screen
- The legal line is readable text, not an image

**Agents:** Reads the legal line and reaches help, privacy and terms by name.

### Rendering rules

- Groups as columns on wide layouts, stacked on compact; headings in type.label.default
- Background color.surface.subtle with a top border; the legal row in type.body.small, color.text.muted
- Never sticky

**A2UI:** custom

## Frameshell

The product's frame: the regions around its screens (header, navigation, main, aside, footer, banner), with an outlet where screens and generated surfaces appear.

| Prop | Type | Notes |
| --- | --- | --- |
| `header` | component id | An AppBar |
| `navigation` | component id | A Navigation of kind 'main'; its placement decides rail, bar, drawer or side |
| `main` required | component id | The outlet: a component the host replaces with the current screen. Usually an Outlet; may be a Group holding a fixed screen |
| `aside` | component id | A secondary column beside main on wide layouts (help, context, activity) |
| `footer` | component id | A Footer |
| `banner` | component id | A Status shown above everything (a phase banner, an outage, a trial ending) |
| `skipTarget` | boolean | The renderer adds a skip link to main |
| `width` | "full" \| "contained" | 'contained' centres main at measure.max × 1.6; 'full' lets dense tools use the whole viewport |

### Use when

- The root of a shell document: the product's frame, authored once, that every screen sits in
- A product whose navigation, header and footer should render in its design system like everything else

### Don't use when

- In a generated document: a surface lives inside a frame and never draws one (the validator refuses it)
- A dialog or a sheet over a screen: use Panel

### Accessibility landmarks: banner (header), navigation, main, complementary (aside), contentinfo (footer)

- Exactly one main; a skip link to it is the first focusable thing
- Each landmark is labelled when there is more than one of its kind
- Regions keep their reading order at every width: banner, header, navigation, main, aside, footer
- The navigation's current item is marked; the document title follows the current screen

**Agents:** Reads the landmarks to know where it is; moves between screens through the navigation by name; acts on what is in main.

### Rendering rules

- Wide (≥ 1024px): header on top, navigation at the start edge as a side column or a rail, main and aside as columns, footer below
- Medium (≥ 640px): navigation becomes a rail; aside stacks below main
- Compact: navigation becomes a bottom bar (up to 5 items) or a drawer behind a menu button in the header; the footer's groups collapse
- Region backgrounds use color.surface.default for main and color.surface.subtle for navigation; borders are color.border.default at border.width.default
- The outlet is where PolyxdSurface documents render: they never repeat the frame's regions

**A2UI:** custom

## Outletshell

Where the current screen renders inside a Frame: the host fills it with a screen document or a generated surface.

| Prop | Type | Notes |
| --- | --- | --- |
| `label` | text or binding | Names the region for assistive tech when the screen has no title yet |
| `loading` | component id | What shows while a screen is on its way; the renderer's skeleton when absent |

### Use when

- The main region of a Frame

### Don't use when

- Anywhere else: an outlet is the frame's, and a document has at most one

### Accessibility main

- The screen's h1 is the first heading inside it
- Focus moves to the screen's heading when the outlet's content changes on navigation

**Agents:** Everything it can act on is here; the frame around it is for getting elsewhere.

### Rendering rules

- Takes the main region's width; padding space.inset.comfortable (compact: default)
- While loading, a skeleton shaped by the coming screen's pattern

**A2UI:** custom

## Cardstructure

One self-contained entity (an account, an order, a place), optionally actionable as a whole.

| Prop | Type | Notes |
| --- | --- | --- |
| `title` required | text or binding | Name of the entity |
| `subtitle` | text or binding |  |
| `media` | component id | Optional Media component id |
| `badge` | object |  |
| `children` | component ids |  |
| `action` | action | Activating the card as a whole (e.g. open details) |
| `progress` | object | Progress through the entity (reading, a course, a goal) |

### Use when

- Items in a Collection that each represent an entity
- A single entity summary in a larger surface

### Don't use when

- Plain grouping: use Group
- Tabular data with many attributes: use Table

### Accessibility article (or link/button when it has an action)

- Title is the accessible name
- When actionable, the title is the card's one link; controls inside it (a Toggle, an Action) stay separate targets with their own names

**Agents:** Activate the card by its title; use controls inside it by their own labels.

### Rendering rules

- If 'action' is set, the whole card opens it; controls in children (e.g. a habit's done Toggle) sit above that target and act on their own
- Uses surface.raised and radius.default
- 'progress' renders a progress bar with its label or percentage

**A2UI:** Card

## Columnsstructure

Side-by-side columns that collapse to a stack on compact layouts: a main column with a narrower one beside it, or equal halves.

| Prop | Type | Notes |
| --- | --- | --- |
| `children` required | component ids | One component per column, in reading order |
| `layout` | "two-thirds" \| "one-third" \| "halves" \| "thirds" \| "sidebar-start" \| "sidebar-end" | 'two-thirds' is a wide first column (GOV.UK's default); 'one-third' the reverse; 'halves' and 'thirds' equal; 'sidebar-*' a fixed 18rem column at that edge |
| `collapse` | "compact" \| "medium" \| "never" | The width below which the columns stack |
| `align` | "start" \| "center" \| "stretch" |  |

### Use when

- A form with help beside it; a record with a summary beside it; a list beside a preview
- Anywhere a wide screen should use its width without the compact screen paying for it

### Don't use when

- Metrics in a row: use Group with arrangement 'inline'
- A master–detail pair that keeps both alive: use Split

### Accessibility none (layout only)

- Reading order is the children's order at every width
- Collapsing never reorders content

**Agents:** Invisible: reads the children in order.

### Rendering rules

- Gutter space.inline.loose between columns; stacked with space.stack.loose
- Column widths follow the layout; 'sidebar-*' columns never shrink below 16rem
- Collapses at the surface's width, not the viewport's

**A2UI:** Row

## Disclosurestructure

Progressive disclosure: secondary content hidden behind a toggle.

| Prop | Type | Notes |
| --- | --- | --- |
| `summary` required | text or binding | Always-visible label describing what is hidden |
| `children` required | component ids |  |
| `open` | boolean |  |

### Use when

- Details most people don't need (fees breakdown, advanced options, help text)

### Don't use when

- Content everyone needs to complete the task
- Primary navigation

### Accessibility button (aria-expanded) + region

- Summary is a button with expanded state
- Hidden content is not in the tab order while closed

**Agents:** Expand by the summary text; state is exposed as expanded/collapsed.

### Rendering rules

- Never hide required inputs or the primary action inside a closed Disclosure

**A2UI:** No direct equivalent; exports as Column (content always shown)

## Groupstructure

Visually groups closely related items without a heading (proximity).

| Prop | Type | Notes |
| --- | --- | --- |
| `children` required | component ids |  |
| `label` | text or binding | Accessible name for the group when it isn't obvious from its content |
| `arrangement` | "auto" \| "inline" \| "grid" | Hint only. 'inline' for a few short peers (e.g. metrics); renderers stack on narrow screens. |

### Use when

- A few items that belong together (a key figure and its caption, several metrics)

### Don't use when

- The group needs a heading: use Section
- Choosing between items: use Choice or Comparison

### Accessibility group

- Has an accessible name when it contains interactive items

**Agents:** Treated as one unit when its label is present.

### Rendering rules

- 'inline' collapses to a stack below the compact breakpoint
- Gap uses space.stack.default or space.inline.default

**A2UI:** Column or Row

## Panelstructure

Content over the current view: a dialog, a drawer, a bottom sheet or a popover, opened from an action and dismissed to return.

| Prop | Type | Notes |
| --- | --- | --- |
| `kind` | "dialog" \| "drawer" \| "sheet" \| "popover" | 'dialog' centres and blocks; 'drawer' slides from the side for a form or a record; 'sheet' rises from the bottom on phones; 'popover' floats by its opener for a small choice |
| `title` required | text or binding |  |
| `children` required | component ids | What the panel holds |
| `actions` | component id | An ActionBar in the panel's footer: its primary action and a Cancel |
| `open` | boolean or binding | Bound to host data: true shows the panel; the renderer writes false on dismiss. Open when absent |
| `dismissible` | boolean | Escape, the close control and the backdrop dismiss it; false for a panel that must be completed |
| `size` | "small" \| "default" \| "large" |  |

### Use when

- A short task on top of the page: edit one thing, pick one thing, see one record
- Detail the person asked for that shouldn't replace where they are
- A small set of options next to the control that opened them (popover)

### Don't use when

- Confirming a consequential action: use Confirm, which is a dialog with the right words and order
- A whole flow of several steps: use Steps on its own surface
- Passing feedback: use Status

### Accessibility dialog (aria-modal for dialog, drawer and sheet) named by the title; popover is a non-modal dialog

- Focus moves into the panel on open and back to the opener on close
- Tab stays inside a modal panel; Escape dismisses a dismissible one
- The page behind a modal panel is inert
- A sheet can be dismissed by dragging down as well as by the close control

**Agents:** Opens, reads the content and closes it by name; the footer actions are exposed like any ActionBar.

### Rendering rules

- A backdrop of color.surface.inverse at opacity.overlay for modal kinds
- dialog: max 560px on desktop, full width with space.inset.default on phones; drawer: 420px from the end edge; sheet: from the bottom with radius.large on the top corners; popover: color.surface.overlay with elevation.overlay and an arrow
- The title is type.heading.medium with the close control at the end of the header
- The action bar sits in a footer; on phones it is fixed to the bottom, full width
- Motion uses motion.duration.default and motion.easing.standard; reduced motion fades instead

**A2UI:** Modal

## Sectionstructure

A titled region of the surface that groups related content under a heading.

| Prop | Type | Notes |
| --- | --- | --- |
| `title` required | text or binding | Heading for the section |
| `description` | text or binding | Optional one-line explanation under the heading |
| `children` required | component ids |  |

### Use when

- The surface has two or more distinct topics (e.g. 'Recipient' and 'Amount')
- Content would otherwise exceed one screen and needs signposting

### Don't use when

- Only one topic: put content directly under the root
- Visual grouping without a heading: use Group

### Accessibility region (with heading)

- Title is rendered as a real heading; nesting depth sets the heading level
- Title is the region's accessible name

**Agents:** Navigate by heading; the title names the region.

### Rendering rules

- Heading level follows nesting depth, never chosen by the model
- Spacing between sections uses space.stack.section

**A2UI:** No direct equivalent; exports as Column with a Text(variant: h2) first child

## Splitstructure

Master and detail side by side: a list that stays while the chosen item's detail shows beside it; on compact layouts the detail replaces the list.

| Prop | Type | Notes |
| --- | --- | --- |
| `primary` required | component id | The list: a Collection or a Table with selection 'single' |
| `detail` required | component id | What shows for the selected item; its bindings resolve against the selected item's scope |
| `selected` required | binding | The selected item's value in host data, shared with the primary's 'selected' |
| `empty` | component id | A Status shown in the detail pane while nothing is selected |
| `ratio` | "narrow" \| "balanced" \| "wide" | The list's share: about a third, half, or two thirds |
| `resizable` | boolean |  |

### Use when

- Mail, tickets, messages, files: a list people move through while reading
- Settings with many sections on wide screens

### Don't use when

- Under about 640px of surface width: it becomes a list then a page, so don't use it for a single record
- Two unrelated things side by side: use Columns

### Accessibility none; the primary and detail keep their own roles

- Selection in the list moves focus to the detail on compact layouts and announces the item's name
- A Back control returns to the list on compact layouts
- The list keeps its scroll position and selection while the detail changes

**Agents:** Selects an item in the primary by name and reads the detail; on compact layouts it goes back to choose another.

### Rendering rules

- Wide: two panes with a border between; the list scrolls independently
- Compact: the list, then the detail as a page with a back control when an item is selected
- Selection state in the list uses color.selection.*
- Resizable splits keep the handle at size.target.min wide with a visible focus ring

**A2UI:** custom

## Viewsstructure

Switch between alternative views of the same subject (tabs).

| Prop | Type | Notes |
| --- | --- | --- |
| `views` required | list of objects |  |
| `selected` | binding | Key of the selected view in host data |
| `variant` | "tabs" \| "segmented" | 'tabs' for sections of a record; 'segmented' for switching how the same thing is shown |

### Use when

- 2–6 peer views of the same data (Overview / Transactions / Settings)

### Don't use when

- Sequential steps: use Steps
- Filtering one list: use Choice

### Accessibility tablist / tab / tabpanel

- Each tab has a visible label
- Arrow keys move between tabs

**Agents:** Select a view by its label.

### Rendering rules

- More than 4 views on compact screens become a menu or scrollable tabs
- Counts sit beside their labels and are part of the tab's accessible name

**A2UI:** Tabs

## Chartcontent

A visual summary of data, always paired with a text summary.

| Prop | Type | Notes |
| --- | --- | --- |
| `intent` required | "trend" \| "comparison" \| "composition" \| "distribution" \| "relationship" \| "flow" \| "hierarchy" \| "matrix" \| "range" | What the chart should communicate; the renderer picks the chart type. 'relationship' between two measures; 'flow' of amounts from each x value into the series; 'hierarchy' of parts by size; 'matrix' of x values against series; 'range' from the first series to the second |
| `data` required | binding | Array of data points in host data |
| `title` required | text or binding |  |
| `summary` required | text or binding | Text alternative stating the takeaway (required) |
| `x` required | object |  |
| `series` required | list of objects |  |

### Use when

- A trend, comparison, share or spread matters more than exact values

### Don't use when

- Exact values matter most: use Table or Metric
- A single number: use Metric

### Accessibility img (figure) with the summary as description, plus a data-table alternative

- 'summary' states the takeaway in words
- Series are distinguishable without color (labels, markers)
- The underlying data is available as a table

**Agents:** Reads the summary, and the data through the table alternative.

### Rendering rules

- trend → line, comparison → bar, composition → stacked bar (pie only for ≤ 4 parts), distribution → histogram
- relationship → scatter (x is a measure too), flow → stacked flows from each x value to each series, hierarchy → treemap of the first series, matrix → heatmap of x values by series, range → bars from the first series to the second
- Series colors use color.data.categorical.N in order; a matrix shades one color by value and prints the value in each cell

**A2UI:** No equivalent in the Basic catalog; exports as Text(summary) + List

## Codecontent

Code, a command or preformatted text, shown as written and copyable.

| Prop | Type | Notes |
| --- | --- | --- |
| `text` required | text or binding | The code itself, usually bound to host data (an API key, a command, a snippet) |
| `label` | text or binding | What it is, shown above: 'Install', 'Your API key' |
| `language` | string | A language name for highlighting, when the renderer has it: 'bash', 'json', 'ts' |
| `copyable` | boolean | A Copy control beside it |
| `wrap` | boolean | Wrap long lines instead of scrolling sideways |
| `secret` | boolean or binding | Masked until revealed: a key shown once |

### Use when

- A command to run, a snippet to paste, an identifier to copy
- Structured text whose spacing matters

### Don't use when

- Prose: use Text
- Something the person types: use TextInput

### Accessibility region named by the label, holding a code element; the copy control is a button named 'Copy <label>'

- Text is real text: selectable and read by screen readers line by line
- Copying is announced ('Copied')
- A secret is masked with the same length, with a 'Show' control

**Agents:** Reads the text verbatim; can activate Copy.

### Rendering rules

- type.mono at type.body.small on color.surface.subtle, with radius.control
- Overflow scrolls sideways unless wrap is set; never clipped
- The copy control sits in the top-right corner and says 'Copied' for two seconds after use

**A2UI:** Text

## Collectioncontent

A list of items from host data, each rendered with the same template.

| Prop | Type | Notes |
| --- | --- | --- |
| `items` required | { path, componentId } |  |
| `label` required | text or binding | What the list contains, e.g. 'Recent payments' |
| `empty` | component id | Status component shown when there are no items |
| `selection` | "none" \| "single" \| "multiple" |  |
| `selected` | binding | Selected item value(s) in host data when selection is not 'none' |
| `layout` | "auto" \| "list" \| "grid" \| "timeline" \| "carousel" \| "calendar" | 'grid' for visual items (products, books); 'list' for rows; 'timeline' for dated activity; 'carousel' for a row people page through; 'calendar' places items on a month grid by 'datePath'; 'auto' lets the renderer choose from the item template |
| `datePath` | JSON Pointer | For 'calendar': relative path in each item to the ISO date that places it |
| `page` | object | Paging state, owned by the host |
| `bulkActions` | component id | ActionBar shown while items are selected (selection 'multiple') |
| `reorderable` | boolean | People can drag items, or move them with the keyboard, into a new order |
| `order` | binding | Item values (ids) in the order people put them, written when reorderable |

### Use when

- Browsing a set of similar entities
- Search results

### Don't use when

- Attributes compared across items: use Table or Comparison
- Choosing an option in a form: use Choice

### Accessibility list / listitem (listbox when selectable)

- Announces the item count
- An empty state is provided

**Agents:** Items are enumerated with their titles; each item exposes its own actions.

### Rendering rules

- Long lists are virtualized by the renderer
- Items keep their order from host data
- grid: 2 columns on phones, more as width allows; list: one item per row
- auto: grid when the item template is a Card with media, otherwise list
- 'timeline' marks each item on a line, newest first, for activity feeds
- 'carousel' scrolls horizontally with snap points, Previous and Next buttons and an 'N of M' readout; every item stays reachable by keyboard
- 'calendar' is a month grid with the items on their days; Previous and Next move a month at a time
- Paging shows items per page, the range and the total, as a Table does
- Selection shows the bulk-action bar, naming how many items are selected, while any are
- reorderable: a drag handle per item, plus Move up / Move down buttons; the new order is written to 'order'

**A2UI:** List (templated children)

## DetailListcontent

Label/value pairs describing one thing (a summary, a receipt, a review step).

| Prop | Type | Notes |
| --- | --- | --- |
| `items` required | list of objects |  |
| `title` | text or binding |  |
| `variant` | "default" \| "receipt" | 'receipt' for money: values right-aligned in tabular figures, with an optional total row |
| `layout` | "rows" \| "grid" | 'grid' packs many short fields into columns (record pages); 'rows' keeps one per line |
| `rowAction` | action | Offered per row as a 'Change' link named after the row (GOV.UK summary list); the row's key is sent as 'key' with the context |

### Use when

- Reviewing before submitting
- Showing the attributes of one entity
- Receipts
- Fees, prices and totals before someone commits (variant 'receipt')

### Don't use when

- Many entities with the same attributes: use Table
- Comparing entities: use Comparison

### Accessibility list of term/definition pairs (dl)

- Label and value are programmatically associated

**Agents:** Read each label with its value.

### Rendering rules

- Keeps the item order stable across generations (keys are remembered)
- receipt: values right-aligned in tabular figures, the total row emphasised; people read amounts from the right
- 'grid' lays fields out in columns by width, label above value
- rowAction: each row ends with a 'Change' link whose accessible name includes the row's label; the total row has none

**A2UI:** Column of Row(Text, Text)

## Identitycontent

A person, team or organisation: picture, name and details, or several of them together.

| Prop | Type | Notes |
| --- | --- | --- |
| `name` required | text or binding | Always bound to host data: a name is never invented |
| `detail` | text or binding | One line under the name: a role, an email, a team, a status |
| `image` | text or binding | A media reference from host data, resolved by the host as Media's is; initials from the name are shown without one |
| `kind` | "person" \| "team" \| "organization" | A person's picture is round; a team's or organisation's is square |
| `size` | "small" \| "default" \| "large" |  |
| `action` | action | Activating the identity as a whole, e.g. open the person's record |
| `group` | object | Several identities as one element ('Rhea, Tom and 3 others'). name is then the group's accessible summary |

### Use when

- Who something belongs to, was sent by or is assigned to
- The recipient on a payment, the owner on a record, the members on a team
- A list of people where the face helps recognition

### Don't use when

- Choosing a person from a list: use Choice with avatarPath
- A person's full record: use Card or DetailList

### Accessibility group named by the name; the picture is decorative and the name is text

- The name is real text, never only an image or initials
- A group's accessible name lists the first names and how many more
- With an action, the whole element is one button or link named by the name

**Agents:** Reads the name and detail; a group exposes every member's name.

### Rendering rules

- Initials come from the name's first letters, on a neutral surface with color.text.default; a pack may derive a stable colour from the name
- Sizes: small 24px, default 40px, large 64px; the name uses type.body.default or type.heading.small for large
- A group overlaps pictures by a quarter, with a '+N' tag for the rest
- Images are never stretched; missing images fall back to initials, never to a broken image

**A2UI:** Row of Image + Text

## Mediacontent

An image, video, audio clip, gallery or QR code supplied by the host.

| Prop | Type | Notes |
| --- | --- | --- |
| `src` | binding | Image, video or audio reference in host data; generated UI never contains URLs |
| `alt` | text or binding | Required unless decorative. For a gallery it names the set; for a QR code it says what the code opens |
| `decorative` | boolean |  |
| `aspect` | "square" \| "landscape" \| "portrait" \| "auto" |  |
| `kind` | "image" \| "video" \| "audio" \| "gallery" \| "qr" | 'video' and 'audio' play with native controls; 'gallery' is several images from 'items'; 'qr' encodes 'value' as a QR code |
| `poster` | text or binding | For 'video': media reference for the frame shown before play |
| `transcript` | text or binding | For 'video' and 'audio': the words, offered under the player |
| `items` | binding | For 'gallery': array of images in host data |
| `imagePath` | JSON Pointer | For 'gallery': relative path in each item to its image reference |
| `altPath` | JSON Pointer | For 'gallery': relative path in each item to its alt text |
| `value` | text or binding | For 'qr': the text encoded, e.g. a payment link from host data |

### Use when

- Photos or illustrations that help identify something (a product, a place, a person)
- A recording people play, or a code they scan

### Don't use when

- Decoration with no information
- Icons for actions (renderer supplies those)

### Accessibility img (video / audio players, list for a gallery)

- Has alt text unless decorative (then hidden from assistive technology)
- Video and audio have native controls and offer a transcript
- A QR code's alt says what scanning it does

**Agents:** Reads the alt text; a gallery's images by their own alt text; a QR code's value.

### Rendering rules

- Images never convey information that is not also in text
- 'video' shows 'poster' until played; 'audio' is a compact player; both put 'transcript' under a disclosure
- 'gallery' is a grid of the items' images, each with its own alt text
- 'qr' is drawn on a canvas at a size that scans from a phone, with the alt as its accessible name

**A2UI:** Image (video, audio and gallery export as Image + Text; a QR code as Text of its value)

## Metriccontent

A key figure with a label, and optionally its change.

| Prop | Type | Notes |
| --- | --- | --- |
| `label` required | text or binding |  |
| `value` required | binding | Always bound to host data |
| `format` | format |  |
| `change` | object |  |
| `caption` | text or binding |  |

### Use when

- One to four headline numbers the user asked about

### Don't use when

- Many numbers: use Table or DetailList
- Trends over time: use Chart

### Accessibility group (label + value)

- Change direction is conveyed in text as well as color
- Screen readers read label, value and change as one sentence

**Agents:** Read label and value.

### Rendering rules

- Value uses type.numeric.display
- Change uses color.data.positive / negative according to 'favorable'

**A2UI:** Column of Text

## Progresscontent

How far along something is, or how much of a bounded amount is used: a bar, a ring or a meter.

| Prop | Type | Notes |
| --- | --- | --- |
| `label` required | text or binding | What is being measured: 'Storage used', 'Upload', 'Goal' |
| `value` required | number or binding | Bound to host data. A fraction from 0 to 1, or an amount when max is given |
| `max` | number or binding | The whole, when value is an amount rather than a fraction: 200 for '180 of 200 GB' |
| `kind` | "bar" \| "ring" \| "meter" | 'bar' and 'ring' show progress towards done; 'meter' shows a measured amount within bounds, with tone by threshold |
| `caption` | text or binding | Text beside the readout: '180 of 200 GB', '3 days left'. The readout itself is derived from value |
| `format` | format | How the readout is shown; percent without it |
| `tone` | tone | Fixed tone; a meter without one takes warning past thresholds.warning and danger past thresholds.danger |
| `thresholds` | object | Fractions at which a meter changes tone |
| `indeterminate` | boolean or binding | The task is running and how far along isn't known |

### Use when

- A task that takes time, with a known share done
- How much of a quota, budget or capacity is used
- Progress towards a goal (pages read, steps walked)

### Don't use when

- A number the person came to see: use Metric
- Steps of a task the person moves through: use Steps
- A short wait with nothing to measure: Status 'loading'

### Accessibility progressbar (bar, ring) or meter, named by the label, with aria-valuenow, aria-valuemin, aria-valuemax and aria-valuetext

- The readout is real text next to the graphic, never only the fill
- A meter's tone is also said in text ('nearly full'), not only shown by colour
- Indeterminate progress has no value attributes and is announced as busy

**Agents:** Reads label, value and max as text.

### Rendering rules

- The track uses color.surface.subtle; the fill uses color.action.primary.background, or color.status.<tone>.emphasis for a toned meter
- Height is size.control.small / 3 for a bar; a ring is size.control.default across
- Readout in tabular figures, type.body.small, after the label
- The bar's length means the fraction: never used for an amount with no bound

**A2UI:** Text

## Tablecontent

Tabular data: many items sharing the same attributes.

| Prop | Type | Notes |
| --- | --- | --- |
| `rows` required | binding | Array of row objects in host data |
| `caption` required | text or binding | What the table shows (required for accessibility) |
| `columns` required | list of objects |  |
| `rowAction` | action | Activated per row; context bindings resolve against the row |
| `empty` | component id | Status component shown when there are no rows |
| `sort` | object | Sorting state, owned by the host |
| `selection` | "none" \| "multiple" | 'multiple' adds a checkbox column and a select-all |
| `selected` | binding | Array of selected row values in host data |
| `rowValuePath` | JSON Pointer | Relative path to each row's id, used for selection |
| `bulkActions` | component id | ActionBar shown instead of the toolbar while rows are selected |
| `rowActions` | component id | ActionBar offered per row, in a menu named after the row |
| `expandable` | boolean | Rows open to show 'detail' beneath them |
| `detail` | component id | Component rendered under an expanded row; relative paths inside it resolve against the row |
| `toolbar` | component id | ActionBar above the table (e.g. New, Import, Export) |
| `search` | component id | TextInput (kind 'search') above the table |
| `views` | list of objects | Saved views (All, Mine, Past due…), shown as tabs with counts |
| `view` | binding | Key of the selected saved view in host data |
| `page` | object | Paging state, owned by the host |

### Use when

- Scanning or comparing many rows by several attributes
- Records people scan, sort, select and act on in bulk (B2B lists)

### Don't use when

- One entity: use DetailList
- A few entities where the user must choose: use Comparison

### Accessibility table with caption and column headers

- Has a caption
- Column headers are real header cells
- Numeric columns are right-aligned by format
- Sortable headers carry aria-sort and say what sorting does
- The select-all checkbox is mixed when some rows are selected
- The bulk-action bar names how many rows are selected, announced politely
- Each row's action menu is named after its row

**Agents:** Read by row and column headers; rows with rowAction are activatable. Sort by activating a column header; select rows by their checkboxes; act in bulk from the bar that appears.

### Rendering rules

- On compact screens, rows become stacked cards with label/value pairs
- Alignment follows the column format, never chosen by the model
- Numbers and currency are right-aligned with tabular figures; 'status' renders as a tag using 'tones'
- On compact surfaces the table becomes a list of rows: the entity, the key fields in one line, and the status
- Selection replaces the toolbar with the bulk-action bar while rows are selected
- Paging shows rows per page, the range and the total
- expandable: each row starts with a toggle (aria-expanded) named after the row; the open row's 'detail' spans the table beneath it

**A2UI:** No direct equivalent; exports as List of Row

## Tagcontent

A short label, status or count attached to something else.

| Prop | Type | Notes |
| --- | --- | --- |
| `label` required | text or binding | The word or two it shows: 'Past due', 'Beta', 'Design' |
| `kind` | "label" \| "status" \| "count" | 'status' is the state of the thing it sits on and is coloured by tone; 'count' is a number, with the label as its accessible name |
| `tone` | tone | Meaning, not decoration: a status tag's tone says whether the state is fine, a warning or a problem |
| `count` | number or binding | For kind 'count': the number, bound to host data |
| `remove` | action | Present when the person can take the tag off (a filter chip, a chosen recipient): the capability that removes it |

### Use when

- The state of a record, next to its name: 'Past due', 'Draft', 'Live'
- A category or label a thing carries
- How many of something, on a navigation item or a section heading
- Chosen items a person can take off again

### Don't use when

- A whole sentence of feedback: use Status
- Something the person switches on or off: use Toggle
- A metric the person came for: use Metric

### Accessibility text; for 'count', the label is the accessible name of the number; for a removable tag, a button named 'Remove <label>'

- Tone is conveyed by the label, never only by colour
- A count reads as '<label>: <count>' to a screen reader
- The remove control is at least size.target.min and named for what it removes

**Agents:** Reads label, tone and count; can activate the remove control by its name.

### Rendering rules

- status uses color.status.<tone>.background and .foreground; label and count use neutral tokens
- count is set in tabular figures
- A tag never wraps: long labels are truncated with the full text on hover and in the accessible name
- Fits inline with text and in a Card header; radius.small unless the pack sets a pill

**A2UI:** Text

## Textcontent

A run of text.

| Prop | Type | Notes |
| --- | --- | --- |
| `text` required | text or binding |  |
| `variant` | "body" \| "supporting" \| "caption" \| "rich" \| "list" \| "quote" | 'rich' allows **bold**, *italic*, `code` and [links](href) in the text; 'list' is a bulleted or numbered list of 'items'; 'quote' is a quotation with an optional 'cite' |
| `format` | format | Only when 'text' is bound to a non-string value |
| `items` | binding | For 'list': array in host data, one entry per item; 'text' introduces the list |
| `itemPath` | JSON Pointer | For 'list': relative path in each entry to the text shown, when the entries are objects |
| `ordered` | boolean | For 'list': numbered rather than bulleted |
| `cite` | text or binding | For 'quote': who or what is quoted |

### Use when

- Explanations, instructions, messages

### Don't use when

- Headings: use Section title
- Label/value pairs: use DetailList
- Key figures: use Metric

### Accessibility text

- Body text is at least 16px (type.body.default)
- Line length is capped by measure.max

**Agents:** Read as text.

### Rendering rules

- 'supporting' uses color.text.muted
- No inline styling; emphasis comes from structure
- 'rich' renders only the four markup forms, as real elements; anything else, including HTML, is shown as typed, and only http, https, mailto and tel links are live
- 'list' renders a ul or ol; entries are shown as text, or by 'itemPath' when they are objects
- 'quote' is a blockquote, the cite on its own line after it
- format 'color' shows a swatch of the value beside it; 'bytes' shows a size like 1.2 MB

**A2UI:** Text

## Treecontent

A hierarchy people expand, browse and pick from: folders, an org chart, nested categories.

| Prop | Type | Notes |
| --- | --- | --- |
| `label` required | text or binding | What the tree holds, e.g. 'Folders' |
| `items` required | binding | The top-level nodes in host data: a list |
| `labelPath` required | JSON Pointer | Relative to each node: the text shown |
| `childrenPath` required | JSON Pointer | Relative to each node: its list of child nodes, absent or empty for a leaf |
| `valuePath` | JSON Pointer | Relative to each node: what selection writes; the label when absent |
| `detailPath` | JSON Pointer | Relative to each node: a count or note shown after the label |
| `selection` | "none" \| "single" \| "multiple" |  |
| `selected` | binding | Selected node value(s) in host data when selection is not 'none' |
| `expanded` | binding | Values of the nodes shown open; the first level is open without it |
| `action` | action | Activating a node, e.g. open that folder; context may bind to the node's fields |

### Use when

- Things that contain things: folders and files, an organisation, nested categories
- Picking a place in a hierarchy (move to folder, choose a category)

### Don't use when

- A flat list, however long: use Collection or Table
- Nested navigation of the product itself: use Navigation
- Progressive disclosure of one section: use Disclosure

### Accessibility tree with treeitem nodes; aria-expanded on nodes with children, aria-selected when selectable

- Arrow keys move and expand; Home and End jump; typing jumps to a label
- Level and position are exposed (aria-level, aria-setsize, aria-posinset)
- Expanded state is announced; selection is announced

**Agents:** Reads every node by label and level; expands a node by name and selects or activates one by name.

### Rendering rules

- Indent per level uses space.inline.default; the expander is a chevron at size.target.min
- Leaves have no expander and align with siblings' labels
- Selection uses color.selection.background; the focused node shows color.border.focus
- Beyond 200 nodes the renderer virtualises rows

**A2UI:** Nested Column of Text

## Statusfeedback

Feedback about state: info, success, warning, error, empty or loading.

| Prop | Type | Notes |
| --- | --- | --- |
| `kind` required | "info" \| "success" \| "warning" \| "error" \| "empty" \| "loading" \| "undo" |  |
| `title` required | text or binding |  |
| `message` | text or binding |  |
| `action` | component id | Optional Action (or ActionBar for a primary and a secondary) to recover or continue, e.g. 'Try again'; for 'undo', the Undo action |
| `icon` | "lock" \| "trash" \| "clock" \| "tag" \| "info" \| "alert" \| "mail" \| "shield" \| "search" \| "book" \| "inbox" \| "check" | Optional icon for empty states; the renderer picks one by kind otherwise |
| `variant` | "default" \| "inline" | 'inline' is a notice inside a page (a warning above a record) rather than a block of its own |

### Use when

- Results of an action
- Empty collections
- Errors with a way to recover
- Loading states
- 'undo': after a reversible action ran, say what happened and offer Undo instead of asking first

### Don't use when

- Validation errors on a field: inputs show their own errors
- Confirmation before acting: use Confirm

### Accessibility status or alert (live region)

- success/info/loading are polite live regions; error is assertive
- Meaning is in text, not only color or icon

**Agents:** Reads the title and message; the recovery action is exposed.

### Rendering rules

- error and warning use color.status.* tokens and an icon
- empty states explain why and what to do next
- loading shows a skeleton when layout is known, otherwise a progress indicator
- 'undo' renders as a snackbar at the bottom of the surface, announced politely, with the Undo action
- Empty states may carry an ActionBar: one primary and one secondary action
- 'inline' renders a bordered notice with its action on the right

**A2UI:** Text + Icon

## Choiceinput

Pick one or several options from a known set. The renderer chooses the control.

| Prop | Type | Notes |
| --- | --- | --- |
| `label` required | text or binding |  |
| `options` required | options (list or from data) |  |
| `value` required | binding | Two-way binding into host data |
| `mode` | "single" \| "multiple" |  |
| `help` | text or binding |  |
| `required` | boolean |  |

### Use when

- Any choice from a known set of options

### Don't use when

- On/off setting: use Toggle
- Picking between rich entities by comparing attributes: use Comparison

### Accessibility radiogroup / group of checkboxes / listbox / combobox (depends on rendering)

- Visible group label
- Every option has a text label

**Agents:** Select options by label.

### Rendering rules

- single, 2–4 short options → segmented control or radios
- single, 5–10 → radios (or select on compact screens)
- single, > 10 → searchable select
- multiple, ≤ 10 → checkboxes; > 10 → searchable multi-select
- Option order comes from host data or remembers the last order used

**A2UI:** ChoicePicker

## CodeInputinput

A one-time code or PIN, typed into one box per character.

| Prop | Type | Notes |
| --- | --- | --- |
| `label` required | text or binding | 'Enter the code we emailed you' |
| `value` required | binding | The code so far in host data, as a string |
| `length` | integer |  |
| `kind` | "numeric" \| "alphanumeric" |  |
| `help` | text or binding | Where the code came from and how long it lasts |
| `required` | boolean or binding |  |
| `action` | action | Runs when every character is in, so the person needn't press a button; context binds to value |

### Use when

- A verification code from email or SMS
- A PIN or a short recovery code

### Don't use when

- A password: the host's own sign-in flow, never a generated screen
- Any text longer than twelve characters: use TextInput

### Accessibility a group named by the label; one text input per character, each named 'Digit N of M'

- Typing moves focus forward; Backspace moves it back; pasting the whole code fills every box
- The numeric kind opens the numeric keyboard and accepts autofill of one-time codes
- Errors are said against the group, not one box

**Agents:** Reads the label and length; enters the code.

### Rendering rules

- Boxes are size.control.large squares with radius.control, type.heading.small, centred, in type.mono
- The active box shows color.border.focus; a filled box shows color.border.strong
- Boxes are grouped in threes or fours by a wider gap past six characters

**A2UI:** Custom (no Basic equivalent)

## ColorInputinput

Pick a colour: from swatches the host offers, or any colour.

| Prop | Type | Notes |
| --- | --- | --- |
| `label` required | text or binding |  |
| `value` required | binding | The colour in host data, as CSS text in the chosen format |
| `swatches` | options (list or from data) | Named colours to pick from (value is the colour, label its name); without them the full picker shows |
| `format` | "hex" \| "rgb" \| "hsl" |  |
| `alpha` | boolean | Let the person set opacity too |
| `help` | text or binding |  |
| `required` | boolean or binding |  |

### Use when

- A colour for a label, a tag, a calendar, a theme
- Choosing from a set of brand colours

### Don't use when

- A choice that happens to be shown as colours (a plan, a size): use Choice

### Accessibility swatches are a radiogroup of radios named by their labels; the free picker is a native color input plus a text field for the value

- Every swatch has a name; colour alone is never the only identification
- The value is editable as text, so it can be typed and read
- The chosen colour is shown beside its text value

**Agents:** Reads the swatch names; picks one by name or types a value.

### Rendering rules

- Swatches are size.control.default squares with radius.control and a 1px color.border.default ring; the chosen one shows color.border.focus
- The free picker is the platform's, with the text value beside it in type.mono

**A2UI:** Custom (no Basic equivalent)

## DateInputinput

A date, time, date-time or date range.

| Prop | Type | Notes |
| --- | --- | --- |
| `label` required | text or binding |  |
| `value` required | binding | ISO 8601 value(s) in host data |
| `kind` | "date" \| "time" \| "datetime" \| "dateRange" \| "month" \| "year" | 'month' stores YYYY-MM and 'year' stores YYYY |
| `multiple` | boolean | Several dates rather than one; the value is a list |
| `min` | text or binding |  |
| `max` | text or binding |  |
| `help` | text or binding |  |
| `required` | boolean |  |

### Use when

- Any date or time entry

### Don't use when

- Relative choices like 'This month / Last month': use Choice

### Accessibility group of spinbuttons or a date picker dialog

- Typing the date is always possible, not only picking from a calendar

**Agents:** Fill by label with an ISO date.

### Rendering rules

- Memorable dates (birthdays) use separate day/month/year fields; near dates use a calendar
- 'month' and 'year' take only that part; the year field is typed, four digits
- multiple: each chosen date becomes a chip with a remove control; the field adds another

**A2UI:** DateTimeInput

## FileInputinput

Choose or drop files to attach or upload.

| Prop | Type | Notes |
| --- | --- | --- |
| `label` required | text or binding |  |
| `value` required | binding | The chosen files in host data: a list of { name, size, type, ref }, written by the renderer; the host uploads them |
| `accept` | list of string | Allowed types as extensions or MIME types: ['.pdf', 'image/*'] |
| `multiple` | boolean |  |
| `maxSize` | integer | Largest file, in bytes; the renderer refuses larger ones with a message |
| `help` | text or binding | What to attach and why; the accepted types and size are said by the renderer |
| `required` | boolean or binding |  |

### Use when

- Attaching a document, a receipt, a photo
- Importing a file the product reads

### Don't use when

- Taking a photo with the camera: the host's own flow
- Pasting text: use TextInput

### Accessibility a native file input, labelled; the drop zone is a large target for the same input

- Keyboard opens the chooser; drag-and-drop is an addition, never the only way
- Chosen files are listed as text with a 'Remove <name>' button each
- Type and size limits are said before choosing and in any refusal

**Agents:** Reads the label and limits; attaches files through the host.

### Rendering rules

- A dashed drop zone on color.surface.subtle with radius.control, the chooser control inside it
- Each chosen file shows name, size and a remove control; a progress bar while the host uploads
- Refusals use color.status.danger text under the zone

**A2UI:** Custom (no Basic equivalent)

## Forminput

Collects inputs and submits them together.

| Prop | Type | Notes |
| --- | --- | --- |
| `children` required | component ids |  |
| `submit` required | { label, action } |  |
| `cancel` | { label, action } | Optional; defaults to ui.dismiss when omitted and the surface is dismissible |
| `aside` | component id | Optional summary (DetailList, Card or Group) kept beside the form, e.g. the running total |
| `layout` | "stacked" \| "horizontal" | 'horizontal' puts label and help on the left and the control on the right (settings pages), on wide surfaces |

### Use when

- Any set of inputs that are submitted together

### Don't use when

- Settings that apply immediately: use Toggles with actions
- Several distinct stages: use Steps

### Accessibility form

- Submit is a real submit button
- On submit errors, focus moves to an error summary that links to each field

**Agents:** Fill fields by label, then activate the submit button by its label.

### Rendering rules

- One column; labels above fields
- Submit is the primary action; cancel is secondary
- Submit context is built from the form's input bindings
- 'horizontal' becomes stacked on compact surfaces
- 'aside' sits beside the form on wide surfaces; on compact ones it comes first, before the fields, so people read what they are agreeing to

**A2UI:** Column + Button

## RangeInputinput

A number, or a range between two numbers, within known bounds.

| Prop | Type | Notes |
| --- | --- | --- |
| `label` required | text or binding |  |
| `value` required | binding | Number in host data, or [min, max] when mode is 'range' |
| `min` required | number |  |
| `max` required | number |  |
| `step` | number |  |
| `format` | format |  |
| `mode` | "single" \| "range" | 'range' picks a lower and upper bound; value is then [min, max] |

### Use when

- Approximate values within a range (budget cap, volume)
- A price or date range for filtering (mode 'range')

### Don't use when

- Exact values: use TextInput with kind 'number' or 'currency'

### Accessibility slider

- Current value is shown as text
- Keyboard arrows change the value by step

**Agents:** Set the value directly by label.

### Rendering rules

- Shows min, max and the current formatted value
- mode 'range' shows two thumbs and min/max fields that stay in sync

**A2UI:** Slider

## Ratinginput

A score out of N: given by the person, or shown as others gave it.

| Prop | Type | Notes |
| --- | --- | --- |
| `label` required | text or binding | What is rated: 'Your rating', 'Average rating' |
| `value` required | binding | The score in host data; written when the person can rate |
| `max` | integer |  |
| `readOnly` | boolean or binding | Show a score rather than take one |
| `count` | number or binding | How many ratings the score is from, shown after it: '4.6 (212)' |
| `help` | text or binding |  |
| `required` | boolean or binding |  |
| `action` | action | Runs when a rating is given, with context bound to value |

### Use when

- Asking how something went, out of five
- Showing a product's or place's rating with the count behind it

### Don't use when

- A number within bounds that isn't a score: use RangeInput
- Yes or no: use Toggle or Choice

### Accessibility radiogroup of radios named '1 star' to 'N stars' when it can be given; an image named '4.6 out of 5' when read-only

- Every star is a real control at size.target.min when the person can rate
- The current value is announced; half values are said as such
- Read-only ratings carry the score as text, not only as filled shapes

**Agents:** Reads the score and the count; sets a score by choosing '<n> stars'.

### Rendering rules

- Filled shapes use color.status.warning.emphasis; empty ones use color.border.default
- The readout ('4.6') is shown after the shapes in tabular figures, with the count in color.text.muted
- A rating that can be given shows the label above, like every input

**A2UI:** Row of Icon

## TextInputinput

A single text-like value: text, number, email, phone, currency, search or long text.

| Prop | Type | Notes |
| --- | --- | --- |
| `label` required | text or binding |  |
| `value` required | binding | Two-way binding into host data |
| `kind` | "text" \| "multiline" \| "number" \| "currency" \| "email" \| "phone" \| "url" \| "search" \| "suggestions" \| "richtext" \| "tags" \| "mentions" \| "code" \| "masked" \| "inline" | 'suggestions' offers completions from 'options' while leaving any text allowed; 'richtext' is short formatted text (bold, italic, lists) stored as markdown-ish text; 'tags' is a list of short values entered one at a time; 'mentions' offers 'options' after an @; 'code' is monospace with no autocorrection; 'masked' follows 'mask'; 'inline' reads as text until it is clicked |
| `options` | options (list or from data) | For 'suggestions' and 'mentions': the completions offered |
| `mask` | string | For 'masked': # for a digit, A for a letter, * for either, anything else typed for you, e.g. '#### #### #### ####' |
| `currency` | text or binding | ISO 4217 code when kind is 'currency' |
| `help` | text or binding | Hint shown below the label (not a placeholder) |
| `required` | boolean |  |
| `validation` | object |  |
| `autocomplete` | string | HTML autocomplete token hint (e.g. 'email', 'postal-code') |
| `placeholder` | text or binding | Only for kind 'search', shown inside the field. Never a substitute for the label. |
| `size` | "default" \| "hero" | 'hero' for the one number a surface is about (an amount to send): large and centred |

### Use when

- Free-form values the user types

### Don't use when

- A value from a known set: use Choice
- Dates: use DateInput
- Secrets (passwords, card numbers): not generated; hosts provide their own secure flows

### Accessibility textbox / searchbox / spinbutton

- Visible label, never placeholder-only
- Help and errors are associated with the field
- Kind sets the right keyboard and autocomplete

**Agents:** Fill by label.

### Rendering rules

- Width suggests expected length
- Errors appear after the user leaves the field or submits, not while typing
- kind 'search' renders as a pill search field with an icon; the label stays as its accessible name
- size 'hero' renders a large centred amount with the currency symbol dimmed
- 'suggestions' and 'mentions' are comboboxes: the list opens as you type, arrow keys move through it, Enter takes an option, Escape closes it
- 'richtext' is an editable area with Bold, Italic and List controls; the value is text with **bold**, *italic* and '- ' items
- 'tags' shows each value as a chip with a remove control; Enter or a comma adds what was typed, Backspace in an empty field removes the last chip; the value is a list of strings
- 'code' uses type.numeric (monospace) and turns off autocorrect, autocapitalize and spellcheck
- 'masked' fills the mask's literal characters as you type and stores the text as shown
- 'inline' renders the value as text with an Edit control; Enter saves, Escape restores the previous value

**A2UI:** TextField

## Toggleinput

An on/off setting.

| Prop | Type | Notes |
| --- | --- | --- |
| `label` required | text or binding |  |
| `value` required | binding | Boolean in host data |
| `description` | text or binding |  |
| `action` | action | Set when the change applies immediately (a setting); omit inside a Form |

### Use when

- Settings that take effect immediately
- A single yes/no inside a form

### Don't use when

- Choosing between named options: use Choice

### Accessibility switch (immediate) / checkbox (in a form)

- Label says what 'on' means

**Agents:** Toggle by label; state is exposed as on/off.

### Rendering rules

- With 'action' it renders as a switch; inside a Form without action, as a checkbox

**A2UI:** CheckBox

## Actionaction

A button that triggers a host capability.

| Prop | Type | Notes |
| --- | --- | --- |
| `label` required | text or binding | Verb-first label saying what happens ('Send £20', not 'OK') |
| `action` required | action |  |
| `emphasis` | "primary" \| "secondary" \| "tertiary" |  |
| `tone` | "default" \| "danger" |  |
| `disabled` | boolean or binding |  |
| `description` | text or binding | A second line under the label saying what the action does, for menu-like actions |
| `copy` | text or binding | For the renderer action ui.copy: the text put on the clipboard; otherwise the action context's 'text' is copied |
| `shortcut` | string | Keyboard shortcut that triggers the action while the surface has focus, e.g. "mod+enter" or "shift+d". 'mod' is Command on Apple platforms and Control elsewhere. The renderer shows the hint beside the label and never binds it outside the surface. |

### Use when

- Anything the user can do that isn't typing or choosing

### Don't use when

- Navigating between views: use Views
- Confirming something destructive: wrap in Confirm

### Accessibility button

- Label describes the outcome
- Target at least size.target.min
- Disabled actions explain why nearby
- A shortcut is a hint, never the only way: the button itself stays clickable and focusable

**Agents:** Activate by label.

### Rendering rules

- At most one primary action visible at a time
- danger tone uses color.action.danger.*
- 'description' sits under the label in type.body.small, muted; the button's accessible description
- ui.copy is handled by the renderer: it copies 'copy' (or the context's 'text') and announces 'Copied' politely
- 'shortcut' shows as a keyboard hint (kbd) beside the label, with mod written as ⌘ or Ctrl for the platform; it fires only while focus is inside the surface, never over the host page, and never on a disabled action

**A2UI:** Button

## ActionBaraction

The set of actions for a surface or section; the renderer places it where that platform expects.

| Prop | Type | Notes |
| --- | --- | --- |
| `children` required | component ids | Action component ids, in order of importance |

### Use when

- Two or more actions that apply to the whole surface or section

### Don't use when

- A single action inline with content: use Action directly

### Accessibility toolbar / group

- Order in the accessibility tree is order of importance

**Agents:** Actions are listed together.

### Rendering rules

- Web: bottom of the section, primary last on desktop and first on mobile stacks
- iOS/Android: toolbar or bottom bar on compact screens

**A2UI:** Row of Button

## ActionMenuaction

Secondary actions behind one control: an overflow menu, a dropdown, a split button or a context menu.

| Prop | Type | Notes |
| --- | --- | --- |
| `label` required | text or binding | The control's name: 'More actions' for an overflow, or the verb for a dropdown ('Export') |
| `kind` | "overflow" \| "dropdown" \| "split" \| "context" | 'overflow' is the '…' control; 'dropdown' is a labelled button that opens the menu; 'split' pairs a primary action with a menu of alternatives; 'context' opens on right-click or long-press of the component it belongs to |
| `children` required | component ids | The Actions in the menu, in order; a danger-toned one goes last and is separated |
| `primary` | component id | For 'split': the Action that runs when the main part is pressed |

### Use when

- Three or more secondary actions on a row, a card or a page header
- One usual action with rarer alternatives (split: 'Save' with 'Save as draft')
- Actions that belong to the thing under the pointer (context)

### Don't use when

- One or two actions: use Action or ActionBar, where they are visible
- The main thing to do on the surface: never hidden in a menu
- Navigation between views: use Views or Navigation

### Accessibility a menu button (aria-haspopup, aria-expanded) opening a menu of menuitems; a split's main part is a plain button

- Every action is reachable by keyboard: arrows move, Enter activates, Escape closes and returns focus
- An overflow control is named for what it holds ('More actions for Acme Corp'), never just '…'
- A context menu's actions are also reachable another way (an overflow control), since right-click and long-press aren't discoverable

**Agents:** Opens the menu by its name and activates an action by its label.

### Rendering rules

- The menu uses color.surface.overlay with elevation.overlay and radius.default; items are size.control.default tall with space.inset.default
- A danger-toned Action shows in color.status.danger.emphasis after a divider
- The overflow control is an icon-only button at size.target.min with the label as its accessible name; a dropdown shows the label with a chevron
- A split's two parts share one outline; the menu part is at least size.target.min wide

**A2UI:** Custom (no Basic equivalent)

## Comparisonflow

Compare a few options across the same attributes, and choose one.

| Prop | Type | Notes |
| --- | --- | --- |
| `items` required | binding | Array of options in host data (2–4 recommended) |
| `itemTitle` required | JSON Pointer | Relative path to each item's name |
| `attributes` required | list of objects |  |
| `choose` | { label, action } | Action per item; context bindings resolve against the item |
| `recommended` | text or binding | Optional value of the recommended item's title, with a reason in 'summary' |
| `summary` | text or binding |  |
| `recommendedReason` | text or binding | Why the recommended item fits, shown on its badge (one short sentence) |

### Use when

- Choosing between 2–4 plans, products, routes or offers

### Don't use when

- More than ~5 items: use Table with sorting
- No decision needed: use Table

### Accessibility table (items as columns on wide screens) or list of cards

- Attribute names are headers
- 'better' is conveyed in text, not only color

**Agents:** Reads attributes per item; choose actions are named with the item title.

### Rendering rules

- Wide: items as columns; compact: one card per item with the same attribute order
- Best values per attribute are marked when 'better' is set
- Exactly one item may be recommended; its badge carries recommendedReason
- Attributes with the same group sit under one heading
- Boolean values render as a check or a dash with text alternatives ('Included' / 'Not included')
- On compact surfaces the recommended item comes first

**A2UI:** No direct equivalent; exports as List of Card

## Confirmflow

Asks the user to confirm a consequential or destructive action, showing what will happen.

| Prop | Type | Notes |
| --- | --- | --- |
| `title` required | text or binding | Question naming the action ('Send £250 to Alex?') |
| `message` | text or binding |  |
| `consequence` | text or binding | What happens and whether it can be undone |
| `summary` | component id | Optional DetailList summarising what is being confirmed |
| `severity` required | "consequential" \| "destructive" |  |
| `confirm` required | { label, action } |  |
| `cancel` | { label, action } | Defaults to 'Cancel' → ui.dismiss |
| `typeToConfirm` | text or binding | Text the user must type before confirming (for irreversible, high-impact actions) |
| `amount` | object | The amount being committed, shown large at the top |
| `subject` | object | Who or what the action is for, shown with a face or picture |
| `consequences` | list of objects | What will happen, as a short list (for destructive actions) |

### Use when

- Moving money, deleting data, sending on someone's behalf, anything irreversible

### Don't use when

- Routine reversible actions (prefer undo)
- Information only: use Status

### Accessibility alertdialog

- Focus starts on the least destructive option
- Confirm label repeats the action verb
- Escape cancels

**Agents:** Reads title and consequence; confirm and cancel are named buttons.

### Rendering rules

- destructive uses color.action.danger.*
- Capabilities with high risk level must use Confirm (enforced by the verifier)
- the consequence sits directly above the confirm button
- amount and subject lead the dialog when set
- consequences render as an icon list, two columns on wide surfaces

**A2UI:** Modal with Text and Buttons

## Stepsflow

A task split into ordered steps with visible progress.

| Prop | Type | Notes |
| --- | --- | --- |
| `steps` required | list of objects |  |
| `kind` | "wizard" \| "tasklist" \| "guide" | 'wizard' shows one step at a time in order; 'tasklist' lists tasks people do in any order, each with its status; 'guide' is numbered instructions with no state |
| `current` | number or binding | Index of the current step (default 0) |
| `finish` required | { label, action } |  |

### Use when

- Tasks with dependent stages or too many inputs for one view (checkout, onboarding)

### Don't use when

- Independent views: use Views
- Under ~6 inputs: a single Form

### Accessibility group with step progress (aria-current on the current step)

- Current step and total are announced
- Back never loses entered data

**Agents:** Reads 'step N of M'; next/back are exposed as buttons.

### Rendering rules

- Renderer provides Back (ui.back) and Next (ui.next); the last step shows 'finish'
- Each step validates before moving on
- 'tasklist' is a list of tasks with a status tag each (GOV.UK task list); a task opens its content in place, and 'finish' follows the list, saying how many are done
- 'guide' shows every step's title and content in a numbered list, with 'finish' after the last

**A2UI:** No direct equivalent; exports as Column of the current step
