# j3w1 UI Theme Spec — full (1.1.0)
<!-- generated by scripts/generate.mjs from tokens/ and spec/ — do not edit. theme: j3w1-theme 1.1.0 · profile: default · sourceDigest: sha256-C+iD5Q7l0XPU/boMj003wPbPLBOq3C3ZM7s4OfzJ6mc= -->

Use the pinned approved default profile. Pending roles in that profile use-and-report their decision IDs; this does not approve them. Proposed profiles are preview-only and blocked for delivery. Heritage profiles are historical-only. Deprecated or heritage roles are blocked for new approved-profile mappings. Consume roles within their documented scope, never primitives. Release numbering does not approve profiles or tokens.
Everything in one document: identity, foundations, every profile's resolved tokens, accessibility with the contrast report, portability, every component specification, the coverage ledger, the support table, the decision log and the references catalogue.

## Identity

<!-- @compact:start -->
True-black canvas (`#000000`), near-black panels (`#100c0c`) and chrome (`#090707`), with dark-red raised layers, rose interface text (`#e99499`), near-white reading text (`#f4eeee`), dark-red selections (`#531310`), a bright red focus ring (`#e53935`), 1px square borders, zero radii, one monospace family for chrome, reading and code. Red marks *interaction*: focus, selection, active borders, primary actions. It is not a decoration and it is not applied to everything. Darkness is the canvas; readable content is the priority.

Five rules keep an implementation recognisable:

1. Preserve the warm-black / rose / red identity. Never substitute a charcoal-and-blue dark theme because the target ships one.
2. Use the role assigned to a token, never the token whose colour happens to look closest.
3. Keep content, selection, keyboard focus, diagnostics and changed text distinguishable from one another. Selection is a fill; focus is a ring.
4. Nothing is rounded, blurred, glowing or gradient-filled. Elevation is a 1px border and, for modals, a backdrop.
5. Preserve the host application's behaviour, keyboard handling and accessibility settings. Geometry and typography limits of a host are documented deviations, not reasons to rewrite it.
<!-- @compact:end -->

## Where it came from

The palette began as a Manjaro i3 workstation preserved in `j3w1/1w3j`: a pywal
ramp pulled from a red wallpaper, then frozen and hand-tuned slot by slot in
`config/Xresources` while watching what `ls`, vim and the agnoster prompt
actually rendered. The i3 configuration supplied the rest of the idiom: 1px
pixel borders, 14/−2 gaps, tabbed layouts, a hidden top bar with Chinese
status labels, dunst toasts framed in `#ae1914`, dmenu selections in
`#630f0d`.

The contemporary reference is `j3w1/j3w1.github.io`, a working web
reinterpretation of that workstation. Its stylesheet named the roles this
specification adopts (`--terminal`, `--surface-raised`, `--selection`,
`--focus`, `--prose`, `--muted` …) and wrote the first contrast contract. Both
sources are pinned by revision in `references/sources.json`; after adoption
this repository is the authority and later changes in either source are
proposals, not automatic updates.

## What "modern" means here

Clear organisation, responsive behaviour, strong keyboard and screen-reader
support, precisely specified states, and controls that are pleasant to
operate. It does not mean pill buttons, glass panels, gradients, neon glows, a
Material-style redesign, or red on every surface. Sixteen concrete
misreadings are rendered as labelled non-examples on the site.

The specification page’s generated literal-color previews have the narrow
geometry, checkerboard and motion exception recorded in D-014; the component
and consumer rules remain as written.

## Profiles

- `default` — the approved everyday composition. Monochrome red/rose for
  chrome, text, borders, interaction and actions; three bounded hues (amber,
  green, blue) for status, diagnostics and diffs (D-001); monochrome syntax.
- `heritage-ansi` — the sixteen Xresources slots and the historical
  assignments, exact, including the ones that fail the contrast floor. For
  terminals, archives and fidelity; never for new interface text.
- `extended` — a proposed overlay that colours syntax, a full sixteen-slot
  semantic terminal palette and chart series with the same three hues. Not
  approved; the site labels it.

## Naming

Roles are `color.<group>.<role>` with a closed group list: `surface`, `text`,
`border`, `interaction`, `action`, `status`, `code`, `diagnostic`, `diff`,
`terminal`, `chart`, `icon`. Primitives are `color.primitive.<family>.<step>`
where the step rises with luminance. A CSS custom property is the path with
dots replaced by dashes: `color.interaction.focus.ring` →
`--color-interaction-focus-ring`. Roles alias primitives; primitives carry
provenance.

## Foundations

Values live in `tokens/`; this document says what each role means and where it
may be used. Every rule below is normative for the `default` profile.

### Surfaces

`surface.desktop` and `surface.canvas` use true black (`#000000`). Readable
content sits on the canvas or `default` (`#100c0c`) panels and cards, `raised` (`#160b0b`) for sticky headers,
menus and popovers, `overlay` for dialogs and drawers, `sunken` (`#000000`)
for reading and detail panes, `input` for form-control fills, `chrome` and
`chrome-alt` for window frames and bars. Layering is subtle by design; it may
not erase the boundary of a control, which is why controls also carry
`border.control`.

Never tint photographs, video, document content or user-defined data colours to
put more red on screen.

### Text ladder

| Role | Value | Contrast on canvas / default / raised | Use |
| --- | --- | --- | --- |
| `text.prose` | `#f4eeee` | 18.31 / 16.96 / 15.83 | long-form reading, on-fill text |
| `text.bright` | `#ffa2a7` | 10.98 / 10.17 / 9.49 | headings and emphasis |
| `text.default` | `#e99499` | 9.16 / 8.48 / 7.92 | interface and body text |
| `text.muted` | `#bd787d` | 6.15 / 5.69 / 5.31 | secondary text; the darkest colour permitted for chrome text |
| `text.subtle` | `#ad7175` | 5.40 / 5.00 / 4.66 | metadata, comments, line numbers |
| `text.disabled` | `#8a5559` | 3.52 / 3.26 / 3.05 | disabled controls only (exempt under WCAG 1.4.3; at least 3:1 by policy) |
| `text.accent` | `#e53935` | 4.97 / 4.60 / 4.30 | accent text on canvas and default surfaces only |
| `text.accent-strong` | `#f73f35` | 5.72 / 5.29 / 4.94 | accent text safe on every surface |

Ordinary text must reach 4.5:1 against its actual background in every state.
`#a3676b` and `#7d1310` are never text roles in `default`, even when a
particular darker background improves their ratio; they
remain in service as `border.control`, `icon.decorative`, `status.neutral.fill`
and `border.disabled`. Metadata and comments that a person needs to read are
not decorative and use `text.subtle`, not a graphic colour.

The specification page’s generated literal-color previews have the narrow
geometry, checkerboard and motion exception recorded in D-014; the component
and consumer rules remain as written.

### Borders

Every border is 1px (`border.width.default`); 2px (`border.width.emphasis`) is
reserved for the selected indicator bar, the sticky-header rule and the invalid
border. The quote rule is the one wider edge, at twice the emphasis width,
because it marks a block rather than bounding a control. `border.divider` (`#2b0e0d`) separates rows and sections;
`border.default` (`#531310`) is decorative and may never be the only visible
edge of a control; `border.control` (`#a3676b`, 4.71:1) is the boundary of
every form control at rest; `border.active` (`#e53935`) marks the focused or
active control; `border.overlay` (`#e53935`) frames every floating layer.
Styles: solid by default; dashed only for focus, drop targets and read-only
fields; dotted for hints; double for conflicts. Radius is `0` everywhere,
including checkboxes, switches, chips, avatars, badges and tooltips.

<!-- @compact:start -->
### Interaction states

Every interactive component declares which of these apply and demonstrates every applicable one, including the combinations. Precedence when several apply: disabled > loading > invalid > selected > pressed > hover; focus-visible is always drawn on top and never suppressed by another state.

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | rest tokens | — |
| hover (`@media (hover: hover)` only) | background → `interaction.hover.bg`; links use it too with `text.link-hover`, menu items → `interaction.hover.bg-strong` | link underline thickens 1px → 2px; cursor |
| active / pressed | background → `interaction.pressed.bg`; border → `border.active` | border change; no translation |
| focus-visible | the ring (below) | the ring itself; never a glow or a colour shift alone |
| selected | fill `interaction.selection.bg` with `interaction.selection.text`; tabs and navigation use a 2px `border.selected-indicator` | `aria-selected` / `aria-current`; a check glyph in lists |
| selected + focus-visible | the selection fill **and** a ring in the fill's on-fill colour (`interaction.focus.ring-container`, 7.48:1 on the selection) | both visible at once |
| selected, container inactive | `interaction.selection.inactive-bg` with `interaction.selection.inactive-text`; no ring | lightness drop between the two fills |
| disabled | `text.disabled`, `border.disabled`, `interaction.disabled.bg`; no hover or pressed styling; still focusable when `aria-disabled` | `disabled` / `aria-disabled`; `cursor: not-allowed`; never opacity |
| invalid | border → `status.danger.border` at 2px; message in `status.danger.text` with the `✕` glyph; `aria-invalid`; `aria-describedby` | border width 1 → 2px; glyph; message |
| invalid + focus-visible | the 2px danger border **and** the ring in `interaction.focus.ring-container` at `focus.offset-invalid` | double boundary |
| read-only | `text.default`; 1px dotted `border.divider` bottom; no fill; focusable; selectable | `readonly`; dotted edge |
| loading | size unchanged; a static `⋯` glyph replaces the leading icon; `aria-busy` | glyph; no animation under reduced motion |
| busy / indeterminate | static 45° stripes of `action.primary.bg` on `interaction.pressed.bg`; `aria-valuetext` | stripes |
| checked / mixed | fill `action.primary.bg`, glyph `✓` or `–` in `action.primary.text` | glyph |
| expanded / collapsed | SVG chevron rotated 0 / 90° within the motion budget | `aria-expanded` |
| current | 2px `border.selected-indicator` and `text.bright` | indicator bar |
| drop-target / dragging | dashed `interaction.drop-target` border; `interaction.marquee` fill | dashed pattern |

Rules that apply everywhere:

1. **Selection is a fill; focus is a ring.** Neither borrows the other's form. A ring still has to be visible on whatever it sits over: `#e53935` measures 2.52:1 on `action.primary.bg` and 1.13:1 on `status.danger.fill`, which is why the ring recolours on those fills instead of relying on hue. It measures 3.38:1 on the `interaction.selection.bg` recessed by D-027, so a selected row keeps the ordinary control ring.
2. **Focus ring (D-006; component mapping clarification D-015).** Controls and rows: `outline: 1px dashed {color.interaction.focus.ring}; outline-offset: -2px`. Focusable containers (panes, windows, dialogs, cards): `outline: 2px solid {color.interaction.focus.ring-container}; outline-offset: -3px`. On filled surfaces use the component's explicit ring mapping: primary buttons and checked controls use `interaction.focus.ring-container` where declared; destructive buttons use their declared on-fill text role. The selection fill is the exception and takes the ordinary ring, on the measurement in rule 1. Keep the control or container geometry independently of the colour mapping. `:focus:not(:focus-visible)` draws nothing. Forced-colors mode uses `Highlight`. A host focus indicator is never removed without this replacement.
3. **Never colour-only.** Every state and every status carries a second channel: a glyph (`✕ ! ✓ i ·`), a border width, an underline pattern, or an ARIA state. Charts carry pattern fills.
4. **Hover changes fills and borders, never text colour alone**, and only on hover-capable pointers.
5. **Disabled never uses opacity.** Opacity leaks the background and breaks contrast accounting.
6. **Contrast floor.** Text ≥ 4.5:1 and control boundaries, rings and meaningful graphics ≥ 3:1 against the actual background of the state, measured after compositing translucent colours.
7. **Motion.** Durations are 80 / 120 / 150 ms at most, on opacity, transform and background only; under `prefers-reduced-motion: reduce` every duration is 0.01 ms, nothing slides or shimmers, and no behaviour waits on `transitionend`.
<!-- @compact:end -->

### Actions

Four button tones. `primary` is the only filled button at rest
(`action.primary.bg` `#7d1310` with `text.prose`, 9.28:1); `secondary` is an
outline (`border.control`, text `text.bright`); `tertiary` is a ghost or icon
button (`text.default`, hover `interaction.hover.bg`); `destructive` is an
outline in `action.destructive.text` (`#f73f35`) that fills with
`action.destructive.hover-bg` (`#dc282e`) and `#f9faf9` text (4.58:1) on
hover, and a filled variant for confirm dialogs. Destructive actions also
require a confirmation step or an undo affordance; the colour is not the
safeguard.

### Status, diagnostics and diffs (D-001)

Five statuses: `danger` (`#f73f35` text, `#dc282e` fill), `warning`
(`#c9973f`), `success` (`#86a46f`), `info` (`#7e9ebb`), `neutral` (`#bd787d`
text, `#a3676b` fill). Each has `text`, `fill`, `on-fill`, `tint` and `border`
roles, a mandatory glyph (`✕ ! ✓ i ·`) and a distinct underline or border
pattern (wavy, dashed, solid, dotted, double). The three extension hues are
desaturated and warm-shifted so they sit inside the identity, all between 6.5:1
and 7.6:1 on every surface, and are **forbidden** in `surface`, `text`,
`border`, `interaction` and `action`. Purple, cyan and magenta do not exist in
the theme.

Diagnostics: error = wavy `#e53935` underline, `#f73f35` text, `#e53935`
stripe, `#2b0e0d` line background; warning = wavy amber; info = dotted blue;
hint = dotted `text.subtle`; unused = `text.muted` with a dotted underline.
Diffs: added = `#0f1a0e` line background, green gutter mark and `+`,
word-level additions as a 2px green underline (no fill, so text contrast is
untouched); removed = `#2b0e0d` background, red gutter mark and `−`, word-level
removals underlined and struck through; modified = `#1f1a0c` background, amber
gutter mark and `~`; conflicts = a double `#f73f35` border. Textual markers and
existing status icons are always preserved.

### Code and terminal

Editor surfaces use `code.bg` (`#0c0909`), `code.current-line` (`#1c0a09`),
`code.line-number` (`text.subtle`, replacing the site's `#7d1310`), caret
`#e99499`, selection `#420f0c` (muted text stays 4.71:1 inside it), search
matches `#630f0d` with the current match inverted (`#ffa2a7` on `#0c0909`),
bracket match as a 1px `#e53935` border, indent guides `#531310`, rendered
whitespace `#7d1310` (decorative). Syntax in `default` is monochrome:
keywords `#f73f35`, strings `#bd787d`, comments `#ad7175` italic, numbers and
constants `#d4868b`, functions `#ffa2a7`, variables and operators `#e99499`,
types `#b37175`, properties and headings `#e53935`, tags `#f73f35`,
attributes and escapes `#d4868b`; invalid text takes a wavy underline,
deprecated text a line-through. Languages inherit these roles; a
language-specific override needs a documented reason.

Terminal roles carry the sixteen slots **exactly** in every profile. Slots
1, 4, 6, 8, 9, 10, 12, 13, 14 and 15 do not reach 4.5:1 on the terminal
background (slot 15, `#a3676b`, measures 4.45:1; slot 0 is the background
itself); the theme documents this rather than normalising it, because
programs, not the theme, choose which slot means what. Inside an editor
selection (`code.selection-bg`, `#420f0c`) muted text stays at 4.71:1 but the
keyword, type, property and comment roles fall below 4.5:1; a selection is a
transient state and the limitation is recorded, not hidden. The `extended` overlay
proposes a semantic sixteen-slot palette that all reaches 4.5:1.

Links use strong red `text.link` (`#f73f35`) and a persistent underline, distinct
from the rose body and heading ladder (D-024). Hover uses near-white
`text.link-hover` on `interaction.hover.bg` (D-027; the strong fill read louder
than the link itself and stays with menu items). Current-page and selected
navigation use their explicit indicator and on-fill roles.

### Typography

One family: `SauceCodePro NFM` (Source Code Pro through the Nerd Fonts build),
falling back to Source Code Pro, Cascadia Mono, Consolas, Liberation Mono,
DejaVu Sans Mono and `monospace`. The site records `size-adjust` metrics for
the fallbacks; no font file is distributed. Weights 400 and 700 plus italic.
Scale (size/line-height in px): `ui-sm` 12/16, `ui-md` 13/18, `ui-lg` 14/20,
`reading` 15/24, `code` 13/19, `terminal` 13/19 with −0.5px letter-spacing,
`h1` 20/28 bold, `h2` 16/24 bold, `h3` 13/18 bold in `text.accent`, `caption`
11/16 only with `text.muted` or lighter. Hosts keep the user's chosen size;
tiny UI text is a reference-implementation metric, not a rule.

### Spacing and density (D-009)

Spacing steps: 0, 1, 2, 4, 8, 12, 16, 24, 32, 48 px. Two density modes applied
with `data-density` on any subtree: `compact` (control 24px, row 28px, `ui-sm`
type, 8px horizontal padding, 2px gaps) matches the workstation; `comfortable`
(control 32px, row 36px, `ui-md` type, 12px padding, 4px gaps) suits everyday
applications. Both meet the 24×24 CSS-pixel target minimum; `compact` requires
4px between adjacent targets. Table rows are exempt and always take the
comfortable row height: a data row is content rather than chrome, and
compacting it costs scannability where it is needed most. Controls inside a
row still follow the page density. The site's 3px i3 gap, 14/−2 window gaps and
28px bar are window-manager geometry, not theme tokens.

### Elevation and layering

There are no shadows except `shadow.floating` (`0 6px 22px rgb(0 0 0 / 55%)`)
on the i3 floating-window specimen. Floating layers are a 1px `border.overlay`
on `surface.raised`; modals add `surface.backdrop` (`rgb(0 0 0 / 65%)`).
Stacking: canvas 0 < raised 1 < popover 10 < drawer 20 < dialog 30 < toast 40 <
skip link 1000.

A popup opened by hovering follows the pointer: it sits 8px from the cursor,
flips to the other side of it rather than crossing a viewport edge, keeps an
8px margin from every edge, and is repositioned on pointer move, scroll and
resize. A popup with nothing to operate takes no pointer events, so it cannot
stand between the cursor and what opened it; one that carries a control or a
link stays reachable and holds while the pointer is inside it. Either closes
when the pointer leaves both the trigger and the popup.

The same popup opened from the keyboard anchors to its trigger instead —
below it, or above when there is no room below — because focus has no pointer
to follow. This is a placement rule only: what opens a popup, and whether it
is a tooltip, a preview or an inspector, is the component's own contract.

### Icons

SVG line icons on a 16px grid, 1.5px stroke, `stroke: currentColor`,
`fill: none`, sizes 12 / 16 / 20. No icon font is required; Nerd Font glyphs
appear only in terminal and heritage specimens. Icon-only controls carry
`aria-label` and a tooltip. `icon.decorative` (`#a3676b`) is for glyphs that
convey nothing on their own.

### Layout

Reading measure 72ch, content column 680px, application layout 1440px.
Breakpoints 320 (reflow floor), 600, 900, 1280. Components use container
queries; sidebars collapse to drawers below 900px; data tables scroll
horizontally with a sticky first column below 600px; the page never scrolls
horizontally at 320px. Right-to-left mirrors breadcrumb separators,
pagination, sidebar and drawer sides, wizard progression, affixes and tree
indentation.

## Tokens — profile `default` (approved)

| Role | Value | Alias of | Use |
| --- | --- | --- | --- |
| `color.surface.desktop` | `#000000` | `{color.primitive.ink.0}` | Behind every window.; use |
| `color.surface.canvas` | `#000000` | `{color.primitive.ink.0}` | Page and terminal background.; use |
| `color.surface.chrome` | `#090707` | `{color.primitive.ink.14}` | Window chrome, title bars, bars.; use |
| `color.surface.chrome-alt` | `#1c0a09` | `{color.primitive.ink.40}` | Alternate chrome.; use |
| `color.surface.sunken` | `#000000` | `{color.primitive.ink.0}` | Reading and detail panes set below the canvas.; use |
| `color.surface.default` | `#100c0c` | `{color.primitive.ink.28}` | Panels and cards.; use |
| `color.surface.raised` | `#160b0b` | `{color.primitive.ink.30}` | Sticky headers, menus, popovers; D-027 recessed it so an open list reads as depth rather than a lighter panel.; use |
| `color.surface.overlay` | `#241010` | `{color.primitive.ink.52}` | Dialogs and drawers.; use |
| `color.surface.input` | `#000000` | `{color.primitive.ink.0}` | Form-control fill.; use |
| `color.surface.code` | `#000000` | `{color.primitive.ink.0}` | Editor background.; use |
| `color.surface.terminal` | `#000000` | `{color.primitive.ink.0}` | Terminal background.; use |
| `color.text.default` | `#e99499` | `{color.primitive.rose.700}` | Interface and body text.; use |
| `color.text.bright` | `#ffa2a7` | `{color.primitive.rose.800}` | Headings and emphasis. Links use color.text.link.; use |
| `color.text.prose` | `#f4eeee` | `{color.primitive.paper.90}` | Long-form reading text.; use |
| `color.text.muted` | `#bd787d` | `{color.primitive.rose.600}` | Secondary text; the darkest colour permitted for chrome text.; use |
| `color.text.subtle` | `#ad7175` | `{color.primitive.rose.450}` | Metadata, comments, line numbers.; use |
| `color.text.disabled` | `#8a5559` | `{color.primitive.rose.300}` | Disabled control text.; use |
| `color.text.placeholder` | `#a3676b` | `{color.primitive.rose.350}` | Placeholder text, italic; D-027 darkened it to 4.71:1, just clear of the 4.5:1 floor.; use |
| `color.text.accent` | `#e53935` | `{color.primitive.red.800}` | Accent text on canvas and default surfaces only.; use |
| `color.text.accent-strong` | `#f73f35` | `{color.primitive.red.850}` | Accent text safe on every surface.; use |
| `color.text.link` | `#f73f35` | `{color.primitive.red.850}` | Strong red link text, distinct from rose body text and headings; retain the underline.; use |
| `color.text.link-underline` | `#dc282e` | `{color.primitive.red.750}` | Link underline.; use |
| `color.text.link-hover` | `#f4eeee` | `{color.primitive.paper.90}` | Link text on hover, over interaction.hover.bg-strong.; use |
| `color.text.on-selection` | `#f4eeee` | `{color.primitive.paper.90}` | Text on the selection fill.; use |
| `color.text.on-action` | `#f4eeee` | `{color.primitive.paper.90}` | Text on the primary action fill.; use |
| `color.text.on-danger` | `#f9faf9` | `{color.primitive.paper.100}` | Text on the danger fill.; use |
| `color.text.on-light` | `#0c0909` | `{color.primitive.ink.20}` | Text on light fills: bright rose, amber, green, blue.; use |
| `color.text.inverse` | `#0c0909` | `{color.primitive.ink.20}` | Text on inverted surfaces.; use |
| `color.border.default` | `#531310` | `{color.primitive.red.100}` | Decorative border (1.39:1; never the only edge of a control).; use |
| `color.border.strong` | `#9e231f` | `{color.primitive.red.400}` | Emphasised 2px rule: sticky headers, section ends.; use |
| `color.border.active` | `#e53935` | `{color.primitive.red.800}` | Active or focused control border.; use |
| `color.border.disabled` | `#7d1310` | `{color.primitive.red.200}` | Disabled control border (exempt).; use |
| `color.border.selected-indicator` | `#e53935` | `{color.primitive.red.800}` | 2px indicator bar on the selected tab or nav item.; use |
| `color.border.selected-indicator-inactive` | `#9e231f` | `{color.primitive.red.400}` | Indicator bar when the container is not focused.; use |
| `color.interaction.focus.ring` | `#e53935` | `{color.primitive.red.800}` | 1px dashed focus ring on controls and rows.; use |
| `color.interaction.focus.ring-container` | `#ffa2a7` | `{color.primitive.rose.800}` | Focus colour for containers and explicitly mapped filled controls (D-015); component rules determine ring geometry.; use |
| `color.interaction.selection.bg` | `#531310` | `{color.primitive.red.100}` | Selected row, tab, workspace; D-027 recessed the fill, leaving the 2px indicator and the check to carry selection.; use |
| `color.interaction.selection.text` | `#f4eeee` | `{color.primitive.paper.90}` | Text on the selection.; use |
| `color.interaction.selection.inactive-bg` | `#420f0c` | `{color.primitive.red.50}` | Selection while the container is not focused.; use |
| `color.interaction.selection.inactive-text` | `#e99499` | `{color.primitive.rose.700}` | Text on the inactive selection (7.02:1).; use |
| `color.interaction.text-selection.bg` | `#911410` | `{color.primitive.red.350}` | ::selection background.; use |
| `color.interaction.text-selection.text` | `#f4eeee` | `{color.primitive.paper.90}` | ::selection text.; use |
| `color.interaction.hover.bg` | `#1c0a09` | `{color.primitive.ink.40}` | Hover on rows, ghost buttons, list items.; use |
| `color.interaction.hover.bg-strong` | `#630f0d` | `{color.primitive.red.150}` | Hover on menu items; D-027 moved links to interaction.hover.bg.; use |
| `color.interaction.pressed.bg` | `#420f0c` | `{color.primitive.red.50}` | Pressed fill.; use |
| `color.interaction.disabled.bg` | `#160b0b` | `{color.primitive.ink.30}` | Disabled control fill.; use |
| `color.interaction.marquee` | `rgb(145 20 16 / 12%)` | `{color.primitive.alpha.red-12}` | Drag marquee and ghost.; use |
| `color.interaction.drop-target` | `#e53935` | `{color.primitive.red.800}` | Dashed drop-target border.; use |
| `color.interaction.scrollbar.track` | `#0c0909` | `{color.primitive.ink.20}` | Scrollbar track.; use |
| `color.interaction.scrollbar.thumb` | `#420f0c` | `{color.primitive.red.50}` | Scrollbar thumb.; use |
| `color.interaction.scrollbar.thumb-hover` | `#911410` | `{color.primitive.red.350}` | Scrollbar thumb on hover.; use |
| `color.action.primary.bg` | `#7d1310` | `{color.primitive.red.200}` | Primary button fill; D-026 darkened it off color12, which stays fixed as heritage ANSI 12.; use |
| `color.action.primary.text` | `#f4eeee` | `{color.primitive.paper.90}` | Primary button text (9.28:1).; use |
| `color.action.primary.hover-bg` | `#911410` | `{color.primitive.red.350}` | Primary button hover (7.92:1 with prose).; use |
| `color.action.primary.pressed-bg` | `#630f0d` | `{color.primitive.red.150}` | Primary button pressed.; use |
| `color.action.primary.border` | `#9e231f` | `{color.primitive.red.400}` | Primary button border.; use |
| `color.action.secondary.bg` | `rgb(0 0 0 / 0%)` | `{color.primitive.alpha.transparent}` | Secondary button has no fill.; use |
| `color.action.secondary.text` | `#ffa2a7` | `{color.primitive.rose.800}` | Secondary button text.; use |
| `color.action.secondary.border` | `#a3676b` | `{color.primitive.rose.350}` | Secondary button border (meets 3:1).; use |
| `color.action.secondary.hover-bg` | `#630f0d` | `{color.primitive.red.150}` | Secondary button hover.; use |
| `color.action.secondary.pressed-bg` | `#420f0c` | `{color.primitive.red.50}` | Secondary button pressed.; use |
| `color.action.tertiary.text` | `#e99499` | `{color.primitive.rose.700}` | Ghost and icon button text.; use |
| `color.action.tertiary.hover-bg` | `#1c0a09` | `{color.primitive.ink.40}` | Ghost and icon button hover.; use |
| `color.action.destructive.text` | `#f73f35` | `{color.primitive.red.850}` | Destructive button text at rest (outline style).; use |
| `color.action.destructive.border` | `#e53935` | `{color.primitive.red.800}` | Destructive button border.; use |
| `color.action.destructive.hover-bg` | `#dc282e` | `{color.primitive.red.750}` | Destructive button hover fill.; use |
| `color.action.destructive.hover-text` | `#f9faf9` | `{color.primitive.paper.100}` | Destructive button text on hover (4.58:1).; use |
| `color.action.destructive.pressed-bg` | `#ae1914` | `{color.primitive.red.550}` | Destructive button pressed fill.; use |
| `color.action.destructive.filled-bg` | `#dc282e` | `{color.primitive.red.750}` | Confirm-dialog destructive fill.; use |
| `color.action.destructive.filled-text` | `#f9faf9` | `{color.primitive.paper.100}` | Text on the destructive fill.; use |
| `color.status.danger.text` | `#f73f35` | `{color.primitive.red.850}` | Danger text (5.40:1).; use |
| `color.status.danger.fill` | `#dc282e` | `{color.primitive.red.750}` | Danger fill.; use |
| `color.status.danger.on-fill` | `#f9faf9` | `{color.primitive.paper.100}` | Text on the danger fill (4.58:1).; use |
| `color.status.danger.tint` | `#2b0e0d` | `{color.primitive.ink.60}` | Danger background tint.; use |
| `color.status.danger.border` | `#e53935` | `{color.primitive.red.800}` | Danger border, 2px on invalid inputs.; use |
| `color.status.warning.text` | `#c9973f` | `{color.primitive.amber.500}` | Warning text (7.54:1).; use |
| `color.status.warning.fill` | `#c9973f` | `{color.primitive.amber.500}` | Warning fill.; use |
| `color.status.warning.on-fill` | `#0c0909` | `{color.primitive.ink.20}` | Text on the warning fill (7.54:1).; use |
| `color.status.warning.tint` | `#1f1a0c` | `{color.primitive.amber.tint}` | Warning background tint.; use |
| `color.status.warning.border` | `#c9973f` | `{color.primitive.amber.500}` | Warning border.; use |
| `color.status.success.text` | `#86a46f` | `{color.primitive.green.500}` | Success text (7.13:1).; use |
| `color.status.success.fill` | `#86a46f` | `{color.primitive.green.500}` | Success fill.; use |
| `color.status.success.on-fill` | `#0c0909` | `{color.primitive.ink.20}` | Text on the success fill (7.13:1).; use |
| `color.status.success.tint` | `#0f1a0e` | `{color.primitive.green.tint}` | Success background tint.; use |
| `color.status.success.border` | `#86a46f` | `{color.primitive.green.500}` | Success border.; use |
| `color.status.info.text` | `#7e9ebb` | `{color.primitive.blue.500}` | Info text (7.08:1).; use |
| `color.status.info.fill` | `#7e9ebb` | `{color.primitive.blue.500}` | Info fill.; use |
| `color.status.info.on-fill` | `#0c0909` | `{color.primitive.ink.20}` | Text on the info fill (7.08:1).; use |
| `color.status.info.tint` | `#0f141c` | `{color.primitive.blue.tint}` | Info background tint.; use |
| `color.status.info.border` | `#7e9ebb` | `{color.primitive.blue.500}` | Info border.; use |
| `color.status.neutral.text` | `#bd787d` | `{color.primitive.rose.600}` | Neutral status text.; use |
| `color.status.neutral.fill` | `#a3676b` | `{color.primitive.rose.350}` | Neutral fill (dunst urgency_low).; use |
| `color.status.neutral.on-fill` | `#000000` | `{color.primitive.ink.0}` | Text on the neutral fill (4.71:1).; use |
| `color.status.neutral.tint` | `#241010` | `{color.primitive.ink.52}` | Neutral background tint.; use |
| `color.status.neutral.border` | `#a3676b` | `{color.primitive.rose.350}` | Neutral border.; use |
| `color.code.bg` | `#0c0909` | `{color.primitive.ink.20}` | Editor background.; use |
| `color.code.current-line` | `#1c0a09` | `{color.primitive.ink.40}` | Current line background.; use |
| `color.code.gutter-bg` | `#0c0909` | `{color.primitive.ink.20}` | Gutter background.; use |
| `color.code.line-number` | `#ad7175` | `{color.primitive.rose.450}` | Line numbers (5.10:1; replaces the site's #7d1310).; use |
| `color.code.line-number-active` | `#e99499` | `{color.primitive.rose.700}` | Line number of the current line.; use |
| `color.code.gutter-rule` | `#2b0e0d` | `{color.primitive.ink.60}` | Rule between gutter and text.; use |
| `color.code.caret` | `#e99499` | `{color.primitive.rose.700}` | Caret (Xresources *cursorColor).; use |
| `color.code.selection-bg` | `#420f0c` | `{color.primitive.red.50}` | Editor selection background (muted text stays 4.71:1).; use |
| `color.code.search-match-bg` | `#630f0d` | `{color.primitive.red.150}` | Other search matches.; use |
| `color.code.search-current-bg` | `#ffa2a7` | `{color.primitive.rose.800}` | Current search match background.; use |
| `color.code.search-current-text` | `#0c0909` | `{color.primitive.ink.20}` | Current search match text.; use |
| `color.code.bracket-match` | `#e53935` | `{color.primitive.red.800}` | Matching bracket 1px border.; use |
| `color.code.indent-guide` | `#531310` | `{color.primitive.red.100}` | Indent guides.; use |
| `color.code.whitespace` | `#7d1310` | `{color.primitive.red.200}` | Rendered whitespace (decorative).; use |
| `color.code.syntax.keyword` | `#f73f35` | `{color.primitive.red.850}` | Keywords (Xresources color5 lineage).; use |
| `color.code.syntax.string` | `#bd787d` | `{color.primitive.rose.600}` | Strings (color2 lineage).; use |
| `color.code.syntax.comment` | `#ad7175` | `{color.primitive.rose.450}` | Comments, italic (color8 lineage, corrected for contrast).; use |
| `color.code.syntax.number` | `#d4868b` | `{color.primitive.rose.650}` | Numbers.; use |
| `color.code.syntax.constant` | `#d4868b` | `{color.primitive.rose.650}` | Constants and booleans.; use |
| `color.code.syntax.function` | `#ffa2a7` | `{color.primitive.rose.800}` | Function names and calls (color7 lineage).; use |
| `color.code.syntax.variable` | `#e99499` | `{color.primitive.rose.700}` | Variables and default text.; use |
| `color.code.syntax.type` | `#b37175` | `{color.primitive.rose.550}` | Types.; use |
| `color.code.syntax.operator` | `#e99499` | `{color.primitive.rose.700}` | Operators.; use |
| `color.code.syntax.punctuation` | `#e99499` | `{color.primitive.rose.700}` | Punctuation.; use |
| `color.code.syntax.property` | `#e53935` | `{color.primitive.red.800}` | Properties and keys (site .syntax-key).; use |
| `color.code.syntax.heading` | `#e53935` | `{color.primitive.red.800}` | Markup headings (site .syntax-heading).; use |
| `color.code.syntax.tag` | `#f73f35` | `{color.primitive.red.850}` | Markup tags.; use |
| `color.code.syntax.attribute` | `#d4868b` | `{color.primitive.rose.650}` | Markup attributes.; use |
| `color.code.syntax.escape` | `#d4868b` | `{color.primitive.rose.650}` | Escape sequences and regex, bold.; use |
| `color.code.syntax.invalid` | `#e53935` | `{color.primitive.red.800}` | Invalid text: wavy underline.; use |
| `color.code.syntax.deprecated` | `#ad7175` | `{color.primitive.rose.450}` | Deprecated symbol: line-through.; use |
| `color.diagnostic.error.underline` | `#e53935` | `{color.primitive.red.800}` | Error wavy underline.; use |
| `color.diagnostic.error.text` | `#f73f35` | `{color.primitive.red.850}` | Error message text.; use |
| `color.diagnostic.error.stripe` | `#e53935` | `{color.primitive.red.800}` | Error stripe mark.; use |
| `color.diagnostic.error.bg` | `#2b0e0d` | `{color.primitive.ink.60}` | Error line background.; use |
| `color.diagnostic.warning.underline` | `#c9973f` | `{color.primitive.amber.500}` | Warning wavy underline.; use |
| `color.diagnostic.warning.text` | `#c9973f` | `{color.primitive.amber.500}` | Warning message text.; use |
| `color.diagnostic.warning.stripe` | `#c9973f` | `{color.primitive.amber.500}` | Warning stripe mark.; use |
| `color.diagnostic.info.underline` | `#7e9ebb` | `{color.primitive.blue.500}` | Info dotted underline.; use |
| `color.diagnostic.info.text` | `#7e9ebb` | `{color.primitive.blue.500}` | Info message text.; use |
| `color.diagnostic.hint.underline` | `#ad7175` | `{color.primitive.rose.450}` | Hint dotted underline.; use |
| `color.diagnostic.unused.text` | `#bd787d` | `{color.primitive.rose.600}` | Unused symbol, dotted underline.; use |
| `color.diff.added.bg` | `#0f1a0e` | `{color.primitive.green.tint}` | Added line background.; use |
| `color.diff.added.gutter` | `#86a46f` | `{color.primitive.green.500}` | Added gutter mark and +.; use |
| `color.diff.added.emphasis` | `#86a46f` | `{color.primitive.green.500}` | Word-level addition: 2px underline.; use |
| `color.diff.removed.bg` | `#2b0e0d` | `{color.primitive.ink.60}` | Removed line background.; use |
| `color.diff.removed.gutter` | `#e53935` | `{color.primitive.red.800}` | Removed gutter mark and −.; use |
| `color.diff.removed.emphasis` | `#e53935` | `{color.primitive.red.800}` | Word-level removal: underline and line-through.; use |
| `color.diff.modified.bg` | `#1f1a0c` | `{color.primitive.amber.tint}` | Modified line background.; use |
| `color.diff.modified.gutter` | `#c9973f` | `{color.primitive.amber.500}` | Modified gutter mark and ~.; use |
| `color.diff.conflict-border` | `#f73f35` | `{color.primitive.red.850}` | Conflict block: double 1px border.; use |
| `color.diff.header-bg` | `#241010` | `{color.primitive.ink.52}` | Hunk header background.; use |
| `color.terminal.bg` | `#0c0909` | `{color.primitive.ink.20}` | Terminal background.; use |
| `color.terminal.fg` | `#e99499` | `{color.primitive.rose.700}` | Terminal foreground.; use |
| `color.terminal.cursor` | `#e99499` | `{color.primitive.rose.700}` | Terminal cursor.; use |
| `color.terminal.selection-bg` | `#911410` | `{color.primitive.red.350}` | Terminal selection.; use |
| `color.terminal.selection-text` | `#f4eeee` | `{color.primitive.paper.90}` | Terminal selection text.; use |
| `color.terminal.ansi.0` | `#0c0909` | `{color.primitive.ansi.0}` | ANSI slot 0, exact heritage value.; use |
| `color.terminal.ansi.1` | `#c81a1a` | `{color.primitive.ansi.1}` | ANSI slot 1, exact heritage value.; use |
| `color.terminal.ansi.2` | `#bd787d` | `{color.primitive.ansi.2}` | ANSI slot 2, exact heritage value.; use |
| `color.terminal.ansi.3` | `#d4868b` | `{color.primitive.ansi.3}` | ANSI slot 3, exact heritage value.; use |
| `color.terminal.ansi.4` | `#8c1212` | `{color.primitive.ansi.4}` | ANSI slot 4, exact heritage value.; use |
| `color.terminal.ansi.5` | `#f73f35` | `{color.primitive.ansi.5}` | ANSI slot 5, exact heritage value.; use |
| `color.terminal.ansi.6` | `#9e474a` | `{color.primitive.ansi.6}` | ANSI slot 6, exact heritage value.; use |
| `color.terminal.ansi.7` | `#ffa2a7` | `{color.primitive.ansi.7}` | ANSI slot 7, exact heritage value.; use |
| `color.terminal.ansi.8` | `#7d1310` | `{color.primitive.ansi.8}` | ANSI slot 8, exact heritage value.; use |
| `color.terminal.ansi.9` | `#ab1612` | `{color.primitive.ansi.9}` | ANSI slot 9, exact heritage value.; use |
| `color.terminal.ansi.10` | `#ad2721` | `{color.primitive.ansi.10}` | ANSI slot 10, exact heritage value.; use |
| `color.terminal.ansi.11` | `#b37175` | `{color.primitive.ansi.11}` | ANSI slot 11, exact heritage value.; use |
| `color.terminal.ansi.12` | `#871f19` | `{color.primitive.ansi.12}` | ANSI slot 12, exact heritage value.; use |
| `color.terminal.ansi.13` | `#e82132` | `{color.primitive.ansi.13}` | ANSI slot 13, exact heritage value.; use |
| `color.terminal.ansi.14` | `#e0292f` | `{color.primitive.ansi.14}` | ANSI slot 14, exact heritage value.; use |
| `color.terminal.ansi.15` | `#a3676b` | `{color.primitive.ansi.15}` | ANSI slot 15, exact heritage value.; use |
| `color.chart.series-1` | `#ffa2a7` | `{color.primitive.rose.800}` | First series.; use |
| `color.chart.series-2` | `#f73f35` | `{color.primitive.red.850}` | Second series.; use |
| `color.chart.series-3` | `#bd787d` | `{color.primitive.rose.600}` | Third series.; use |
| `color.chart.series-4` | `#a3676b` | `{color.primitive.rose.350}` | Fourth series (non-text, 4.45:1).; use |
| `color.chart.axis` | `#9e231f` | `{color.primitive.red.400}` | Axis lines.; use |
| `color.chart.grid` | `#2b0e0d` | `{color.primitive.ink.60}` | Grid lines.; use |
| `color.chart.label` | `#bd787d` | `{color.primitive.rose.600}` | Axis labels.; use |
| `color.icon.default` | `#e99499` | `{color.primitive.rose.700}` | Icons take the text colour (currentColor in CSS).; use |
| `color.icon.decorative` | `#a3676b` | `{color.primitive.rose.350}` | Decorative glyphs (3:1 as graphics, never text).; use |

### Roles pending an owner decision in `default`

| Role | Value | Alias of | Use |
| --- | --- | --- | --- |
| `color.surface.backdrop` | `rgb(0 0 0 / 65%)` | `{color.primitive.alpha.black-65}` | Behind modal dialogs.; use-and-report (D-008) |
| `color.border.divider` | `#2b0e0d` | `{color.primitive.ink.60}` | Hairline between rows and sections.; use-and-report (D-008) |
| `color.border.control` | `#a3676b` | `{color.primitive.rose.350}` | Form-control boundary (4.45:1, meets the 3:1 non-text minimum).; use-and-report (D-007) |
| `color.border.overlay` | `#e53935` | `{color.primitive.red.800}` | Every floating layer's 1px border.; use-and-report (D-008) |

### Foundations

| Token | Value | Use |
| --- | --- | --- |
| `space.0` | `0px` |  |
| `space.1` | `1px` |  |
| `space.2` | `2px` |  |
| `space.4` | `4px` |  |
| `space.8` | `8px` |  |
| `space.12` | `12px` |  |
| `space.16` | `16px` |  |
| `space.24` | `24px` |  |
| `space.32` | `32px` |  |
| `space.48` | `48px` |  |
| `border.width.default` | `1px` | Every border is 1px. |
| `border.width.emphasis` | `2px` | Selected indicator, sticky header rule, invalid border. |
| `radius.none` | `0px` | The only radius. Nothing is rounded. |
| `font.family.mono` | `"SauceCodePro NFM", "Source Code Pro", "Cascadia Mono", "Consolas", "Liberation Mono", "DejaVu Sans Mono", monospace` | One family for chrome, reading and code. Not bundled; see theme.json font. |
| `font.weight.regular` | `400` |  |
| `font.weight.bold` | `700` |  |
| `font.size.ui-sm` | `12px` |  |
| `font.size.ui-md` | `13px` |  |
| `font.size.ui-lg` | `14px` |  |
| `font.size.reading` | `15px` |  |
| `font.size.code` | `13px` |  |
| `font.size.terminal` | `13px` | URxvt pixelsize 13. |
| `font.size.h1` | `20px` |  |
| `font.size.h2` | `16px` |  |
| `font.size.h3` | `13px` |  |
| `font.size.caption` | `11px` | Only with text.muted or lighter. |
| `font.line-height.ui-sm` | `16px` |  |
| `font.line-height.ui-md` | `18px` |  |
| `font.line-height.ui-lg` | `20px` |  |
| `font.line-height.reading` | `24px` |  |
| `font.line-height.code` | `19px` |  |
| `font.line-height.terminal` | `19px` |  |
| `font.line-height.h1` | `28px` |  |
| `font.line-height.h2` | `24px` |  |
| `font.line-height.h3` | `18px` |  |
| `font.line-height.caption` | `16px` |  |
| `font.letter-spacing.terminal` | `-0.5px` | URxvt letterSpace -1 at pixelsize 13, as the site renders it. |
| `density.compact.control-height` | `24px` |  |
| `density.compact.row-height` | `28px` |  |
| `density.compact.control-padding-x` | `8px` |  |
| `density.compact.icon` | `12px` |  |
| `density.compact.gap` | `2px` |  |
| `density.comfortable.control-height` | `32px` |  |
| `density.comfortable.row-height` | `36px` |  |
| `density.comfortable.control-padding-x` | `12px` |  |
| `density.comfortable.icon` | `16px` |  |
| `density.comfortable.gap` | `4px` |  |
| `focus.ring` | `1px dashed #e53935` | outline on controls and rows. |
| `focus.ring-container` | `2px solid #ffa2a7` | outline on focusable containers. |
| `focus.offset` | `-2px` | outline-offset for controls. |
| `focus.offset-container` | `-3px` | outline-offset for containers. |
| `focus.offset-invalid` | `-4px` | outline-offset inside the 2px invalid border. |
| `motion.duration.fast` | `80ms` |  |
| `motion.duration.base` | `120ms` |  |
| `motion.duration.slow` | `150ms` | The maximum. Reduced motion sets every duration to 0.01ms. |
| `shadow.floating` | `0px 6px 22px 0px rgb(0 0 0 / 55%)` | The only shadow: the i3 floating window. |
| `layout.measure` | `72` | Reading measure in ch. |
| `layout.content-max` | `680px` | Reading column maximum. |
| `layout.app-max` | `1440px` | Application layout maximum. |
| `breakpoint.xs` | `320px` | Reflow floor (WCAG 1.4.10). |
| `breakpoint.sm` | `600px` |  |
| `breakpoint.md` | `900px` |  |
| `breakpoint.lg` | `1280px` | The site's bar breaks below this. |
| `icon.size.sm` | `12px` |  |
| `icon.size.md` | `16px` |  |
| `icon.size.lg` | `20px` |  |
| `icon.stroke` | `1.5px` | SVG line icons, currentColor, no fill. |
| `z.canvas` | `0` |  |
| `z.raised` | `1` |  |
| `z.popover` | `10` |  |
| `z.drawer` | `20` |  |
| `z.dialog` | `30` |  |
| `z.toast` | `40` |  |
| `z.skip-link` | `1000` |  |

## Tokens — profile `heritage-ansi` (heritage)

| Role | Value | Alias of | Use |
| --- | --- | --- | --- |
| `color.surface.desktop` | `#000000` | `{color.primitive.ink.0}` | Behind every window.; historical-only |
| `color.surface.canvas` | `#0c0909` | `{color.primitive.ink.20}` | Historical surface assignment retained across D-023.; historical-only |
| `color.surface.chrome` | `#100909` | `{color.primitive.ink.25}` | Historical surface assignment retained across D-023.; historical-only |
| `color.surface.chrome-alt` | `#1c0a09` | `{color.primitive.ink.40}` | Alternate chrome.; historical-only |
| `color.surface.sunken` | `#0a0707` | `{color.primitive.ink.15}` | Historical surface assignment retained across D-023.; historical-only |
| `color.surface.default` | `#160b0b` | `{color.primitive.ink.30}` | Historical surface assignment retained across D-023.; historical-only |
| `color.surface.raised` | `#160b0b` | `{color.primitive.ink.30}` | Sticky headers, menus, popovers; D-027 recessed it so an open list reads as depth rather than a lighter panel.; historical-only |
| `color.surface.overlay` | `#241010` | `{color.primitive.ink.52}` | Dialogs and drawers.; historical-only |
| `color.surface.input` | `#0c0909` | `{color.primitive.ink.20}` | Historical surface assignment retained across D-023.; historical-only |
| `color.surface.code` | `#0c0909` | `{color.primitive.ink.20}` | Historical surface assignment retained across D-023.; historical-only |
| `color.surface.terminal` | `#0c0909` | `{color.primitive.ink.20}` | Historical surface assignment retained across D-023.; historical-only |
| `color.text.default` | `#e99499` | `{color.primitive.rose.700}` | Interface and body text.; historical-only |
| `color.text.bright` | `#ffa2a7` | `{color.primitive.rose.800}` | Headings and emphasis. Links use color.text.link.; historical-only |
| `color.text.prose` | `#f4eeee` | `{color.primitive.paper.90}` | Long-form reading text.; historical-only |
| `color.text.muted` | `#bd787d` | `{color.primitive.rose.600}` | Secondary text; the darkest colour permitted for chrome text.; historical-only |
| `color.text.subtle` | `#a3676b` | `{color.primitive.rose.350}` | color15 as the site's --quiet.; historical-only |
| `color.text.disabled` | `#8a5559` | `{color.primitive.rose.300}` | Disabled control text.; historical-only |
| `color.text.placeholder` | `#a3676b` | `{color.primitive.rose.350}` | Placeholder text, italic; D-027 darkened it to 4.71:1, just clear of the 4.5:1 floor.; historical-only |
| `color.text.accent` | `#e53935` | `{color.primitive.red.800}` | Accent text on canvas and default surfaces only.; historical-only |
| `color.text.accent-strong` | `#f73f35` | `{color.primitive.red.850}` | Accent text safe on every surface.; historical-only |
| `color.text.link` | `#ffa2a7` | `{color.primitive.rose.800}` | Historical bright-rose link assignment before D-024.; historical-only |
| `color.text.link-underline` | `#dc282e` | `{color.primitive.red.750}` | Link underline.; historical-only |
| `color.text.link-hover` | `#f4eeee` | `{color.primitive.paper.90}` | Link text on hover, over interaction.hover.bg-strong.; historical-only |
| `color.text.on-selection` | `#f4eeee` | `{color.primitive.paper.90}` | Text on the selection fill.; historical-only |
| `color.text.on-action` | `#f4eeee` | `{color.primitive.paper.90}` | Text on the primary action fill.; historical-only |
| `color.text.on-danger` | `#f9faf9` | `{color.primitive.paper.100}` | Text on the danger fill.; historical-only |
| `color.text.on-light` | `#0c0909` | `{color.primitive.ink.20}` | Text on light fills: bright rose, amber, green, blue.; historical-only |
| `color.text.inverse` | `#0c0909` | `{color.primitive.ink.20}` | Text on inverted surfaces.; historical-only |
| `color.border.default` | `#531310` | `{color.primitive.red.100}` | Decorative border (1.39:1; never the only edge of a control).; historical-only |
| `color.border.strong` | `#9e231f` | `{color.primitive.red.400}` | Emphasised 2px rule: sticky headers, section ends.; historical-only |
| `color.border.active` | `#e53935` | `{color.primitive.red.800}` | Active or focused control border.; historical-only |
| `color.border.overlay` | `#ae1914` | `{color.primitive.red.550}` | dunst frame_color.; historical-only |
| `color.border.disabled` | `#7d1310` | `{color.primitive.red.200}` | Disabled control border (exempt).; historical-only |
| `color.border.selected-indicator` | `#e53935` | `{color.primitive.red.800}` | 2px indicator bar on the selected tab or nav item.; historical-only |
| `color.border.selected-indicator-inactive` | `#9e231f` | `{color.primitive.red.400}` | Indicator bar when the container is not focused.; historical-only |
| `color.interaction.focus.ring` | `#e53935` | `{color.primitive.red.800}` | 1px dashed focus ring on controls and rows.; historical-only |
| `color.interaction.focus.ring-container` | `#ffa2a7` | `{color.primitive.rose.800}` | Focus colour for containers and explicitly mapped filled controls (D-015); component rules determine ring geometry.; historical-only |
| `color.interaction.selection.bg` | `#531310` | `{color.primitive.red.100}` | Selected row, tab, workspace; D-027 recessed the fill, leaving the 2px indicator and the check to carry selection.; historical-only |
| `color.interaction.selection.text` | `#f4eeee` | `{color.primitive.paper.90}` | Text on the selection.; historical-only |
| `color.interaction.selection.inactive-bg` | `#420f0c` | `{color.primitive.red.50}` | Selection while the container is not focused.; historical-only |
| `color.interaction.selection.inactive-text` | `#e99499` | `{color.primitive.rose.700}` | Text on the inactive selection (7.02:1).; historical-only |
| `color.interaction.text-selection.bg` | `#911410` | `{color.primitive.red.350}` | ::selection background.; historical-only |
| `color.interaction.text-selection.text` | `#f4eeee` | `{color.primitive.paper.90}` | ::selection text.; historical-only |
| `color.interaction.hover.bg` | `#1c0a09` | `{color.primitive.ink.40}` | Hover on rows, ghost buttons, list items.; historical-only |
| `color.interaction.hover.bg-strong` | `#630f0d` | `{color.primitive.red.150}` | Hover on menu items; D-027 moved links to interaction.hover.bg.; historical-only |
| `color.interaction.pressed.bg` | `#420f0c` | `{color.primitive.red.50}` | Pressed fill.; historical-only |
| `color.interaction.disabled.bg` | `#160b0b` | `{color.primitive.ink.30}` | Disabled control fill.; historical-only |
| `color.interaction.marquee` | `rgb(145 20 16 / 12%)` | `{color.primitive.alpha.red-12}` | Drag marquee and ghost.; historical-only |
| `color.interaction.drop-target` | `#e53935` | `{color.primitive.red.800}` | Dashed drop-target border.; historical-only |
| `color.interaction.scrollbar.track` | `#0c0909` | `{color.primitive.ink.20}` | Scrollbar track.; historical-only |
| `color.interaction.scrollbar.thumb` | `#420f0c` | `{color.primitive.red.50}` | Scrollbar thumb.; historical-only |
| `color.interaction.scrollbar.thumb-hover` | `#911410` | `{color.primitive.red.350}` | Scrollbar thumb on hover.; historical-only |
| `color.action.primary.bg` | `#7d1310` | `{color.primitive.red.200}` | Primary button fill; D-026 darkened it off color12, which stays fixed as heritage ANSI 12.; historical-only |
| `color.action.primary.text` | `#f4eeee` | `{color.primitive.paper.90}` | Primary button text (9.28:1).; historical-only |
| `color.action.primary.hover-bg` | `#911410` | `{color.primitive.red.350}` | Primary button hover (7.92:1 with prose).; historical-only |
| `color.action.primary.pressed-bg` | `#630f0d` | `{color.primitive.red.150}` | Primary button pressed.; historical-only |
| `color.action.primary.border` | `#9e231f` | `{color.primitive.red.400}` | Primary button border.; historical-only |
| `color.action.secondary.bg` | `rgb(0 0 0 / 0%)` | `{color.primitive.alpha.transparent}` | Secondary button has no fill.; historical-only |
| `color.action.secondary.text` | `#ffa2a7` | `{color.primitive.rose.800}` | Secondary button text.; historical-only |
| `color.action.secondary.border` | `#a3676b` | `{color.primitive.rose.350}` | Secondary button border (meets 3:1).; historical-only |
| `color.action.secondary.hover-bg` | `#630f0d` | `{color.primitive.red.150}` | Secondary button hover.; historical-only |
| `color.action.secondary.pressed-bg` | `#420f0c` | `{color.primitive.red.50}` | Secondary button pressed.; historical-only |
| `color.action.tertiary.text` | `#e99499` | `{color.primitive.rose.700}` | Ghost and icon button text.; historical-only |
| `color.action.tertiary.hover-bg` | `#1c0a09` | `{color.primitive.ink.40}` | Ghost and icon button hover.; historical-only |
| `color.action.destructive.text` | `#f73f35` | `{color.primitive.red.850}` | Destructive button text at rest (outline style).; historical-only |
| `color.action.destructive.border` | `#e53935` | `{color.primitive.red.800}` | Destructive button border.; historical-only |
| `color.action.destructive.hover-bg` | `#dc282e` | `{color.primitive.red.750}` | Destructive button hover fill.; historical-only |
| `color.action.destructive.hover-text` | `#f9faf9` | `{color.primitive.paper.100}` | Destructive button text on hover (4.58:1).; historical-only |
| `color.action.destructive.pressed-bg` | `#ae1914` | `{color.primitive.red.550}` | Destructive button pressed fill.; historical-only |
| `color.action.destructive.filled-bg` | `#dc282e` | `{color.primitive.red.750}` | Confirm-dialog destructive fill.; historical-only |
| `color.action.destructive.filled-text` | `#f9faf9` | `{color.primitive.paper.100}` | Text on the destructive fill.; historical-only |
| `color.status.danger.text` | `#f73f35` | `{color.primitive.red.850}` | Danger text (5.40:1).; historical-only |
| `color.status.danger.fill` | `#dc282e` | `{color.primitive.red.750}` | Danger fill.; historical-only |
| `color.status.danger.on-fill` | `#f9faf9` | `{color.primitive.paper.100}` | Text on the danger fill (4.58:1).; historical-only |
| `color.status.danger.tint` | `#2b0e0d` | `{color.primitive.ink.60}` | Danger background tint.; historical-only |
| `color.status.danger.border` | `#e53935` | `{color.primitive.red.800}` | Danger border, 2px on invalid inputs.; historical-only |
| `color.status.warning.text` | `#c9973f` | `{color.primitive.amber.500}` | Warning text (7.54:1).; historical-only |
| `color.status.warning.fill` | `#c9973f` | `{color.primitive.amber.500}` | Warning fill.; historical-only |
| `color.status.warning.on-fill` | `#0c0909` | `{color.primitive.ink.20}` | Text on the warning fill (7.54:1).; historical-only |
| `color.status.warning.tint` | `#1f1a0c` | `{color.primitive.amber.tint}` | Warning background tint.; historical-only |
| `color.status.warning.border` | `#c9973f` | `{color.primitive.amber.500}` | Warning border.; historical-only |
| `color.status.success.text` | `#86a46f` | `{color.primitive.green.500}` | Success text (7.13:1).; historical-only |
| `color.status.success.fill` | `#86a46f` | `{color.primitive.green.500}` | Success fill.; historical-only |
| `color.status.success.on-fill` | `#0c0909` | `{color.primitive.ink.20}` | Text on the success fill (7.13:1).; historical-only |
| `color.status.success.tint` | `#0f1a0e` | `{color.primitive.green.tint}` | Success background tint.; historical-only |
| `color.status.success.border` | `#86a46f` | `{color.primitive.green.500}` | Success border.; historical-only |
| `color.status.info.text` | `#7e9ebb` | `{color.primitive.blue.500}` | Info text (7.08:1).; historical-only |
| `color.status.info.fill` | `#7e9ebb` | `{color.primitive.blue.500}` | Info fill.; historical-only |
| `color.status.info.on-fill` | `#0c0909` | `{color.primitive.ink.20}` | Text on the info fill (7.08:1).; historical-only |
| `color.status.info.tint` | `#0f141c` | `{color.primitive.blue.tint}` | Info background tint.; historical-only |
| `color.status.info.border` | `#7e9ebb` | `{color.primitive.blue.500}` | Info border.; historical-only |
| `color.status.neutral.text` | `#bd787d` | `{color.primitive.rose.600}` | Neutral status text.; historical-only |
| `color.status.neutral.fill` | `#a3676b` | `{color.primitive.rose.350}` | dunst urgency_low background.; historical-only |
| `color.status.neutral.on-fill` | `#0c0909` | `{color.primitive.ink.20}` | dunst urgency_low foreground.; historical-only |
| `color.status.neutral.tint` | `#241010` | `{color.primitive.ink.52}` | Neutral background tint.; historical-only |
| `color.status.neutral.border` | `#a3676b` | `{color.primitive.rose.350}` | Neutral border.; historical-only |
| `color.code.bg` | `#0c0909` | `{color.primitive.ink.20}` | Editor background.; historical-only |
| `color.code.current-line` | `#1c0a09` | `{color.primitive.ink.40}` | Current line background.; historical-only |
| `color.code.gutter-bg` | `#0c0909` | `{color.primitive.ink.20}` | Gutter background.; historical-only |
| `color.code.line-number` | `#7d1310` | `{color.primitive.red.200}` | color8, as vim rendered line numbers.; historical-only |
| `color.code.line-number-active` | `#e99499` | `{color.primitive.rose.700}` | Line number of the current line.; historical-only |
| `color.code.gutter-rule` | `#2b0e0d` | `{color.primitive.ink.60}` | Rule between gutter and text.; historical-only |
| `color.code.caret` | `#e99499` | `{color.primitive.rose.700}` | Caret (Xresources *cursorColor).; historical-only |
| `color.code.selection-bg` | `#420f0c` | `{color.primitive.red.50}` | Editor selection background (muted text stays 4.71:1).; historical-only |
| `color.code.search-match-bg` | `#630f0d` | `{color.primitive.red.150}` | Other search matches.; historical-only |
| `color.code.search-current-bg` | `#ffa2a7` | `{color.primitive.rose.800}` | Current search match background.; historical-only |
| `color.code.search-current-text` | `#0c0909` | `{color.primitive.ink.20}` | Current search match text.; historical-only |
| `color.code.bracket-match` | `#e53935` | `{color.primitive.red.800}` | Matching bracket 1px border.; historical-only |
| `color.code.indent-guide` | `#531310` | `{color.primitive.red.100}` | Indent guides.; historical-only |
| `color.code.whitespace` | `#7d1310` | `{color.primitive.red.200}` | Rendered whitespace (decorative).; historical-only |
| `color.code.syntax.keyword` | `#f73f35` | `{color.primitive.red.850}` | Keywords (Xresources color5 lineage).; historical-only |
| `color.code.syntax.string` | `#bd787d` | `{color.primitive.rose.600}` | Strings (color2 lineage).; historical-only |
| `color.code.syntax.comment` | `#7d1310` | `{color.primitive.red.200}` | color8: comments and shebang in vim.; historical-only |
| `color.code.syntax.number` | `#d4868b` | `{color.primitive.rose.650}` | Numbers.; historical-only |
| `color.code.syntax.constant` | `#d4868b` | `{color.primitive.rose.650}` | Constants and booleans.; historical-only |
| `color.code.syntax.function` | `#ffa2a7` | `{color.primitive.rose.800}` | color7: function calls, quotes, operators.; historical-only |
| `color.code.syntax.variable` | `#ab1612` | `{color.primitive.red.450}` | color9: declarations and assignments.; historical-only |
| `color.code.syntax.type` | `#b37175` | `{color.primitive.rose.550}` | Types.; historical-only |
| `color.code.syntax.operator` | `#ffa2a7` | `{color.primitive.rose.800}` | color7.; historical-only |
| `color.code.syntax.punctuation` | `#d4868b` | `{color.primitive.rose.650}` | color3: parentheses, symbols, commas.; historical-only |
| `color.code.syntax.property` | `#e53935` | `{color.primitive.red.800}` | Properties and keys (site .syntax-key).; historical-only |
| `color.code.syntax.heading` | `#e53935` | `{color.primitive.red.800}` | Markup headings (site .syntax-heading).; historical-only |
| `color.code.syntax.tag` | `#f73f35` | `{color.primitive.red.850}` | Markup tags.; historical-only |
| `color.code.syntax.attribute` | `#d4868b` | `{color.primitive.rose.650}` | Markup attributes.; historical-only |
| `color.code.syntax.escape` | `#d4868b` | `{color.primitive.rose.650}` | Escape sequences and regex, bold.; historical-only |
| `color.code.syntax.invalid` | `#e53935` | `{color.primitive.red.800}` | Invalid text: wavy underline.; historical-only |
| `color.code.syntax.deprecated` | `#ad7175` | `{color.primitive.rose.450}` | Deprecated symbol: line-through.; historical-only |
| `color.diagnostic.error.underline` | `#e53935` | `{color.primitive.red.800}` | Error wavy underline.; historical-only |
| `color.diagnostic.error.text` | `#f73f35` | `{color.primitive.red.850}` | Error message text.; historical-only |
| `color.diagnostic.error.stripe` | `#e53935` | `{color.primitive.red.800}` | Error stripe mark.; historical-only |
| `color.diagnostic.error.bg` | `#2b0e0d` | `{color.primitive.ink.60}` | Error line background.; historical-only |
| `color.diagnostic.warning.underline` | `#c9973f` | `{color.primitive.amber.500}` | Warning wavy underline.; historical-only |
| `color.diagnostic.warning.text` | `#c9973f` | `{color.primitive.amber.500}` | Warning message text.; historical-only |
| `color.diagnostic.warning.stripe` | `#c9973f` | `{color.primitive.amber.500}` | Warning stripe mark.; historical-only |
| `color.diagnostic.info.underline` | `#7e9ebb` | `{color.primitive.blue.500}` | Info dotted underline.; historical-only |
| `color.diagnostic.info.text` | `#7e9ebb` | `{color.primitive.blue.500}` | Info message text.; historical-only |
| `color.diagnostic.hint.underline` | `#ad7175` | `{color.primitive.rose.450}` | Hint dotted underline.; historical-only |
| `color.diagnostic.unused.text` | `#bd787d` | `{color.primitive.rose.600}` | Unused symbol, dotted underline.; historical-only |
| `color.diff.added.bg` | `#0f1a0e` | `{color.primitive.green.tint}` | Added line background.; historical-only |
| `color.diff.added.gutter` | `#86a46f` | `{color.primitive.green.500}` | Added gutter mark and +.; historical-only |
| `color.diff.added.emphasis` | `#86a46f` | `{color.primitive.green.500}` | Word-level addition: 2px underline.; historical-only |
| `color.diff.removed.bg` | `#2b0e0d` | `{color.primitive.ink.60}` | Removed line background.; historical-only |
| `color.diff.removed.gutter` | `#e53935` | `{color.primitive.red.800}` | Removed gutter mark and −.; historical-only |
| `color.diff.removed.emphasis` | `#e53935` | `{color.primitive.red.800}` | Word-level removal: underline and line-through.; historical-only |
| `color.diff.modified.bg` | `#1f1a0c` | `{color.primitive.amber.tint}` | Modified line background.; historical-only |
| `color.diff.modified.gutter` | `#c9973f` | `{color.primitive.amber.500}` | Modified gutter mark and ~.; historical-only |
| `color.diff.conflict-border` | `#f73f35` | `{color.primitive.red.850}` | Conflict block: double 1px border.; historical-only |
| `color.diff.header-bg` | `#241010` | `{color.primitive.ink.52}` | Hunk header background.; historical-only |
| `color.terminal.bg` | `#0c0909` | `{color.primitive.ink.20}` | Terminal background.; historical-only |
| `color.terminal.fg` | `#e99499` | `{color.primitive.rose.700}` | Terminal foreground.; historical-only |
| `color.terminal.cursor` | `#e99499` | `{color.primitive.rose.700}` | Terminal cursor.; historical-only |
| `color.terminal.selection-bg` | `#911410` | `{color.primitive.red.350}` | Terminal selection.; historical-only |
| `color.terminal.selection-text` | `#f4eeee` | `{color.primitive.paper.90}` | Terminal selection text.; historical-only |
| `color.terminal.ansi.0` | `#0c0909` | `{color.primitive.ansi.0}` | ANSI slot 0, exact heritage value.; historical-only |
| `color.terminal.ansi.1` | `#c81a1a` | `{color.primitive.ansi.1}` | ANSI slot 1, exact heritage value.; historical-only |
| `color.terminal.ansi.2` | `#bd787d` | `{color.primitive.ansi.2}` | ANSI slot 2, exact heritage value.; historical-only |
| `color.terminal.ansi.3` | `#d4868b` | `{color.primitive.ansi.3}` | ANSI slot 3, exact heritage value.; historical-only |
| `color.terminal.ansi.4` | `#8c1212` | `{color.primitive.ansi.4}` | ANSI slot 4, exact heritage value.; historical-only |
| `color.terminal.ansi.5` | `#f73f35` | `{color.primitive.ansi.5}` | ANSI slot 5, exact heritage value.; historical-only |
| `color.terminal.ansi.6` | `#9e474a` | `{color.primitive.ansi.6}` | ANSI slot 6, exact heritage value.; historical-only |
| `color.terminal.ansi.7` | `#ffa2a7` | `{color.primitive.ansi.7}` | ANSI slot 7, exact heritage value.; historical-only |
| `color.terminal.ansi.8` | `#7d1310` | `{color.primitive.ansi.8}` | ANSI slot 8, exact heritage value.; historical-only |
| `color.terminal.ansi.9` | `#ab1612` | `{color.primitive.ansi.9}` | ANSI slot 9, exact heritage value.; historical-only |
| `color.terminal.ansi.10` | `#ad2721` | `{color.primitive.ansi.10}` | ANSI slot 10, exact heritage value.; historical-only |
| `color.terminal.ansi.11` | `#b37175` | `{color.primitive.ansi.11}` | ANSI slot 11, exact heritage value.; historical-only |
| `color.terminal.ansi.12` | `#871f19` | `{color.primitive.ansi.12}` | ANSI slot 12, exact heritage value.; historical-only |
| `color.terminal.ansi.13` | `#e82132` | `{color.primitive.ansi.13}` | ANSI slot 13, exact heritage value.; historical-only |
| `color.terminal.ansi.14` | `#e0292f` | `{color.primitive.ansi.14}` | ANSI slot 14, exact heritage value.; historical-only |
| `color.terminal.ansi.15` | `#a3676b` | `{color.primitive.ansi.15}` | ANSI slot 15, exact heritage value.; historical-only |
| `color.chart.series-1` | `#ffa2a7` | `{color.primitive.rose.800}` | First series.; historical-only |
| `color.chart.series-2` | `#f73f35` | `{color.primitive.red.850}` | Second series.; historical-only |
| `color.chart.series-3` | `#bd787d` | `{color.primitive.rose.600}` | Third series.; historical-only |
| `color.chart.series-4` | `#a3676b` | `{color.primitive.rose.350}` | Fourth series (non-text, 4.45:1).; historical-only |
| `color.chart.axis` | `#9e231f` | `{color.primitive.red.400}` | Axis lines.; historical-only |
| `color.chart.grid` | `#2b0e0d` | `{color.primitive.ink.60}` | Grid lines.; historical-only |
| `color.chart.label` | `#bd787d` | `{color.primitive.rose.600}` | Axis labels.; historical-only |
| `color.icon.default` | `#e99499` | `{color.primitive.rose.700}` | Icons take the text colour (currentColor in CSS).; historical-only |
| `color.icon.decorative` | `#a3676b` | `{color.primitive.rose.350}` | Decorative glyphs (3:1 as graphics, never text).; historical-only |

### Roles pending an owner decision in `heritage-ansi`

| Role | Value | Alias of | Use |
| --- | --- | --- | --- |
| `color.surface.backdrop` | `rgb(0 0 0 / 65%)` | `{color.primitive.alpha.black-65}` | Behind modal dialogs.; historical-only (D-008) |
| `color.border.divider` | `#2b0e0d` | `{color.primitive.ink.60}` | Hairline between rows and sections.; historical-only (D-008) |
| `color.border.control` | `#a3676b` | `{color.primitive.rose.350}` | Form-control boundary (4.45:1, meets the 3:1 non-text minimum).; historical-only (D-007) |

## Tokens — profile `extended` (proposed)

| Role | Value | Alias of | Use |
| --- | --- | --- | --- |
| `color.surface.desktop` | `#000000` | `{color.primitive.ink.0}` | Behind every window.; blocked |
| `color.surface.canvas` | `#000000` | `{color.primitive.ink.0}` | Page and terminal background.; blocked |
| `color.surface.chrome` | `#090707` | `{color.primitive.ink.14}` | Window chrome, title bars, bars.; blocked |
| `color.surface.chrome-alt` | `#1c0a09` | `{color.primitive.ink.40}` | Alternate chrome.; blocked |
| `color.surface.sunken` | `#000000` | `{color.primitive.ink.0}` | Reading and detail panes set below the canvas.; blocked |
| `color.surface.default` | `#100c0c` | `{color.primitive.ink.28}` | Panels and cards.; blocked |
| `color.surface.raised` | `#160b0b` | `{color.primitive.ink.30}` | Sticky headers, menus, popovers; D-027 recessed it so an open list reads as depth rather than a lighter panel.; blocked |
| `color.surface.overlay` | `#241010` | `{color.primitive.ink.52}` | Dialogs and drawers.; blocked |
| `color.surface.input` | `#000000` | `{color.primitive.ink.0}` | Form-control fill.; blocked |
| `color.surface.code` | `#000000` | `{color.primitive.ink.0}` | Editor background.; blocked |
| `color.surface.terminal` | `#000000` | `{color.primitive.ink.0}` | Terminal background.; blocked |
| `color.text.default` | `#e99499` | `{color.primitive.rose.700}` | Interface and body text.; blocked |
| `color.text.bright` | `#ffa2a7` | `{color.primitive.rose.800}` | Headings and emphasis. Links use color.text.link.; blocked |
| `color.text.prose` | `#f4eeee` | `{color.primitive.paper.90}` | Long-form reading text.; blocked |
| `color.text.muted` | `#bd787d` | `{color.primitive.rose.600}` | Secondary text; the darkest colour permitted for chrome text.; blocked |
| `color.text.subtle` | `#ad7175` | `{color.primitive.rose.450}` | Metadata, comments, line numbers.; blocked |
| `color.text.disabled` | `#8a5559` | `{color.primitive.rose.300}` | Disabled control text.; blocked |
| `color.text.placeholder` | `#a3676b` | `{color.primitive.rose.350}` | Placeholder text, italic; D-027 darkened it to 4.71:1, just clear of the 4.5:1 floor.; blocked |
| `color.text.accent` | `#e53935` | `{color.primitive.red.800}` | Accent text on canvas and default surfaces only.; blocked |
| `color.text.accent-strong` | `#f73f35` | `{color.primitive.red.850}` | Accent text safe on every surface.; blocked |
| `color.text.link` | `#f73f35` | `{color.primitive.red.850}` | Strong red link text, distinct from rose body text and headings; retain the underline.; blocked |
| `color.text.link-underline` | `#dc282e` | `{color.primitive.red.750}` | Link underline.; blocked |
| `color.text.link-hover` | `#f4eeee` | `{color.primitive.paper.90}` | Link text on hover, over interaction.hover.bg-strong.; blocked |
| `color.text.on-selection` | `#f4eeee` | `{color.primitive.paper.90}` | Text on the selection fill.; blocked |
| `color.text.on-action` | `#f4eeee` | `{color.primitive.paper.90}` | Text on the primary action fill.; blocked |
| `color.text.on-danger` | `#f9faf9` | `{color.primitive.paper.100}` | Text on the danger fill.; blocked |
| `color.text.on-light` | `#0c0909` | `{color.primitive.ink.20}` | Text on light fills: bright rose, amber, green, blue.; blocked |
| `color.text.inverse` | `#0c0909` | `{color.primitive.ink.20}` | Text on inverted surfaces.; blocked |
| `color.border.default` | `#531310` | `{color.primitive.red.100}` | Decorative border (1.39:1; never the only edge of a control).; blocked |
| `color.border.strong` | `#9e231f` | `{color.primitive.red.400}` | Emphasised 2px rule: sticky headers, section ends.; blocked |
| `color.border.active` | `#e53935` | `{color.primitive.red.800}` | Active or focused control border.; blocked |
| `color.border.disabled` | `#7d1310` | `{color.primitive.red.200}` | Disabled control border (exempt).; blocked |
| `color.border.selected-indicator` | `#e53935` | `{color.primitive.red.800}` | 2px indicator bar on the selected tab or nav item.; blocked |
| `color.border.selected-indicator-inactive` | `#9e231f` | `{color.primitive.red.400}` | Indicator bar when the container is not focused.; blocked |
| `color.interaction.focus.ring` | `#e53935` | `{color.primitive.red.800}` | 1px dashed focus ring on controls and rows.; blocked |
| `color.interaction.focus.ring-container` | `#ffa2a7` | `{color.primitive.rose.800}` | Focus colour for containers and explicitly mapped filled controls (D-015); component rules determine ring geometry.; blocked |
| `color.interaction.selection.bg` | `#531310` | `{color.primitive.red.100}` | Selected row, tab, workspace; D-027 recessed the fill, leaving the 2px indicator and the check to carry selection.; blocked |
| `color.interaction.selection.text` | `#f4eeee` | `{color.primitive.paper.90}` | Text on the selection.; blocked |
| `color.interaction.selection.inactive-bg` | `#420f0c` | `{color.primitive.red.50}` | Selection while the container is not focused.; blocked |
| `color.interaction.selection.inactive-text` | `#e99499` | `{color.primitive.rose.700}` | Text on the inactive selection (7.02:1).; blocked |
| `color.interaction.text-selection.bg` | `#911410` | `{color.primitive.red.350}` | ::selection background.; blocked |
| `color.interaction.text-selection.text` | `#f4eeee` | `{color.primitive.paper.90}` | ::selection text.; blocked |
| `color.interaction.hover.bg` | `#1c0a09` | `{color.primitive.ink.40}` | Hover on rows, ghost buttons, list items.; blocked |
| `color.interaction.hover.bg-strong` | `#630f0d` | `{color.primitive.red.150}` | Hover on menu items; D-027 moved links to interaction.hover.bg.; blocked |
| `color.interaction.pressed.bg` | `#420f0c` | `{color.primitive.red.50}` | Pressed fill.; blocked |
| `color.interaction.disabled.bg` | `#160b0b` | `{color.primitive.ink.30}` | Disabled control fill.; blocked |
| `color.interaction.marquee` | `rgb(145 20 16 / 12%)` | `{color.primitive.alpha.red-12}` | Drag marquee and ghost.; blocked |
| `color.interaction.drop-target` | `#e53935` | `{color.primitive.red.800}` | Dashed drop-target border.; blocked |
| `color.interaction.scrollbar.track` | `#0c0909` | `{color.primitive.ink.20}` | Scrollbar track.; blocked |
| `color.interaction.scrollbar.thumb` | `#420f0c` | `{color.primitive.red.50}` | Scrollbar thumb.; blocked |
| `color.interaction.scrollbar.thumb-hover` | `#911410` | `{color.primitive.red.350}` | Scrollbar thumb on hover.; blocked |
| `color.action.primary.bg` | `#7d1310` | `{color.primitive.red.200}` | Primary button fill; D-026 darkened it off color12, which stays fixed as heritage ANSI 12.; blocked |
| `color.action.primary.text` | `#f4eeee` | `{color.primitive.paper.90}` | Primary button text (9.28:1).; blocked |
| `color.action.primary.hover-bg` | `#911410` | `{color.primitive.red.350}` | Primary button hover (7.92:1 with prose).; blocked |
| `color.action.primary.pressed-bg` | `#630f0d` | `{color.primitive.red.150}` | Primary button pressed.; blocked |
| `color.action.primary.border` | `#9e231f` | `{color.primitive.red.400}` | Primary button border.; blocked |
| `color.action.secondary.bg` | `rgb(0 0 0 / 0%)` | `{color.primitive.alpha.transparent}` | Secondary button has no fill.; blocked |
| `color.action.secondary.text` | `#ffa2a7` | `{color.primitive.rose.800}` | Secondary button text.; blocked |
| `color.action.secondary.border` | `#a3676b` | `{color.primitive.rose.350}` | Secondary button border (meets 3:1).; blocked |
| `color.action.secondary.hover-bg` | `#630f0d` | `{color.primitive.red.150}` | Secondary button hover.; blocked |
| `color.action.secondary.pressed-bg` | `#420f0c` | `{color.primitive.red.50}` | Secondary button pressed.; blocked |
| `color.action.tertiary.text` | `#e99499` | `{color.primitive.rose.700}` | Ghost and icon button text.; blocked |
| `color.action.tertiary.hover-bg` | `#1c0a09` | `{color.primitive.ink.40}` | Ghost and icon button hover.; blocked |
| `color.action.destructive.text` | `#f73f35` | `{color.primitive.red.850}` | Destructive button text at rest (outline style).; blocked |
| `color.action.destructive.border` | `#e53935` | `{color.primitive.red.800}` | Destructive button border.; blocked |
| `color.action.destructive.hover-bg` | `#dc282e` | `{color.primitive.red.750}` | Destructive button hover fill.; blocked |
| `color.action.destructive.hover-text` | `#f9faf9` | `{color.primitive.paper.100}` | Destructive button text on hover (4.58:1).; blocked |
| `color.action.destructive.pressed-bg` | `#ae1914` | `{color.primitive.red.550}` | Destructive button pressed fill.; blocked |
| `color.action.destructive.filled-bg` | `#dc282e` | `{color.primitive.red.750}` | Confirm-dialog destructive fill.; blocked |
| `color.action.destructive.filled-text` | `#f9faf9` | `{color.primitive.paper.100}` | Text on the destructive fill.; blocked |
| `color.status.danger.text` | `#f73f35` | `{color.primitive.red.850}` | Danger text (5.40:1).; blocked |
| `color.status.danger.fill` | `#dc282e` | `{color.primitive.red.750}` | Danger fill.; blocked |
| `color.status.danger.on-fill` | `#f9faf9` | `{color.primitive.paper.100}` | Text on the danger fill (4.58:1).; blocked |
| `color.status.danger.tint` | `#2b0e0d` | `{color.primitive.ink.60}` | Danger background tint.; blocked |
| `color.status.danger.border` | `#e53935` | `{color.primitive.red.800}` | Danger border, 2px on invalid inputs.; blocked |
| `color.status.warning.text` | `#c9973f` | `{color.primitive.amber.500}` | Warning text (7.54:1).; blocked |
| `color.status.warning.fill` | `#c9973f` | `{color.primitive.amber.500}` | Warning fill.; blocked |
| `color.status.warning.on-fill` | `#0c0909` | `{color.primitive.ink.20}` | Text on the warning fill (7.54:1).; blocked |
| `color.status.warning.tint` | `#1f1a0c` | `{color.primitive.amber.tint}` | Warning background tint.; blocked |
| `color.status.warning.border` | `#c9973f` | `{color.primitive.amber.500}` | Warning border.; blocked |
| `color.status.success.text` | `#86a46f` | `{color.primitive.green.500}` | Success text (7.13:1).; blocked |
| `color.status.success.fill` | `#86a46f` | `{color.primitive.green.500}` | Success fill.; blocked |
| `color.status.success.on-fill` | `#0c0909` | `{color.primitive.ink.20}` | Text on the success fill (7.13:1).; blocked |
| `color.status.success.tint` | `#0f1a0e` | `{color.primitive.green.tint}` | Success background tint.; blocked |
| `color.status.success.border` | `#86a46f` | `{color.primitive.green.500}` | Success border.; blocked |
| `color.status.info.text` | `#7e9ebb` | `{color.primitive.blue.500}` | Info text (7.08:1).; blocked |
| `color.status.info.fill` | `#7e9ebb` | `{color.primitive.blue.500}` | Info fill.; blocked |
| `color.status.info.on-fill` | `#0c0909` | `{color.primitive.ink.20}` | Text on the info fill (7.08:1).; blocked |
| `color.status.info.tint` | `#0f141c` | `{color.primitive.blue.tint}` | Info background tint.; blocked |
| `color.status.info.border` | `#7e9ebb` | `{color.primitive.blue.500}` | Info border.; blocked |
| `color.status.neutral.text` | `#bd787d` | `{color.primitive.rose.600}` | Neutral status text.; blocked |
| `color.status.neutral.fill` | `#a3676b` | `{color.primitive.rose.350}` | Neutral fill (dunst urgency_low).; blocked |
| `color.status.neutral.on-fill` | `#000000` | `{color.primitive.ink.0}` | Text on the neutral fill (4.71:1).; blocked |
| `color.status.neutral.tint` | `#241010` | `{color.primitive.ink.52}` | Neutral background tint.; blocked |
| `color.status.neutral.border` | `#a3676b` | `{color.primitive.rose.350}` | Neutral border.; blocked |
| `color.code.bg` | `#0c0909` | `{color.primitive.ink.20}` | Editor background.; blocked |
| `color.code.current-line` | `#1c0a09` | `{color.primitive.ink.40}` | Current line background.; blocked |
| `color.code.gutter-bg` | `#0c0909` | `{color.primitive.ink.20}` | Gutter background.; blocked |
| `color.code.line-number` | `#ad7175` | `{color.primitive.rose.450}` | Line numbers (5.10:1; replaces the site's #7d1310).; blocked |
| `color.code.line-number-active` | `#e99499` | `{color.primitive.rose.700}` | Line number of the current line.; blocked |
| `color.code.gutter-rule` | `#2b0e0d` | `{color.primitive.ink.60}` | Rule between gutter and text.; blocked |
| `color.code.caret` | `#e99499` | `{color.primitive.rose.700}` | Caret (Xresources *cursorColor).; blocked |
| `color.code.selection-bg` | `#420f0c` | `{color.primitive.red.50}` | Editor selection background (muted text stays 4.71:1).; blocked |
| `color.code.search-match-bg` | `#630f0d` | `{color.primitive.red.150}` | Other search matches.; blocked |
| `color.code.search-current-bg` | `#ffa2a7` | `{color.primitive.rose.800}` | Current search match background.; blocked |
| `color.code.search-current-text` | `#0c0909` | `{color.primitive.ink.20}` | Current search match text.; blocked |
| `color.code.bracket-match` | `#e53935` | `{color.primitive.red.800}` | Matching bracket 1px border.; blocked |
| `color.code.indent-guide` | `#531310` | `{color.primitive.red.100}` | Indent guides.; blocked |
| `color.code.whitespace` | `#7d1310` | `{color.primitive.red.200}` | Rendered whitespace (decorative).; blocked |
| `color.code.syntax.keyword` | `#f73f35` | `{color.primitive.red.850}` | Keywords (Xresources color5 lineage).; blocked |
| `color.code.syntax.comment` | `#ad7175` | `{color.primitive.rose.450}` | Comments, italic (color8 lineage, corrected for contrast).; blocked |
| `color.code.syntax.variable` | `#e99499` | `{color.primitive.rose.700}` | Variables and default text.; blocked |
| `color.code.syntax.heading` | `#e53935` | `{color.primitive.red.800}` | Markup headings (site .syntax-heading).; blocked |
| `color.code.syntax.tag` | `#f73f35` | `{color.primitive.red.850}` | Markup tags.; blocked |
| `color.code.syntax.invalid` | `#e53935` | `{color.primitive.red.800}` | Invalid text: wavy underline.; blocked |
| `color.code.syntax.deprecated` | `#ad7175` | `{color.primitive.rose.450}` | Deprecated symbol: line-through.; blocked |
| `color.diagnostic.error.underline` | `#e53935` | `{color.primitive.red.800}` | Error wavy underline.; blocked |
| `color.diagnostic.error.text` | `#f73f35` | `{color.primitive.red.850}` | Error message text.; blocked |
| `color.diagnostic.error.stripe` | `#e53935` | `{color.primitive.red.800}` | Error stripe mark.; blocked |
| `color.diagnostic.error.bg` | `#2b0e0d` | `{color.primitive.ink.60}` | Error line background.; blocked |
| `color.diagnostic.warning.underline` | `#c9973f` | `{color.primitive.amber.500}` | Warning wavy underline.; blocked |
| `color.diagnostic.warning.text` | `#c9973f` | `{color.primitive.amber.500}` | Warning message text.; blocked |
| `color.diagnostic.warning.stripe` | `#c9973f` | `{color.primitive.amber.500}` | Warning stripe mark.; blocked |
| `color.diagnostic.info.underline` | `#7e9ebb` | `{color.primitive.blue.500}` | Info dotted underline.; blocked |
| `color.diagnostic.info.text` | `#7e9ebb` | `{color.primitive.blue.500}` | Info message text.; blocked |
| `color.diagnostic.hint.underline` | `#ad7175` | `{color.primitive.rose.450}` | Hint dotted underline.; blocked |
| `color.diagnostic.unused.text` | `#bd787d` | `{color.primitive.rose.600}` | Unused symbol, dotted underline.; blocked |
| `color.diff.added.bg` | `#0f1a0e` | `{color.primitive.green.tint}` | Added line background.; blocked |
| `color.diff.added.gutter` | `#86a46f` | `{color.primitive.green.500}` | Added gutter mark and +.; blocked |
| `color.diff.added.emphasis` | `#86a46f` | `{color.primitive.green.500}` | Word-level addition: 2px underline.; blocked |
| `color.diff.removed.bg` | `#2b0e0d` | `{color.primitive.ink.60}` | Removed line background.; blocked |
| `color.diff.removed.gutter` | `#e53935` | `{color.primitive.red.800}` | Removed gutter mark and −.; blocked |
| `color.diff.removed.emphasis` | `#e53935` | `{color.primitive.red.800}` | Word-level removal: underline and line-through.; blocked |
| `color.diff.modified.bg` | `#1f1a0c` | `{color.primitive.amber.tint}` | Modified line background.; blocked |
| `color.diff.modified.gutter` | `#c9973f` | `{color.primitive.amber.500}` | Modified gutter mark and ~.; blocked |
| `color.diff.conflict-border` | `#f73f35` | `{color.primitive.red.850}` | Conflict block: double 1px border.; blocked |
| `color.diff.header-bg` | `#241010` | `{color.primitive.ink.52}` | Hunk header background.; blocked |
| `color.terminal.bg` | `#0c0909` | `{color.primitive.ink.20}` | Terminal background.; blocked |
| `color.terminal.fg` | `#e99499` | `{color.primitive.rose.700}` | Terminal foreground.; blocked |
| `color.terminal.cursor` | `#e99499` | `{color.primitive.rose.700}` | Terminal cursor.; blocked |
| `color.terminal.selection-bg` | `#911410` | `{color.primitive.red.350}` | Terminal selection.; blocked |
| `color.terminal.selection-text` | `#f4eeee` | `{color.primitive.paper.90}` | Terminal selection text.; blocked |
| `color.chart.series-1` | `#ffa2a7` | `{color.primitive.rose.800}` | First series.; blocked |
| `color.chart.axis` | `#9e231f` | `{color.primitive.red.400}` | Axis lines.; blocked |
| `color.chart.grid` | `#2b0e0d` | `{color.primitive.ink.60}` | Grid lines.; blocked |
| `color.chart.label` | `#bd787d` | `{color.primitive.rose.600}` | Axis labels.; blocked |
| `color.icon.default` | `#e99499` | `{color.primitive.rose.700}` | Icons take the text colour (currentColor in CSS).; blocked |
| `color.icon.decorative` | `#a3676b` | `{color.primitive.rose.350}` | Decorative glyphs (3:1 as graphics, never text).; blocked |

### Roles pending an owner decision in `extended`

| Role | Value | Alias of | Use |
| --- | --- | --- | --- |
| `color.surface.backdrop` | `rgb(0 0 0 / 65%)` | `{color.primitive.alpha.black-65}` | Behind modal dialogs.; blocked (D-008) |
| `color.border.divider` | `#2b0e0d` | `{color.primitive.ink.60}` | Hairline between rows and sections.; blocked (D-008) |
| `color.border.control` | `#a3676b` | `{color.primitive.rose.350}` | Form-control boundary (4.45:1, meets the 3:1 non-text minimum).; blocked (D-007) |
| `color.border.overlay` | `#e53935` | `{color.primitive.red.800}` | Every floating layer's 1px border.; blocked (D-008) |
| `color.code.syntax.string` | `#86a46f` | `{color.primitive.green.500}` | Strings in green.; blocked (D-001) |
| `color.code.syntax.number` | `#c9973f` | `{color.primitive.amber.500}` | Numbers in amber.; blocked (D-001) |
| `color.code.syntax.constant` | `#c9973f` | `{color.primitive.amber.500}` | Constants in amber.; blocked (D-001) |
| `color.code.syntax.function` | `#c9973f` | `{color.primitive.amber.500}` | Function and method names in amber, distinct from the identifiers they act on.; blocked (D-001) |
| `color.code.syntax.type` | `#7e9ebb` | `{color.primitive.blue.500}` | Types in blue.; blocked (D-001) |
| `color.code.syntax.operator` | `#ffa2a7` | `{color.primitive.rose.800}` | Operators in bright rose, separated from plain identifiers.; blocked (D-001) |
| `color.code.syntax.punctuation` | `#b37175` | `{color.primitive.rose.550}` | Punctuation recedes; it is structure, not content.; blocked (D-001) |
| `color.code.syntax.property` | `#7e9ebb` | `{color.primitive.blue.500}` | Object properties in blue, matching the type family they belong to.; blocked (D-001) |
| `color.code.syntax.attribute` | `#c9973f` | `{color.primitive.amber.500}` | Markup attribute names in amber, against red tags.; blocked (D-001) |
| `color.code.syntax.escape` | `#c9973f` | `{color.primitive.amber.500}` | Escapes in amber so they read out of the green string around them.; blocked (D-001) |
| `color.terminal.ansi.0` | `#0c0909` | `{color.primitive.ink.20}` | Semantic ANSI slot 0.; blocked (D-001) |
| `color.terminal.ansi.1` | `#e53935` | `{color.primitive.red.800}` | Semantic ANSI slot 1.; blocked (D-001) |
| `color.terminal.ansi.2` | `#86a46f` | `{color.primitive.green.500}` | Semantic ANSI slot 2.; blocked (D-001) |
| `color.terminal.ansi.3` | `#c9973f` | `{color.primitive.amber.500}` | Semantic ANSI slot 3.; blocked (D-001) |
| `color.terminal.ansi.4` | `#7e9ebb` | `{color.primitive.blue.500}` | Semantic ANSI slot 4.; blocked (D-001) |
| `color.terminal.ansi.5` | `#b37175` | `{color.primitive.rose.550}` | Semantic ANSI slot 5.; blocked (D-001) |
| `color.terminal.ansi.6` | `#bd787d` | `{color.primitive.rose.600}` | Semantic ANSI slot 6.; blocked (D-001) |
| `color.terminal.ansi.7` | `#e99499` | `{color.primitive.rose.700}` | Semantic ANSI slot 7.; blocked (D-001) |
| `color.terminal.ansi.8` | `#ad7175` | `{color.primitive.rose.450}` | Semantic ANSI slot 8.; blocked (D-001) |
| `color.terminal.ansi.9` | `#f73f35` | `{color.primitive.red.850}` | Semantic ANSI slot 9.; blocked (D-001) |
| `color.terminal.ansi.10` | `#7fbf7a` | `{color.primitive.green.600}` | Semantic ANSI slot 10.; blocked (D-001) |
| `color.terminal.ansi.11` | `#d6a24b` | `{color.primitive.amber.600}` | Semantic ANSI slot 11.; blocked (D-001) |
| `color.terminal.ansi.12` | `#9ab3c9` | `{color.primitive.blue.600}` | Semantic ANSI slot 12.; blocked (D-001) |
| `color.terminal.ansi.13` | `#d4868b` | `{color.primitive.rose.650}` | Semantic ANSI slot 13.; blocked (D-001) |
| `color.terminal.ansi.14` | `#ffa2a7` | `{color.primitive.rose.800}` | Semantic ANSI slot 14.; blocked (D-001) |
| `color.terminal.ansi.15` | `#f4eeee` | `{color.primitive.paper.90}` | Semantic ANSI slot 15.; blocked (D-001) |
| `color.chart.series-2` | `#86a46f` | `{color.primitive.green.500}` | Second series in green.; blocked (D-001) |
| `color.chart.series-3` | `#c9973f` | `{color.primitive.amber.500}` | Third series in amber.; blocked (D-001) |
| `color.chart.series-4` | `#7e9ebb` | `{color.primitive.blue.500}` | Fourth series in blue.; blocked (D-001) |

## Accessibility

### What is claimed

WCAG 2.2 criteria are used as measurable **design targets** for the roles this
theme defines. A green contrast report and a clean automated scan of the site
are evidence that the specified colours and the reference markup meet those
targets. They are not a conformance claim for any application that adopts the
theme: keyboard operation, focus management, labels, reflow and manual review
remain the consumer's responsibility, and the theme's own limitations are
listed at the end of this document.

### Contrast

- Ordinary text: at least 4.5:1 against the actual background of the state it
  is in, measured on resolved sRGB values after compositing translucent colours.
- Large text (18.66px bold or 24px regular and above): at least 3:1.
- Control boundaries, focus rings, meaningful icons and chart marks: at least
  3:1 (WCAG 1.4.11).
- Disabled text is exempt under WCAG 1.4.3 but must still reach 3:1 by house
  policy (`text.disabled` `#8a5559`, 3.52:1 on the true-black canvas).
- Never round a value into a pass. The report keeps the unrounded ratio and
  displays two decimals; `4.4996` reads 4.50 and fails.
- Heritage values that fail (`#a3676b` as text, `#7d1310` anywhere as text)
  are flagged `$deprecated` in the `heritage-ansi` profile and are never
  presented as ordinary passes.

`exports/contrast.md` lists every declared pair: the global pairs in
`spec/contrast.json` and every `stateTokens` and `contrast[]` entry of every
component. Waived pairs carry their reason; a waiver is only valid for
decorative graphics that are never the sole boundary of a control.

### Focus

Focus is never lost and never invisible. Controls and rows show
`focus.ring` (1px dashed `#e53935`, offset −2px); focusable containers show
`focus.ring-container` (2px solid `#ffa2a7`, offset −3px). On fills, use the
component's explicit ring role (D-015): primary and checked controls use
`focus.ring-container` where declared; destructive fills use their on-fill
text role. Colour mapping does not change ring geometry.
`:focus` without `:focus-visible` draws nothing,
which keeps pointer clicks quiet without hiding keyboard focus. A component
that removes a host's default indicator must draw this one. State matrices on
the site render every declared combination including `selected+focus-visible`
and `invalid+focus-visible`, because those are where rings disappear in
practice.

### Keyboard

Native controls keep native keyboard behaviour. Custom widgets follow the WAI
ARIA Authoring Practices pattern cited in their frontmatter (`aria.apg`):
roving tabindex in toolbars, tabs, trees and listboxes; `aria-activedescendant`
in comboboxes; focus trapping and `Escape` in dialogs and drawers; arrow keys
in radio groups and menus. Positive `tabindex` is banned. Every icon-only
control has an accessible name. The site's own controls (search, filters,
inspector, copy) are reachable and operable with the keyboard, and the no-JS
build keeps every normative section readable.

### Motion

Durations are at most 150ms and apply to opacity, transform and background
only. Under `prefers-reduced-motion: reduce` every duration is 0.01ms: toasts,
dialogs and drawers appear in place, indeterminate progress shows static
stripes, loading glyphs do not spin, skeletons do not shimmer, tab indicators
do not slide. No behaviour is sequenced on `transitionend`.

The specification page’s generated literal-color previews have the narrow
geometry, checkerboard and motion exception recorded in D-014; the component
and consumer rules remain as written.

### Forced colours and high contrast

Under `forced-colors: active` rings use `Highlight`, selections use
`Highlight` / `HighlightText`, borders survive (they are real borders, not
box-shadows), and pattern fills keep charts and diffs legible.

### Reflow, zoom and text

Components reflow at 320 CSS pixels without horizontal page scrolling, at 200%
browser zoom and at 200% text-only zoom. Rings and borders stay 1px / 2px CSS.
Targets are at least 24×24 CSS pixels with 4px spacing in `compact` density.
Long labels wrap or truncate with a visible affordance; the site's stress
fixtures (English, Spanish, Chinese, Arabic with `dir="rtl"`) exercise this.

### Colour is never the only channel

Every status has a glyph; every state has a border, pattern or ARIA change;
diffs keep `+ − ~` markers; charts carry pattern fills and a table fallback.
The `heritage-ansi` terminal palette has reduced hue differentiation by
design, and the specification documents it as a limitation rather than fixing
it silently.

### Limitations (recorded, not hidden)

- The theme specifies colours, states and reference markup. It cannot make a
  host application's own focus management, labels or reading order correct.
- The automated scan on the site uses axe-core with the WCAG 2.x A and AA
  rule sets. It does not evaluate every success criterion, and manual checks
  (keyboard walks, screen-reader reading, zoom) require separate execution
  evidence. Missing protocols remain not run, not implied by the scan.
- `text.disabled` at 3.52:1 on canvas is below the ordinary-text floor by design and
  relies on the WCAG exemption for inactive controls.
- Heritage ANSI slots 1, 4, 6, 8, 9, 10, 12, 13, 14 and 15 fail 4.5:1 on the
  terminal background (slot 0 is the background). Programs that render errors
  or directories in those slots inherit that failure; the `extended` overlay
  proposes a corrected set.
- Syntax roles inside an editor selection: keyword, type, property and comment
  drop below 4.5:1 on `code.selection-bg`. Selection is transient; the
  limitation is recorded in the code-editor specification.


### Verification evidence and freshness

The coverage ledger describes source facts: specified, demonstrated and test
implemented. `exports/coverage.json` schemaVersion 1 adds `testImplemented` and
retains `tested` as a compatibility alias with its original file-existence
meaning. Component JSON coverage and family totals use the same definitions.
No source coverage Boolean proves execution or blanket WCAG conformance.

The linked static verification report supplies actual per-test outcomes and
per-component summaries. Evidence schemaVersion 1 records automated or manual
method, category, component, explicitly covered states/variants, browser/version,
OS, viewport, profile, density, JavaScript setting and a run or protocol reference.
Empty state/variant lists mean unspecified, never all. The environment's profile
and density describe the initial configuration; tests that switch them say so
in their scope. Browser records from one engine never imply another engine.
Skipped, failed, not run and not applicable are separate outcomes; every non-pass
has a reason. Manual screen-reader and keyboard claims require protocol records.
The existing automated suite targets Chromium; other engines remain not run.

Freshness compares both the tested artifact digest and a conservative source
fingerprint covering tracked contract sources, schemas, generators, site code,
tests, agent guidance, CI configuration and package lockfile. All such changes
invalidate the current claim; unrelated source changes may therefore invalidate
more evidence than strictly necessary. A stale record retains its actual outcome
and supporting reference but does not establish a current pass.

A build first publishes a static not-run report and a subject manifest. The
browser reporter checks that source and specimen assets still match that subject
before publishing execution evidence. Report generation writes only under
`dist/verification/` and verifies all specimen bytes remain unchanged. Report
presentation gets a separate static/browser gate and never certifies itself.
Run timestamps and revisions live only in this post-test evidence, outside the
committed deterministic exports and their digests, avoiding circular hashes.

Local workflow: build, run the browser suite, `npm run verification:report`,
`npm run verification:check`, then `npm run test:verification`. A local run names
its local execution and digests; it is not represented as hosted CI evidence.
For a manual protocol, provide a schema-valid evidence document to
`node scripts/verification-report.mjs <repo-relative-evidence.json>` with its
actual tested digests, browser/environment and a `protocol:` reference containing
the protocol identifier and recorded observations. Retain the protocol with the
evidence; do not copy private artifacts into this public repository.

### Contrast report

| Pair | State | Kind | Foreground | Background | Ratio | Minimum | Result |
| --- | --- | --- | --- | --- | --- | --- | --- |
| default text on canvas | — | text | `color.text.default` #e99499 | `color.surface.canvas` #000000 | 9.16 | 4.5 | pass |
| default text on default surface | — | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| default text on raised | — | text | `color.text.default` #e99499 | `color.surface.raised` #160b0b | 8.43 | 4.5 | pass |
| bright text on canvas | — | text | `color.text.bright` #ffa2a7 | `color.surface.canvas` #000000 | 10.98 | 4.5 | pass |
| prose on canvas | — | text | `color.text.prose` #f4eeee | `color.surface.canvas` #000000 | 18.31 | 4.5 | pass |
| prose on sunken | — | text | `color.text.prose` #f4eeee | `color.surface.sunken` #000000 | 18.31 | 4.5 | pass |
| muted text on canvas | — | text | `color.text.muted` #bd787d | `color.surface.canvas` #000000 | 6.15 | 4.5 | pass |
| muted text on raised | — | text | `color.text.muted` #bd787d | `color.surface.raised` #160b0b | 5.66 | 4.5 | pass |
| subtle text on canvas | — | text | `color.text.subtle` #ad7175 | `color.surface.canvas` #000000 | 5.40 | 4.5 | pass |
| subtle text on raised | — | text | `color.text.subtle` #ad7175 | `color.surface.raised` #160b0b | 4.96 | 4.5 | pass |
| disabled text on canvas (exempt; house floor 3:1) | — | ui | `color.text.disabled` #8a5559 | `color.surface.canvas` #000000 | 3.52 | 3 | pass |
| placeholder on input | — | text | `color.text.placeholder` #a3676b | `color.surface.input` #000000 | 4.71 | 4.5 | pass |
| accent text on canvas | — | text | `color.text.accent` #e53935 | `color.surface.canvas` #000000 | 4.97 | 4.5 | pass |
| accent text on default surface | — | text | `color.text.accent` #e53935 | `color.surface.default` #100c0c | 4.60 | 4.5 | pass |
| strong accent on raised | — | text | `color.text.accent-strong` #f73f35 | `color.surface.raised` #160b0b | 5.26 | 4.5 | pass |
| link on canvas | — | text | `color.text.link` #f73f35 | `color.surface.canvas` #000000 | 5.72 | 4.5 | pass |
| link hover text on strong hover fill | — | text | `color.text.link-hover` #f4eeee | `color.interaction.hover.bg-strong` #630f0d | 11.41 | 4.5 | pass |
| selection text on selection | — | text | `color.interaction.selection.text` #f4eeee | `color.interaction.selection.bg` #531310 | 12.47 | 4.5 | pass |
| inactive selection text on inactive selection | — | text | `color.interaction.selection.inactive-text` #e99499 | `color.interaction.selection.inactive-bg` #420f0c | 7.02 | 4.5 | pass |
| primary action text on fill | — | text | `color.action.primary.text` #f4eeee | `color.action.primary.bg` #7d1310 | 9.28 | 4.5 | pass |
| primary action text on hover fill | — | text | `color.action.primary.text` #f4eeee | `color.action.primary.hover-bg` #911410 | 7.92 | 4.5 | pass |
| secondary action text on canvas | — | text | `color.action.secondary.text` #ffa2a7 | `color.surface.canvas` #000000 | 10.98 | 4.5 | pass |
| destructive text on canvas | — | text | `color.action.destructive.text` #f73f35 | `color.surface.canvas` #000000 | 5.72 | 4.5 | pass |
| destructive hover text on hover fill | — | text | `color.action.destructive.hover-text` #f9faf9 | `color.action.destructive.hover-bg` #dc282e | 4.58 | 4.5 | pass |
| danger text on canvas | — | text | `color.status.danger.text` #f73f35 | `color.surface.canvas` #000000 | 5.72 | 4.5 | pass |
| danger text on danger tint | — | text | `color.status.danger.text` #f73f35 | `color.status.danger.tint` #2b0e0d | 4.88 | 4.5 | pass |
| danger on-fill text | — | text | `color.status.danger.on-fill` #f9faf9 | `color.status.danger.fill` #dc282e | 4.58 | 4.5 | pass |
| warning text on canvas | — | text | `color.status.warning.text` #c9973f | `color.surface.canvas` #000000 | 7.98 | 4.5 | pass |
| warning text on warning tint | — | text | `color.status.warning.text` #c9973f | `color.status.warning.tint` #1f1a0c | 6.59 | 4.5 | pass |
| warning on-fill text | — | text | `color.status.warning.on-fill` #0c0909 | `color.status.warning.fill` #c9973f | 7.54 | 4.5 | pass |
| success text on canvas | — | text | `color.status.success.text` #86a46f | `color.surface.canvas` #000000 | 7.55 | 4.5 | pass |
| success text on success tint | — | text | `color.status.success.text` #86a46f | `color.status.success.tint` #0f1a0e | 6.43 | 4.5 | pass |
| success on-fill text | — | text | `color.status.success.on-fill` #0c0909 | `color.status.success.fill` #86a46f | 7.13 | 4.5 | pass |
| info text on canvas | — | text | `color.status.info.text` #7e9ebb | `color.surface.canvas` #000000 | 7.50 | 4.5 | pass |
| info text on info tint | — | text | `color.status.info.text` #7e9ebb | `color.status.info.tint` #0f141c | 6.59 | 4.5 | pass |
| info on-fill text | — | text | `color.status.info.on-fill` #0c0909 | `color.status.info.fill` #7e9ebb | 7.08 | 4.5 | pass |
| neutral on-fill text | — | text | `color.status.neutral.on-fill` #000000 | `color.status.neutral.fill` #a3676b | 4.71 | 4.5 | pass |
| focus ring on canvas | — | ui | `color.interaction.focus.ring` #e53935 | `color.surface.canvas` #000000 | 4.97 | 3 | pass |
| focus ring on default surface | — | ui | `color.interaction.focus.ring` #e53935 | `color.surface.default` #100c0c | 4.60 | 3 | pass |
| container focus ring on selection | — | ui | `color.interaction.focus.ring-container` #ffa2a7 | `color.interaction.selection.bg` #531310 | 7.48 | 3 | pass |
| control border on input | — | ui | `color.border.control` #a3676b | `color.surface.input` #000000 | 4.71 | 3 | pass |
| control border on default surface | — | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| active border on input | — | ui | `color.border.active` #e53935 | `color.surface.input` #000000 | 4.97 | 3 | pass |
| decorative border on canvas (decorative; not a control edge) | — | ui | `color.border.default` #531310 | `color.surface.canvas` #000000 | 1.47 | 3 | waived: decorative hairline; never the only boundary of a control (spec/foundations.md) |
| strong rule on canvas (decorative) | — | ui | `color.border.strong` #9e231f | `color.surface.canvas` #000000 | 2.71 | 3 | waived: 2px decorative rule paired with a surface change; never the only boundary of a control |
| line numbers on code background | — | text | `color.code.line-number` #ad7175 | `color.code.bg` #0c0909 | 5.10 | 4.5 | pass |
| caret on current line | — | ui | `color.code.caret` #e99499 | `color.code.current-line` #1c0a09 | 8.35 | 3 | pass |
| comment on code background | — | text | `color.code.syntax.comment` #ad7175 | `color.code.bg` #0c0909 | 5.10 | 4.5 | pass |
| string on code background | — | text | `color.code.syntax.string` #bd787d | `color.code.bg` #0c0909 | 5.81 | 4.5 | pass |
| keyword on code background | — | text | `color.code.syntax.keyword` #f73f35 | `color.code.bg` #0c0909 | 5.40 | 4.5 | pass |
| type on code background | — | text | `color.code.syntax.type` #b37175 | `color.code.bg` #0c0909 | 5.23 | 4.5 | pass |
| muted text on editor selection | — | text | `color.text.muted` #bd787d | `color.code.selection-bg` #420f0c | 4.71 | 4.5 | pass |
| current search match text | — | text | `color.code.search-current-text` #0c0909 | `color.code.search-current-bg` #ffa2a7 | 10.37 | 4.5 | pass |
| error text on canvas | — | text | `color.diagnostic.error.text` #f73f35 | `color.surface.canvas` #000000 | 5.72 | 4.5 | pass |
| warning diagnostic text on canvas | — | text | `color.diagnostic.warning.text` #c9973f | `color.surface.canvas` #000000 | 7.98 | 4.5 | pass |
| info diagnostic text on canvas | — | text | `color.diagnostic.info.text` #7e9ebb | `color.surface.canvas` #000000 | 7.50 | 4.5 | pass |
| default text on added-line background | — | text | `color.text.default` #e99499 | `color.diff.added.bg` #0f1a0e | 7.80 | 4.5 | pass |
| default text on removed-line background | — | text | `color.text.default` #e99499 | `color.diff.removed.bg` #2b0e0d | 7.82 | 4.5 | pass |
| default text on modified-line background | — | text | `color.text.default` #e99499 | `color.diff.modified.bg` #1f1a0c | 7.56 | 4.5 | pass |
| terminal foreground | — | text | `color.terminal.fg` #e99499 | `color.terminal.bg` #0c0909 | 8.65 | 4.5 | pass |
| chart label on canvas | — | text | `color.chart.label` #bd787d | `color.surface.canvas` #000000 | 6.15 | 4.5 | pass |
| chart series 4 on canvas (graphic) | — | ui | `color.chart.series-4` #a3676b | `color.surface.canvas` #000000 | 4.71 | 3 | pass |
| decorative icon on canvas (graphic) | — | ui | `color.icon.decorative` #a3676b | `color.surface.canvas` #000000 | 4.71 | 3 | pass |
| admin-form default text | default | text | `color.text.default` #e99499 | `color.surface.input` #000000 | 9.16 | 4.5 | pass |
| admin-form default border | default | ui | `color.border.control` #a3676b | `color.surface.input` #000000 | 4.71 | 3 | pass |
| admin-form invalid text | invalid | text | `color.status.danger.text` #f73f35 | `color.status.danger.tint` #2b0e0d | 4.88 | 4.5 | pass |
| admin-form invalid border | invalid | ui | `color.status.danger.border` #e53935 | `color.status.danger.tint` #2b0e0d | 4.24 | 3 | pass |
| admin-form busy text | busy | text | `color.action.primary.text` #f4eeee | `color.action.primary.bg` #7d1310 | 9.28 | 4.5 | pass |
| title, labels and the current step on the panel surface | — | text | `color.text.bright` #ffa2a7 | `color.surface.default` #100c0c | 10.17 | 4.5 | pass |
| help, affix and review keys on the panel surface | — | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| breadcrumb links on the panel surface | — | text | `color.text.link` #f73f35 | `color.surface.default` #100c0c | 5.29 | 4.5 | pass |
| complete-step check | — | text | `color.status.success.text` #86a46f | `color.surface.default` #100c0c | 6.99 | 4.5 | pass |
| notice title on its tint | — | text | `color.status.info.text` #7e9ebb | `color.status.info.tint` #0f141c | 6.59 | 4.5 | pass |
| notice text on its tint | — | text | `color.text.default` #e99499 | `color.status.info.tint` #0f141c | 8.06 | 4.5 | pass |
| summary text on the danger tint | invalid | text | `color.text.default` #e99499 | `color.status.danger.tint` #2b0e0d | 7.82 | 4.5 | pass |
| field messages on the panel surface | invalid | text | `color.status.danger.text` #f73f35 | `color.surface.default` #100c0c | 5.29 | 4.5 | pass |
| invalid field boundary | invalid | ui | `color.status.danger.border` #e53935 | `color.surface.input` #000000 | 4.97 | 3 | pass |
| current-step indicator bar | — | ui | `color.border.selected-indicator` #e53935 | `color.surface.default` #100c0c | 4.60 | 3 | pass |
| Back button and file-input button boundaries | — | ui | `color.action.secondary.border` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| disabled control text (exempt; house floor 3:1) | disabled | ui | `color.text.disabled` #8a5559 | `color.interaction.disabled.bg` #160b0b | 3.24 | 3 | pass |
| disabled labels and review keys (exempt; house floor 3:1) | disabled | ui | `color.text.disabled` #8a5559 | `color.surface.default` #100c0c | 3.26 | 3 | pass |
| disabled boundaries (exempt) | disabled | ui | `color.border.disabled` #7d1310 | `color.interaction.disabled.bg` #160b0b | 1.82 | 1 | pass |
| review header rule (decorative) | — | ui | `color.border.strong` #9e231f | `color.surface.default` #100c0c | 2.51 | 1 | pass |
| panel edge (decorative) | — | ui | `color.border.default` #531310 | `color.surface.default` #100c0c | 1.36 | 1 | pass |
| step, row and footer rules (decorative) | — | ui | `color.border.divider` #2b0e0d | `color.surface.default` #100c0c | 1.08 | 1 | pass |
| alert default text | default | text | `color.status.danger.text` #f73f35 | `color.status.danger.tint` #2b0e0d | 4.88 | 4.5 | pass |
| alert default border | default | ui | `color.status.danger.border` #e53935 | `color.status.danger.tint` #2b0e0d | 4.24 | 3 | pass |
| alert hover text | hover | text | `color.action.tertiary.text` #e99499 | `color.action.tertiary.hover-bg` #1c0a09 | 8.35 | 4.5 | pass |
| alert focus-visible text | focus-visible | text | `color.action.tertiary.text` #e99499 | `color.status.info.tint` #0f141c | 8.06 | 4.5 | pass |
| alert focus-visible outline | focus-visible | ui | `color.interaction.focus.ring` #e53935 | `color.status.info.tint` #0f141c | 4.37 | 3 | pass |
| body text on the danger tint | — | text | `color.text.default` #e99499 | `color.status.danger.tint` #2b0e0d | 7.82 | 4.5 | pass |
| warning glyph and title on the warning tint | — | text | `color.status.warning.text` #c9973f | `color.status.warning.tint` #1f1a0c | 6.59 | 4.5 | pass |
| warning border on the warning tint | — | ui | `color.status.warning.border` #c9973f | `color.status.warning.tint` #1f1a0c | 6.59 | 3 | pass |
| body text on the warning tint | — | text | `color.text.default` #e99499 | `color.status.warning.tint` #1f1a0c | 7.56 | 4.5 | pass |
| success glyph and title on the success tint | — | text | `color.status.success.text` #86a46f | `color.status.success.tint` #0f1a0e | 6.43 | 4.5 | pass |
| success border on the success tint | — | ui | `color.status.success.border` #86a46f | `color.status.success.tint` #0f1a0e | 6.43 | 3 | pass |
| body text on the success tint | — | text | `color.text.default` #e99499 | `color.status.success.tint` #0f1a0e | 7.80 | 4.5 | pass |
| info glyph and title on the info tint | — | text | `color.status.info.text` #7e9ebb | `color.status.info.tint` #0f141c | 6.59 | 4.5 | pass |
| info border on the info tint | — | ui | `color.status.info.border` #7e9ebb | `color.status.info.tint` #0f141c | 6.59 | 3 | pass |
| body text on the info tint | — | text | `color.text.default` #e99499 | `color.status.info.tint` #0f141c | 8.06 | 4.5 | pass |
| neutral glyph and title on the neutral tint | — | text | `color.status.neutral.text` #bd787d | `color.status.neutral.tint` #241010 | 5.31 | 4.5 | pass |
| neutral border on the neutral tint | — | ui | `color.status.neutral.border` #a3676b | `color.status.neutral.tint` #241010 | 4.07 | 3 | pass |
| body text on the neutral tint | — | text | `color.text.default` #e99499 | `color.status.neutral.tint` #241010 | 7.92 | 4.5 | pass |
| close button on the info tint | — | text | `color.action.tertiary.text` #e99499 | `color.status.info.tint` #0f141c | 8.06 | 4.5 | pass |
| ring on the info tint | focus-visible | ui | `color.interaction.focus.ring` #e53935 | `color.status.info.tint` #0f141c | 4.37 | 3 | pass |
| avatar default text | default | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| avatar default border | default | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| Primary content | — | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| Supporting content | — | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| Control boundary | — | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| badge default text | default | text | `color.status.danger.on-fill` #f9faf9 | `color.status.danger.fill` #dc282e | 4.58 | 4.5 | pass |
| badge empty text | empty | text | `color.status.danger.on-fill` #f9faf9 | `color.status.danger.fill` #dc282e | 4.58 | 4.5 | pass |
| warning badge | — | text | `color.status.warning.on-fill` #0c0909 | `color.status.warning.fill` #c9973f | 7.54 | 4.5 | pass |
| success badge | — | text | `color.status.success.on-fill` #0c0909 | `color.status.success.fill` #86a46f | 7.13 | 4.5 | pass |
| info badge | — | text | `color.status.info.on-fill` #0c0909 | `color.status.info.fill` #7e9ebb | 7.08 | 4.5 | pass |
| neutral badge | — | text | `color.status.neutral.on-fill` #000000 | `color.status.neutral.fill` #a3676b | 4.71 | 4.5 | pass |
| count badge | — | text | `color.action.primary.text` #f4eeee | `color.action.primary.bg` #7d1310 | 9.28 | 4.5 | pass |
| danger outline text | — | text | `color.status.danger.text` #f73f35 | `color.surface.default` #100c0c | 5.29 | 4.5 | pass |
| warning outline text | — | text | `color.status.warning.text` #c9973f | `color.surface.default` #100c0c | 7.39 | 4.5 | pass |
| success outline text | — | text | `color.status.success.text` #86a46f | `color.surface.default` #100c0c | 6.99 | 4.5 | pass |
| info outline text | — | text | `color.status.info.text` #7e9ebb | `color.surface.default` #100c0c | 6.94 | 4.5 | pass |
| neutral outline text | — | text | `color.status.neutral.text` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| danger outline border | — | ui | `color.status.danger.border` #e53935 | `color.surface.default` #100c0c | 4.60 | 3 | pass |
| warning outline border | — | ui | `color.status.warning.border` #c9973f | `color.surface.default` #100c0c | 7.39 | 3 | pass |
| success outline border | — | ui | `color.status.success.border` #86a46f | `color.surface.default` #100c0c | 6.99 | 3 | pass |
| info outline border | — | ui | `color.status.info.border` #7e9ebb | `color.surface.default` #100c0c | 6.94 | 3 | pass |
| neutral outline border | — | ui | `color.status.neutral.border` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| danger dot on the panel surface | empty | ui | `color.status.danger.fill` #dc282e | `color.surface.default` #100c0c | 4.06 | 3 | pass |
| warning dot on the panel surface | empty | ui | `color.status.warning.fill` #c9973f | `color.surface.default` #100c0c | 7.39 | 3 | pass |
| success dot on the panel surface | empty | ui | `color.status.success.fill` #86a46f | `color.surface.default` #100c0c | 6.99 | 3 | pass |
| info dot on the panel surface | empty | ui | `color.status.info.fill` #7e9ebb | `color.surface.default` #100c0c | 6.94 | 3 | pass |
| neutral dot on the panel surface | empty | ui | `color.status.neutral.fill` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| breadcrumbs default text | default | text | `color.text.link` #f73f35 | `color.surface.default` #100c0c | 5.29 | 4.5 | pass |
| breadcrumbs hover text | hover | text | `color.text.link-hover` #f4eeee | `color.interaction.hover.bg-strong` #630f0d | 11.41 | 4.5 | pass |
| breadcrumbs focus-visible text | focus-visible | text | `color.text.link` #f73f35 | `color.surface.default` #100c0c | 5.29 | 4.5 | pass |
| breadcrumbs focus-visible outline | focus-visible | ui | `color.interaction.focus.ring` #e53935 | `color.surface.default` #100c0c | 4.60 | 3 | pass |
| breadcrumbs current text | current | text | `color.text.bright` #ffa2a7 | `color.surface.default` #100c0c | 10.17 | 4.5 | pass |
| breadcrumbs truncated text | truncated | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| separator on the panel surface | — | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| link underline | — | ui | `color.text.link-underline` #dc282e | `color.surface.default` #100c0c | 4.06 | 3 | pass |
| button-group default text | default | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| button-group default border | default | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| Primary content | — | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| Supporting content | — | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| Control boundary | — | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| button default text | default | text | `color.action.primary.text` #f4eeee | `color.action.primary.bg` #7d1310 | 9.28 | 4.5 | pass |
| button hover text | hover | text | `color.action.primary.text` #f4eeee | `color.action.primary.hover-bg` #911410 | 7.92 | 4.5 | pass |
| button focus-visible text | focus-visible | text | `color.action.primary.text` #f4eeee | `color.action.primary.bg` #7d1310 | 9.28 | 4.5 | pass |
| button focus-visible outline | focus-visible | ui | `color.interaction.focus.ring-container` #ffa2a7 | `color.action.primary.bg` #7d1310 | 5.57 | 3 | pass |
| button active text | active | text | `color.action.primary.text` #f4eeee | `color.action.primary.pressed-bg` #630f0d | 11.41 | 4.5 | pass |
| button loading text | loading | text | `color.action.primary.text` #f4eeee | `color.action.primary.bg` #7d1310 | 9.28 | 4.5 | pass |
| primary fill against the panel (decorative; the label identifies the button) | — | ui | `color.action.primary.bg` #7d1310 | `color.surface.default` #100c0c | 1.83 | 3 | waived: the fill is not the boundary of the control; the label text at 9.28:1 identifies it, and hover, active and focus each add a second channel |
| secondary text at rest | — | text | `color.action.secondary.text` #ffa2a7 | `color.action.secondary.bg` #000000 | 10.98 | 4.5 | pass |
| secondary border at rest | — | ui | `color.action.secondary.border` #a3676b | `color.action.secondary.bg` #000000 | 4.71 | 3 | pass |
| secondary text on the hover fill | hover | text | `color.action.secondary.text` #ffa2a7 | `color.action.secondary.hover-bg` #630f0d | 6.84 | 4.5 | pass |
| secondary border against the panel while hovered (the fill inside it measures 2.94:1) | hover | ui | `color.action.secondary.border` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| ring on the secondary hover fill | hover | ui | `color.interaction.focus.ring` #e53935 | `color.action.secondary.hover-bg` #630f0d | 3.10 | 3 | pass |
| secondary text while pressed | active | text | `color.action.secondary.text` #ffa2a7 | `color.action.secondary.pressed-bg` #420f0c | 8.41 | 4.5 | pass |
| tertiary text at rest | — | text | `color.action.tertiary.text` #e99499 | `color.action.secondary.bg` #000000 | 9.16 | 4.5 | pass |
| tertiary text on the hover fill | hover | text | `color.action.tertiary.text` #e99499 | `color.action.tertiary.hover-bg` #1c0a09 | 8.35 | 4.5 | pass |
| tertiary text while pressed | active | text | `color.action.tertiary.text` #e99499 | `color.interaction.pressed.bg` #420f0c | 7.02 | 4.5 | pass |
| active border on the pressed fill | active | ui | `color.border.active` #e53935 | `color.interaction.pressed.bg` #420f0c | 3.81 | 3 | pass |
| destructive text at rest | — | text | `color.action.destructive.text` #f73f35 | `color.action.secondary.bg` #000000 | 5.72 | 4.5 | pass |
| destructive border at rest | — | ui | `color.action.destructive.border` #e53935 | `color.action.secondary.bg` #000000 | 4.97 | 3 | pass |
| destructive text on the hover fill | hover | text | `color.action.destructive.hover-text` #f9faf9 | `color.action.destructive.hover-bg` #dc282e | 4.58 | 4.5 | pass |
| ring on the destructive hover fill (the ring takes the on-fill colour) | hover | ui | `color.action.destructive.hover-text` #f9faf9 | `color.action.destructive.hover-bg` #dc282e | 4.58 | 3 | pass |
| destructive text while pressed | active | text | `color.action.destructive.hover-text` #f9faf9 | `color.action.destructive.pressed-bg` #ae1914 | 6.81 | 4.5 | pass |
| confirm-dialog filled destructive | — | text | `color.action.destructive.filled-text` #f9faf9 | `color.action.destructive.filled-bg` #dc282e | 4.58 | 4.5 | pass |
| disabled text (exempt; house floor 3:1) | disabled | ui | `color.text.disabled` #8a5559 | `color.interaction.disabled.bg` #160b0b | 3.24 | 3 | pass |
| disabled border (exempt) | disabled | ui | `color.border.disabled` #7d1310 | `color.interaction.disabled.bg` #160b0b | 1.82 | 1 | pass |
| card default text | default | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| card hover text | hover | text | `color.text.default` #e99499 | `color.interaction.hover.bg` #1c0a09 | 8.35 | 4.5 | pass |
| card hover border | hover | ui | `color.border.control` #a3676b | `color.interaction.hover.bg` #1c0a09 | 4.29 | 3 | pass |
| card focus-visible text | focus-visible | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| card focus-visible outline | focus-visible | ui | `color.interaction.focus.ring-container` #ffa2a7 | `color.surface.default` #100c0c | 10.17 | 3 | pass |
| card selected text | selected | text | `color.interaction.selection.text` #f4eeee | `color.interaction.selection.bg` #531310 | 12.47 | 4.5 | pass |
| card selected+focus-visible text | selected+focus-visible | text | `color.interaction.selection.text` #f4eeee | `color.interaction.selection.bg` #531310 | 12.47 | 4.5 | pass |
| card selected+focus-visible outline | selected+focus-visible | ui | `color.interaction.focus.ring-container` #ffa2a7 | `color.interaction.selection.bg` #531310 | 7.48 | 3 | pass |
| title on the card | — | text | `color.text.bright` #ffa2a7 | `color.surface.default` #100c0c | 10.17 | 4.5 | pass |
| metadata on the card | — | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| title on the hover fill | hover | text | `color.text.bright` #ffa2a7 | `color.interaction.hover.bg` #1c0a09 | 10.01 | 4.5 | pass |
| metadata on the hover fill | hover | text | `color.text.muted` #bd787d | `color.interaction.hover.bg` #1c0a09 | 5.60 | 4.5 | pass |
| card edge (decorative; the card is not a control at rest) | — | ui | `color.border.default` #531310 | `color.surface.default` #100c0c | 1.36 | 1 | pass |
| chart default text | default | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| chart default border | default | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| Primary content | — | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| Supporting content | — | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| Control boundary | — | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| checkbox default border | default | ui | `color.border.control` #a3676b | `color.surface.input` #000000 | 4.71 | 3 | pass |
| checkbox hover border | hover | ui | `color.border.control` #a3676b | `color.interaction.hover.bg` #1c0a09 | 4.29 | 3 | pass |
| checkbox focus-visible border | focus-visible | ui | `color.border.control` #a3676b | `color.surface.input` #000000 | 4.71 | 3 | pass |
| checkbox focus-visible outline | focus-visible | ui | `color.interaction.focus.ring` #e53935 | `color.surface.input` #000000 | 4.97 | 3 | pass |
| checkbox checked text | checked | text | `color.action.primary.text` #f4eeee | `color.action.primary.bg` #7d1310 | 9.28 | 4.5 | pass |
| checkbox mixed text | mixed | text | `color.action.primary.text` #f4eeee | `color.action.primary.bg` #7d1310 | 9.28 | 4.5 | pass |
| checkbox checked+focus-visible text | checked+focus-visible | text | `color.action.primary.text` #f4eeee | `color.action.primary.bg` #7d1310 | 9.28 | 4.5 | pass |
| checkbox checked+focus-visible outline | checked+focus-visible | ui | `color.interaction.focus.ring-container` #ffa2a7 | `color.action.primary.bg` #7d1310 | 5.57 | 3 | pass |
| checkbox invalid border | invalid | ui | `color.status.danger.border` #e53935 | `color.surface.input` #000000 | 4.97 | 3 | pass |
| checkbox required border | required | ui | `color.border.control` #a3676b | `color.surface.input` #000000 | 4.71 | 3 | pass |
| option text on the panel surface | — | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| legend on the panel surface | — | text | `color.text.bright` #ffa2a7 | `color.surface.default` #100c0c | 10.17 | 4.5 | pass |
| checked box edge against the panel (the border stays border.control) | checked | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| glyph on the hovered checked fill | hover | text | `color.action.primary.text` #f4eeee | `color.action.primary.hover-bg` #911410 | 7.92 | 4.5 | pass |
| validation message on the panel surface | — | text | `color.status.danger.text` #f73f35 | `color.surface.default` #100c0c | 5.29 | 4.5 | pass |
| disabled option text (exempt; house floor 3:1) | disabled | ui | `color.text.disabled` #8a5559 | `color.surface.default` #100c0c | 3.26 | 3 | pass |
| disabled glyph (exempt; house floor 3:1) | checked+disabled | ui | `color.text.disabled` #8a5559 | `color.interaction.disabled.bg` #160b0b | 3.24 | 3 | pass |
| disabled border (exempt) | disabled | ui | `color.border.disabled` #7d1310 | `color.interaction.disabled.bg` #160b0b | 1.82 | 1 | pass |
| chip default text | default | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| chip default border | default | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| chip hover text | hover | text | `color.text.default` #e99499 | `color.interaction.hover.bg` #1c0a09 | 8.35 | 4.5 | pass |
| chip hover border | hover | ui | `color.border.control` #a3676b | `color.interaction.hover.bg` #1c0a09 | 4.29 | 3 | pass |
| chip focus-visible text | focus-visible | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| chip focus-visible border | focus-visible | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| chip focus-visible outline | focus-visible | ui | `color.interaction.focus.ring` #e53935 | `color.surface.default` #100c0c | 4.60 | 3 | pass |
| chip selected text | selected | text | `color.interaction.selection.text` #f4eeee | `color.interaction.selection.bg` #531310 | 12.47 | 4.5 | pass |
| chip selected+focus-visible text | selected+focus-visible | text | `color.interaction.selection.text` #f4eeee | `color.interaction.selection.bg` #531310 | 12.47 | 4.5 | pass |
| chip selected+focus-visible outline | selected+focus-visible | ui | `color.interaction.focus.ring-container` #ffa2a7 | `color.interaction.selection.bg` #531310 | 7.48 | 3 | pass |
| remove glyph at rest | — | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| disabled chip text (exempt; house floor 3:1) | disabled | ui | `color.text.disabled` #8a5559 | `color.interaction.disabled.bg` #160b0b | 3.24 | 3 | pass |
| disabled chip border (exempt) | disabled | ui | `color.border.disabled` #7d1310 | `color.interaction.disabled.bg` #160b0b | 1.82 | 1 | pass |
| code-editor default text | default | text | `color.code.syntax.variable` #e99499 | `color.code.bg` #0c0909 | 8.65 | 4.5 | pass |
| code-editor default border | default | ui | `color.border.control` #a3676b | `color.code.bg` #0c0909 | 4.45 | 3 | pass |
| code-editor focus-visible text | focus-visible | text | `color.code.syntax.variable` #e99499 | `color.code.current-line` #1c0a09 | 8.35 | 4.5 | pass |
| code-editor focus-visible outline | focus-visible | ui | `color.interaction.focus.ring-container` #ffa2a7 | `color.code.current-line` #1c0a09 | 10.01 | 3 | pass |
| code-editor selected text | selected | text | `color.code.syntax.variable` #e99499 | `color.code.selection-bg` #420f0c | 7.02 | 4.5 | pass |
| code-editor selected+container-inactive text | selected+container-inactive | text | `color.interaction.selection.inactive-text` #e99499 | `color.interaction.selection.inactive-bg` #420f0c | 7.02 | 4.5 | pass |
| code-editor current text | current | text | `color.code.syntax.variable` #e99499 | `color.code.bg` #0c0909 | 8.65 | 4.5 | pass |
| code-editor current border | current | ui | `color.code.bracket-match` #e53935 | `color.code.bg` #0c0909 | 4.69 | 3 | pass |
| code-editor read-only text | read-only | text | `color.code.line-number` #ad7175 | `color.surface.sunken` #000000 | 5.40 | 4.5 | pass |
| caret on the current line | focus-visible | ui | `color.code.caret` #e99499 | `color.code.current-line` #1c0a09 | 8.35 | 3 | pass |
| bracket-match outline on the editor surface | current | ui | `color.code.bracket-match` #e53935 | `color.code.bg` #0c0909 | 4.69 | 3 | pass |
| bracket-match outline on the current line | current | ui | `color.code.bracket-match` #e53935 | `color.code.current-line` #1c0a09 | 4.53 | 3 | pass |
| line numbers in the gutter | — | text | `color.code.line-number` #ad7175 | `color.code.gutter-bg` #0c0909 | 5.10 | 4.5 | pass |
| active line number | — | text | `color.code.line-number-active` #e99499 | `color.code.current-line` #1c0a09 | 8.35 | 4.5 | pass |
| keyword | — | text | `color.code.syntax.keyword` #f73f35 | `color.code.bg` #0c0909 | 5.40 | 4.5 | pass |
| string | — | text | `color.code.syntax.string` #bd787d | `color.code.bg` #0c0909 | 5.81 | 4.5 | pass |
| comment | — | text | `color.code.syntax.comment` #ad7175 | `color.code.bg` #0c0909 | 5.10 | 4.5 | pass |
| number and constant | — | text | `color.code.syntax.number` #d4868b | `color.code.bg` #0c0909 | 7.16 | 4.5 | pass |
| function | — | text | `color.code.syntax.function` #ffa2a7 | `color.code.bg` #0c0909 | 10.37 | 4.5 | pass |
| type | — | text | `color.code.syntax.type` #b37175 | `color.code.bg` #0c0909 | 5.23 | 4.5 | pass |
| property, heading and invalid text | — | text | `color.code.syntax.property` #e53935 | `color.code.bg` #0c0909 | 4.69 | 4.5 | pass |
| tag | — | text | `color.code.syntax.tag` #f73f35 | `color.code.bg` #0c0909 | 5.40 | 4.5 | pass |
| attribute and escape | — | text | `color.code.syntax.attribute` #d4868b | `color.code.bg` #0c0909 | 7.16 | 4.5 | pass |
| deprecated text | — | text | `color.code.syntax.deprecated` #ad7175 | `color.code.bg` #0c0909 | 5.10 | 4.5 | pass |
| muted string text inside the selection | selected | text | `color.code.syntax.string` #bd787d | `color.code.selection-bg` #420f0c | 4.71 | 4.5 | pass |
| fold marker text | — | text | `color.text.muted` #bd787d | `color.code.bg` #0c0909 | 5.81 | 4.5 | pass |
| indent guide (decorative) | — | ui | `color.code.indent-guide` #531310 | `color.code.bg` #0c0909 | 1.39 | 1 | pass |
| rendered whitespace (decorative) | — | ui | `color.code.whitespace` #7d1310 | `color.code.bg` #0c0909 | 1.86 | 1 | pass |
| gutter rule (decorative) | — | ui | `color.code.gutter-rule` #2b0e0d | `color.code.bg` #0c0909 | 1.11 | 1 | pass |
| combobox default text | default | text | `color.text.default` #e99499 | `color.surface.input` #000000 | 9.16 | 4.5 | pass |
| combobox default border | default | ui | `color.border.control` #a3676b | `color.surface.input` #000000 | 4.71 | 3 | pass |
| combobox hover text | hover | text | `color.text.link-hover` #f4eeee | `color.interaction.hover.bg-strong` #630f0d | 11.41 | 4.5 | pass |
| combobox focus-visible text | focus-visible | text | `color.text.default` #e99499 | `color.surface.input` #000000 | 9.16 | 4.5 | pass |
| combobox focus-visible border | focus-visible | ui | `color.border.active` #e53935 | `color.surface.input` #000000 | 4.97 | 3 | pass |
| combobox focus-visible outline | focus-visible | ui | `color.interaction.focus.ring` #e53935 | `color.surface.input` #000000 | 4.97 | 3 | pass |
| combobox open text | open | text | `color.text.default` #e99499 | `color.surface.raised` #160b0b | 8.43 | 4.5 | pass |
| combobox open border | open | ui | `color.border.overlay` #e53935 | `color.surface.raised` #160b0b | 4.57 | 3 | pass |
| combobox selected text | selected | text | `color.interaction.selection.text` #f4eeee | `color.interaction.selection.bg` #531310 | 12.47 | 4.5 | pass |
| combobox selected outline | selected | ui | `color.interaction.focus.ring-container` #ffa2a7 | `color.interaction.selection.bg` #531310 | 7.48 | 3 | pass |
| combobox invalid text | invalid | text | `color.text.default` #e99499 | `color.surface.input` #000000 | 9.16 | 4.5 | pass |
| combobox invalid border | invalid | ui | `color.status.danger.border` #e53935 | `color.surface.input` #000000 | 4.97 | 3 | pass |
| combobox invalid+focus-visible text | invalid+focus-visible | text | `color.text.default` #e99499 | `color.surface.input` #000000 | 9.16 | 4.5 | pass |
| combobox invalid+focus-visible border | invalid+focus-visible | ui | `color.status.danger.border` #e53935 | `color.surface.input` #000000 | 4.97 | 3 | pass |
| combobox invalid+focus-visible outline | invalid+focus-visible | ui | `color.interaction.focus.ring-container` #ffa2a7 | `color.surface.input` #000000 | 10.98 | 3 | pass |
| combobox no-results text | no-results | text | `color.text.muted` #bd787d | `color.surface.raised` #160b0b | 5.66 | 4.5 | pass |
| combobox no-results border | no-results | ui | `color.border.overlay` #e53935 | `color.surface.raised` #160b0b | 4.57 | 3 | pass |
| group heading on the listbox | — | text | `color.text.subtle` #ad7175 | `color.surface.raised` #160b0b | 4.96 | 4.5 | pass |
| validation message on the canvas | — | text | `color.status.danger.text` #f73f35 | `color.surface.canvas` #000000 | 5.72 | 4.5 | pass |
| help text on the canvas | — | text | `color.text.muted` #bd787d | `color.surface.canvas` #000000 | 6.15 | 4.5 | pass |
| disabled text (exempt; house floor 3:1) | disabled | ui | `color.text.disabled` #8a5559 | `color.interaction.disabled.bg` #160b0b | 3.24 | 3 | pass |
| disabled border (exempt) | disabled | ui | `color.border.disabled` #7d1310 | `color.interaction.disabled.bg` #160b0b | 1.82 | 1 | pass |
| group separator (decorative) | — | ui | `color.border.divider` #2b0e0d | `color.surface.raised` #160b0b | 1.08 | 1 | pass |
| command-palette default text | default | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| command-palette default border | default | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| Primary content | — | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| Supporting content | — | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| Control boundary | — | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| compact-dashboard default text | default | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| compact-dashboard default border | default | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| Primary content | — | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| Supporting content | — | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| Control boundary | — | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| data-table default text | default | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| data-table hover text | hover | text | `color.text.default` #e99499 | `color.interaction.hover.bg` #1c0a09 | 8.35 | 4.5 | pass |
| data-table focus-visible text | focus-visible | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| data-table focus-visible outline | focus-visible | ui | `color.interaction.focus.ring` #e53935 | `color.surface.default` #100c0c | 4.60 | 3 | pass |
| data-table selected text | selected | text | `color.interaction.selection.text` #f4eeee | `color.interaction.selection.bg` #531310 | 12.47 | 4.5 | pass |
| data-table selected+focus-visible text | selected+focus-visible | text | `color.interaction.selection.text` #f4eeee | `color.interaction.selection.bg` #531310 | 12.47 | 4.5 | pass |
| data-table selected+focus-visible outline | selected+focus-visible | ui | `color.interaction.focus.ring-container` #ffa2a7 | `color.interaction.selection.bg` #531310 | 7.48 | 3 | pass |
| data-table sorted text | sorted | text | `color.text.bright` #ffa2a7 | `color.surface.default` #100c0c | 10.17 | 4.5 | pass |
| data-table mixed text | mixed | text | `color.action.primary.text` #f4eeee | `color.action.primary.bg` #7d1310 | 9.28 | 4.5 | pass |
| data-table empty text | empty | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| data-table loading text | loading | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| header text | — | text | `color.text.bright` #ffa2a7 | `color.surface.default` #100c0c | 10.17 | 4.5 | pass |
| checkbox boundary | — | ui | `color.border.control` #a3676b | `color.surface.input` #000000 | 4.71 | 3 | pass |
| checked fill against the row (the glyph and the boundary carry the state) | mixed | ui | `color.action.primary.bg` #7d1310 | `color.surface.default` #100c0c | 1.83 | 1 | pass |
| header rule (decorative) | — | ui | `color.border.strong` #9e231f | `color.surface.default` #100c0c | 2.51 | 1 | pass |
| row divider (decorative) | — | ui | `color.border.divider` #2b0e0d | `color.surface.default` #100c0c | 1.08 | 1 | pass |
| resize handle at rest (decorative; keyboard resizing is a header action) | — | ui | `color.border.default` #531310 | `color.surface.default` #100c0c | 1.36 | 1 | pass |
| date-picker default text | default | text | `color.text.default` #e99499 | `color.surface.input` #000000 | 9.16 | 4.5 | pass |
| date-picker default border | default | ui | `color.border.control` #a3676b | `color.surface.input` #000000 | 4.71 | 3 | pass |
| date-picker hover text | hover | text | `color.text.default` #e99499 | `color.interaction.hover.bg` #1c0a09 | 8.35 | 4.5 | pass |
| date-picker hover border | hover | ui | `color.border.control` #a3676b | `color.interaction.hover.bg` #1c0a09 | 4.29 | 3 | pass |
| date-picker focus-visible text | focus-visible | text | `color.text.default` #e99499 | `color.surface.input` #000000 | 9.16 | 4.5 | pass |
| date-picker focus-visible border | focus-visible | ui | `color.border.active` #e53935 | `color.surface.input` #000000 | 4.97 | 3 | pass |
| date-picker focus-visible outline | focus-visible | ui | `color.interaction.focus.ring` #e53935 | `color.surface.input` #000000 | 4.97 | 3 | pass |
| date-picker invalid text | invalid | text | `color.text.default` #e99499 | `color.surface.input` #000000 | 9.16 | 4.5 | pass |
| date-picker invalid border | invalid | ui | `color.status.danger.border` #e53935 | `color.surface.input` #000000 | 4.97 | 3 | pass |
| date-picker invalid+focus-visible text | invalid+focus-visible | text | `color.text.default` #e99499 | `color.surface.input` #000000 | 9.16 | 4.5 | pass |
| date-picker invalid+focus-visible border | invalid+focus-visible | ui | `color.status.danger.border` #e53935 | `color.surface.input` #000000 | 4.97 | 3 | pass |
| date-picker invalid+focus-visible outline | invalid+focus-visible | ui | `color.interaction.focus.ring-container` #ffa2a7 | `color.surface.input` #000000 | 10.98 | 3 | pass |
| date-picker read-only text | read-only | text | `color.text.default` #e99499 | `color.surface.canvas` #000000 | 9.16 | 4.5 | pass |
| validation message on the canvas | — | text | `color.status.danger.text` #f73f35 | `color.surface.canvas` #000000 | 5.72 | 4.5 | pass |
| help text and the range separator on the canvas | — | text | `color.text.muted` #bd787d | `color.surface.canvas` #000000 | 6.15 | 4.5 | pass |
| disabled text (exempt; house floor 3:1) | disabled | ui | `color.text.disabled` #8a5559 | `color.interaction.disabled.bg` #160b0b | 3.24 | 3 | pass |
| disabled border (exempt) | disabled | ui | `color.border.disabled` #7d1310 | `color.interaction.disabled.bg` #160b0b | 1.82 | 1 | pass |
| read-only bottom edge (decorative; the field has no box) | read-only | ui | `color.border.divider` #2b0e0d | `color.surface.canvas` #000000 | 1.17 | 1 | pass |
| calendar text | — | text | `color.text.default` #e99499 | `color.surface.raised` #160b0b | 8.43 | 4.5 | pass |
| selected date | — | text | `color.interaction.selection.text` #f4eeee | `color.interaction.selection.bg` #531310 | 12.47 | 4.5 | pass |
| description-list default text | default | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| description-list default border | default | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| Primary content | — | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| Supporting content | — | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| Control boundary | — | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| diagnostics default text | default | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| diagnostics default border | default | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| diagnostics hover text | hover | text | `color.text.default` #e99499 | `color.interaction.hover.bg` #1c0a09 | 8.35 | 4.5 | pass |
| diagnostics focus-visible text | focus-visible | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| diagnostics focus-visible outline | focus-visible | ui | `color.interaction.focus.ring` #e53935 | `color.surface.default` #100c0c | 4.60 | 3 | pass |
| diagnostics selected text | selected | text | `color.interaction.selection.text` #f4eeee | `color.interaction.selection.bg` #531310 | 12.47 | 4.5 | pass |
| diagnostics selected+focus-visible text | selected+focus-visible | text | `color.interaction.selection.text` #f4eeee | `color.interaction.selection.bg` #531310 | 12.47 | 4.5 | pass |
| diagnostics selected+focus-visible outline | selected+focus-visible | ui | `color.interaction.focus.ring-container` #ffa2a7 | `color.interaction.selection.bg` #531310 | 7.48 | 3 | pass |
| diagnostics error text | error | text | `color.status.danger.text` #f73f35 | `color.surface.default` #100c0c | 5.29 | 4.5 | pass |
| diagnostics empty text | empty | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| code text | — | text | `color.text.default` #e99499 | `color.code.bg` #0c0909 | 8.65 | 4.5 | pass |
| code text on the error line | — | text | `color.text.default` #e99499 | `color.diagnostic.error.bg` #2b0e0d | 7.82 | 4.5 | pass |
| error glyph on the error line | — | text | `color.diagnostic.error.text` #f73f35 | `color.diagnostic.error.bg` #2b0e0d | 4.88 | 4.5 | pass |
| error underline | — | ui | `color.diagnostic.error.underline` #e53935 | `color.code.bg` #0c0909 | 4.69 | 3 | pass |
| error underline on the error line | — | ui | `color.diagnostic.error.underline` #e53935 | `color.diagnostic.error.bg` #2b0e0d | 4.24 | 3 | pass |
| warning underline inside the selected range | selected | ui | `color.diagnostic.warning.underline` #c9973f | `color.code.selection-bg` #420f0c | 6.12 | 3 | pass |
| warning underline | — | ui | `color.diagnostic.warning.underline` #c9973f | `color.code.bg` #0c0909 | 7.54 | 3 | pass |
| info underline | — | ui | `color.diagnostic.info.underline` #7e9ebb | `color.code.bg` #0c0909 | 7.08 | 3 | pass |
| hint underline | — | ui | `color.diagnostic.hint.underline` #ad7175 | `color.code.bg` #0c0909 | 5.10 | 3 | pass |
| unused text | — | text | `color.diagnostic.unused.text` #bd787d | `color.code.bg` #0c0909 | 5.81 | 4.5 | pass |
| deprecated text | — | text | `color.code.syntax.deprecated` #ad7175 | `color.code.bg` #0c0909 | 5.10 | 4.5 | pass |
| error stripe mark | — | ui | `color.diagnostic.error.stripe` #e53935 | `color.code.bg` #0c0909 | 4.69 | 3 | pass |
| warning stripe mark | — | ui | `color.diagnostic.warning.stripe` #c9973f | `color.code.bg` #0c0909 | 7.54 | 3 | pass |
| info stripe mark | — | ui | `color.diagnostic.info.text` #7e9ebb | `color.code.bg` #0c0909 | 7.08 | 3 | pass |
| error glyph and count in the panel | — | text | `color.diagnostic.error.text` #f73f35 | `color.surface.default` #100c0c | 5.29 | 4.5 | pass |
| warning glyph and count in the panel | — | text | `color.diagnostic.warning.text` #c9973f | `color.surface.default` #100c0c | 7.39 | 4.5 | pass |
| info glyph and count in the panel | — | text | `color.diagnostic.info.text` #7e9ebb | `color.surface.default` #100c0c | 6.94 | 4.5 | pass |
| hint glyph in the panel | — | text | `color.text.subtle` #ad7175 | `color.surface.default` #100c0c | 5.00 | 4.5 | pass |
| location and rule code | — | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| row text on hover | hover | text | `color.text.default` #e99499 | `color.interaction.hover.bg` #1c0a09 | 8.35 | 4.5 | pass |
| error glyph on hover | hover | text | `color.diagnostic.error.text` #f73f35 | `color.interaction.hover.bg` #1c0a09 | 5.21 | 4.5 | pass |
| the ✓ of the empty message | empty | text | `color.status.success.text` #86a46f | `color.surface.default` #100c0c | 6.99 | 4.5 | pass |
| dialog default text | default | text | `color.text.default` #e99499 | `color.surface.overlay` #241010 | 7.92 | 4.5 | pass |
| dialog default border | default | ui | `color.border.overlay` #e53935 | `color.surface.overlay` #241010 | 4.30 | 3 | pass |
| dialog open text | open | text | `color.text.default` #e99499 | `color.surface.overlay` #241010 | 7.92 | 4.5 | pass |
| dialog open border | open | ui | `color.border.overlay` #e53935 | `color.surface.overlay` #241010 | 4.30 | 3 | pass |
| dialog focus-trapped text | focus-trapped | text | `color.text.default` #e99499 | `color.surface.overlay` #241010 | 7.92 | 4.5 | pass |
| dialog focus-trapped border | focus-trapped | ui | `color.border.overlay` #e53935 | `color.surface.overlay` #241010 | 4.30 | 3 | pass |
| dialog focus-trapped outline | focus-trapped | ui | `color.interaction.focus.ring-container` #ffa2a7 | `color.surface.overlay` #241010 | 9.49 | 3 | pass |
| dialog hover text | hover | text | `color.action.tertiary.text` #e99499 | `color.action.tertiary.hover-bg` #1c0a09 | 8.35 | 4.5 | pass |
| dialog focus-visible text | focus-visible | text | `color.action.tertiary.text` #e99499 | `color.surface.overlay` #241010 | 7.92 | 4.5 | pass |
| dialog focus-visible outline | focus-visible | ui | `color.interaction.focus.ring` #e53935 | `color.surface.overlay` #241010 | 4.30 | 3 | pass |
| dialog reduced-motion text | reduced-motion | text | `color.text.default` #e99499 | `color.surface.overlay` #241010 | 7.92 | 4.5 | pass |
| dialog reduced-motion border | reduced-motion | ui | `color.border.overlay` #e53935 | `color.surface.overlay` #241010 | 4.30 | 3 | pass |
| title on the overlay surface | — | text | `color.text.bright` #ffa2a7 | `color.surface.overlay` #241010 | 9.49 | 4.5 | pass |
| note on the overlay surface | — | text | `color.text.muted` #bd787d | `color.surface.overlay` #241010 | 5.31 | 4.5 | pass |
| secondary button text on the overlay surface | — | text | `color.action.secondary.text` #ffa2a7 | `color.surface.overlay` #241010 | 9.49 | 4.5 | pass |
| secondary button boundary on the overlay surface | — | ui | `color.action.secondary.border` #a3676b | `color.surface.overlay` #241010 | 4.07 | 3 | pass |
| primary button text | — | text | `color.action.primary.text` #f4eeee | `color.action.primary.bg` #7d1310 | 9.28 | 4.5 | pass |
| destructive confirm text on its fill | — | text | `color.action.destructive.filled-text` #f9faf9 | `color.action.destructive.filled-bg` #dc282e | 4.58 | 4.5 | pass |
| destructive fill against the overlay surface | — | ui | `color.action.destructive.filled-bg` #dc282e | `color.surface.overlay` #241010 | 3.79 | 3 | pass |
| ring on the primary fill | focus-visible | ui | `color.interaction.focus.ring-container` #ffa2a7 | `color.action.primary.bg` #7d1310 | 5.57 | 3 | pass |
| ring on the destructive fill | focus-visible | ui | `color.action.destructive.filled-text` #f9faf9 | `color.action.destructive.filled-bg` #dc282e | 4.58 | 3 | pass |
| form field text | — | text | `color.text.default` #e99499 | `color.surface.input` #000000 | 9.16 | 4.5 | pass |
| form field boundary on the overlay surface | — | ui | `color.border.control` #a3676b | `color.surface.overlay` #241010 | 4.07 | 3 | pass |
| diff-view default text | default | text | `color.text.default` #e99499 | `color.code.bg` #0c0909 | 8.65 | 4.5 | pass |
| diff-view default border | default | ui | `color.border.control` #a3676b | `color.code.bg` #0c0909 | 4.45 | 3 | pass |
| diff-view hover text | hover | text | `color.text.default` #e99499 | `color.interaction.hover.bg` #1c0a09 | 8.35 | 4.5 | pass |
| diff-view selected text | selected | text | `color.interaction.selection.text` #f4eeee | `color.interaction.selection.bg` #531310 | 12.47 | 4.5 | pass |
| diff-view focus-visible text | focus-visible | text | `color.text.default` #e99499 | `color.code.bg` #0c0909 | 8.65 | 4.5 | pass |
| diff-view focus-visible outline | focus-visible | ui | `color.interaction.focus.ring` #e53935 | `color.code.bg` #0c0909 | 4.69 | 3 | pass |
| diff-view expanded text | expanded | text | `color.text.link` #f73f35 | `color.diff.header-bg` #241010 | 4.94 | 4.5 | pass |
| diff-view collapsed text | collapsed | text | `color.text.link` #f73f35 | `color.diff.header-bg` #241010 | 4.94 | 4.5 | pass |
| text on an added row | — | text | `color.text.default` #e99499 | `color.diff.added.bg` #0f1a0e | 7.80 | 4.5 | pass |
| text on a removed row | — | text | `color.text.default` #e99499 | `color.diff.removed.bg` #2b0e0d | 7.82 | 4.5 | pass |
| text on a modified row | — | text | `color.text.default` #e99499 | `color.diff.modified.bg` #1f1a0c | 7.56 | 4.5 | pass |
| hunk context on the header | — | text | `color.text.default` #e99499 | `color.diff.header-bg` #241010 | 7.92 | 4.5 | pass |
| hunk range on the header | — | text | `color.text.muted` #bd787d | `color.diff.header-bg` #241010 | 5.31 | 4.5 | pass |
| line numbers on a context row | — | text | `color.text.muted` #bd787d | `color.code.bg` #0c0909 | 5.81 | 4.5 | pass |
| line numbers on an added row | — | text | `color.text.muted` #bd787d | `color.diff.added.bg` #0f1a0e | 5.23 | 4.5 | pass |
| line numbers on a removed row | — | text | `color.text.muted` #bd787d | `color.diff.removed.bg` #2b0e0d | 5.25 | 4.5 | pass |
| line numbers on a modified row | — | text | `color.text.muted` #bd787d | `color.diff.modified.bg` #1f1a0c | 5.07 | 4.5 | pass |
| the + marker | — | text | `color.diff.added.gutter` #86a46f | `color.diff.added.bg` #0f1a0e | 6.43 | 4.5 | pass |
| the − marker (bold, 4.24:1; glyph plus fill) | — | ui | `color.diff.removed.gutter` #e53935 | `color.diff.removed.bg` #2b0e0d | 4.24 | 3 | pass |
| the ~ marker | — | text | `color.diff.modified.gutter` #c9973f | `color.diff.modified.bg` #1f1a0c | 6.59 | 4.5 | pass |
| word-level added underline | — | ui | `color.diff.added.emphasis` #86a46f | `color.diff.added.bg` #0f1a0e | 6.43 | 3 | pass |
| word-level removed underline and strike | — | ui | `color.diff.removed.emphasis` #e53935 | `color.diff.removed.bg` #2b0e0d | 4.24 | 3 | pass |
| conflict double border | — | ui | `color.diff.conflict-border` #f73f35 | `color.code.bg` #0c0909 | 5.40 | 3 | pass |
| conflict marker lines | — | text | `color.text.bright` #ffa2a7 | `color.diff.header-bg` #241010 | 9.49 | 4.5 | pass |
| ring on an added row | focus-visible | ui | `color.interaction.focus.ring` #e53935 | `color.diff.added.bg` #0f1a0e | 4.23 | 3 | pass |
| ring on a removed row | focus-visible | ui | `color.interaction.focus.ring` #e53935 | `color.diff.removed.bg` #2b0e0d | 4.24 | 3 | pass |
| ring on a modified row | focus-visible | ui | `color.interaction.focus.ring` #e53935 | `color.diff.modified.bg` #1f1a0c | 4.10 | 3 | pass |
| ring on the selected row | focus-visible | ui | `color.interaction.focus.ring-container` #ffa2a7 | `color.interaction.selection.bg` #531310 | 7.48 | 3 | pass |
| fold button text | — | text | `color.text.link` #f73f35 | `color.diff.header-bg` #241010 | 4.94 | 4.5 | pass |
| disclosure default text | default | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| disclosure default border | default | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| Primary content | — | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| Supporting content | — | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| Control boundary | — | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| drawer default text | default | text | `color.text.default` #e99499 | `color.surface.overlay` #241010 | 7.92 | 4.5 | pass |
| drawer default border | default | ui | `color.border.overlay` #e53935 | `color.surface.overlay` #241010 | 4.30 | 3 | pass |
| drawer open text | open | text | `color.text.default` #e99499 | `color.surface.overlay` #241010 | 7.92 | 4.5 | pass |
| drawer open border | open | ui | `color.border.overlay` #e53935 | `color.surface.overlay` #241010 | 4.30 | 3 | pass |
| drawer focus-trapped text | focus-trapped | text | `color.text.default` #e99499 | `color.surface.overlay` #241010 | 7.92 | 4.5 | pass |
| drawer focus-trapped border | focus-trapped | ui | `color.border.overlay` #e53935 | `color.surface.overlay` #241010 | 4.30 | 3 | pass |
| drawer focus-trapped outline | focus-trapped | ui | `color.interaction.focus.ring-container` #ffa2a7 | `color.surface.overlay` #241010 | 9.49 | 3 | pass |
| drawer reduced-motion text | reduced-motion | text | `color.text.default` #e99499 | `color.surface.overlay` #241010 | 7.92 | 4.5 | pass |
| drawer reduced-motion border | reduced-motion | ui | `color.border.overlay` #e53935 | `color.surface.overlay` #241010 | 4.30 | 3 | pass |
| title on the overlay surface | — | text | `color.text.bright` #ffa2a7 | `color.surface.overlay` #241010 | 9.49 | 4.5 | pass |
| note on the overlay surface | — | text | `color.text.muted` #bd787d | `color.surface.overlay` #241010 | 5.31 | 4.5 | pass |
| close button on the overlay surface | — | text | `color.action.tertiary.text` #e99499 | `color.surface.overlay` #241010 | 7.92 | 4.5 | pass |
| ring on the overlay surface | — | ui | `color.interaction.focus.ring` #e53935 | `color.surface.overlay` #241010 | 4.30 | 3 | pass |
| hovered item | — | text | `color.text.link-hover` #f4eeee | `color.interaction.hover.bg-strong` #630f0d | 11.41 | 4.5 | pass |
| row dividers (decorative) | — | ui | `color.border.divider` #2b0e0d | `color.surface.overlay` #241010 | 1.01 | 1 | pass |
| editor-search default text | default | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| editor-search default border | default | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| Primary content | — | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| Supporting content | — | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| Control boundary | — | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| empty-state default text | default | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| heading on the panel surface | — | text | `color.text.bright` #ffa2a7 | `color.surface.default` #100c0c | 10.17 | 4.5 | pass |
| action text on the panel surface | — | text | `color.action.secondary.text` #ffa2a7 | `color.surface.default` #100c0c | 10.17 | 4.5 | pass |
| action boundary on the panel surface | — | ui | `color.action.secondary.border` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| glyph (decorative) | — | ui | `color.icon.decorative` #a3676b | `color.surface.default` #100c0c | 4.36 | 1 | pass |
| container edge (decorative) | — | ui | `color.border.divider` #2b0e0d | `color.surface.canvas` #000000 | 1.17 | 1 | pass |
| error-state default text | default | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| error-state default border | default | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| Primary content | — | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| Supporting content | — | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| Control boundary | — | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| field default text | default | text | `color.text.bright` #ffa2a7 | `color.surface.default` #100c0c | 10.17 | 4.5 | pass |
| field required text | required | text | `color.status.danger.text` #f73f35 | `color.surface.default` #100c0c | 5.29 | 4.5 | pass |
| field invalid text | invalid | text | `color.status.danger.text` #f73f35 | `color.surface.default` #100c0c | 5.29 | 4.5 | pass |
| help text on the panel surface | — | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| help text on the canvas | — | text | `color.text.muted` #bd787d | `color.surface.canvas` #000000 | 6.15 | 4.5 | pass |
| disabled label (exempt; house floor 3:1) | disabled | ui | `color.text.disabled` #8a5559 | `color.surface.default` #100c0c | 3.26 | 3 | pass |
| disabled control text (exempt; house floor 3:1) | disabled | ui | `color.text.disabled` #8a5559 | `color.interaction.disabled.bg` #160b0b | 3.24 | 3 | pass |
| disabled border (exempt) | disabled | ui | `color.border.disabled` #7d1310 | `color.interaction.disabled.bg` #160b0b | 1.82 | 1 | pass |
| fieldset default text | default | text | `color.text.bright` #ffa2a7 | `color.surface.default` #100c0c | 10.17 | 4.5 | pass |
| group border (decorative; the legend and spacing carry the grouping) | — | ui | `color.border.divider` #2b0e0d | `color.surface.default` #100c0c | 1.08 | 1 | pass |
| field labels inside the group | — | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| help text inside the group | — | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| disabled legend and labels (exempt; house floor 3:1) | disabled | ui | `color.text.disabled` #8a5559 | `color.surface.default` #100c0c | 3.26 | 3 | pass |
| disabled field text (exempt; house floor 3:1) | disabled | ui | `color.text.disabled` #8a5559 | `color.interaction.disabled.bg` #160b0b | 3.24 | 3 | pass |
| disabled field border (exempt) | disabled | ui | `color.border.disabled` #7d1310 | `color.interaction.disabled.bg` #160b0b | 1.82 | 1 | pass |
| file-browser default text | default | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| file-browser default border | default | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| Primary content | — | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| Supporting content | — | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| Control boundary | — | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| file-input default text | default | text | `color.action.secondary.text` #ffa2a7 | `color.surface.canvas` #000000 | 10.98 | 4.5 | pass |
| file-input default border | default | ui | `color.action.secondary.border` #a3676b | `color.surface.canvas` #000000 | 4.71 | 3 | pass |
| file-input hover text | hover | text | `color.action.secondary.text` #ffa2a7 | `color.action.secondary.hover-bg` #630f0d | 6.84 | 4.5 | pass |
| file-input focus-visible text | focus-visible | text | `color.action.secondary.text` #ffa2a7 | `color.surface.canvas` #000000 | 10.98 | 4.5 | pass |
| file-input focus-visible border | focus-visible | ui | `color.action.secondary.border` #a3676b | `color.surface.canvas` #000000 | 4.71 | 3 | pass |
| file-input focus-visible outline | focus-visible | ui | `color.interaction.focus.ring` #e53935 | `color.surface.canvas` #000000 | 4.97 | 3 | pass |
| file-input drop-target text | drop-target | text | `color.text.default` #e99499 | `color.interaction.marquee` #110202 | 8.84 | 4.5 | pass |
| file-input drop-target border | drop-target | ui | `color.interaction.drop-target` #e53935 | `color.interaction.marquee` #110202 | 4.80 | 3 | pass |
| file-input invalid text | invalid | text | `color.action.secondary.text` #ffa2a7 | `color.surface.canvas` #000000 | 10.98 | 4.5 | pass |
| file-input invalid border | invalid | ui | `color.status.danger.border` #e53935 | `color.surface.canvas` #000000 | 4.97 | 3 | pass |
| hovered button boundary against the canvas outside it | hover | ui | `color.action.secondary.border` #a3676b | `color.surface.canvas` #000000 | 4.71 | 3 | pass |
| drop-zone dashed border at rest | — | ui | `color.interaction.drop-target` #e53935 | `color.surface.canvas` #000000 | 4.97 | 3 | pass |
| status, hint and help text on the canvas | — | text | `color.text.muted` #bd787d | `color.surface.canvas` #000000 | 6.15 | 4.5 | pass |
| file sizes on the canvas | — | text | `color.text.subtle` #ad7175 | `color.surface.canvas` #000000 | 5.40 | 4.5 | pass |
| per-file error and validation message on the canvas | — | text | `color.status.danger.text` #f73f35 | `color.surface.canvas` #000000 | 5.72 | 4.5 | pass |
| disabled button text (exempt; house floor 3:1) | disabled | ui | `color.text.disabled` #8a5559 | `color.interaction.disabled.bg` #160b0b | 3.24 | 3 | pass |
| disabled border (exempt) | disabled | ui | `color.border.disabled` #7d1310 | `color.interaction.disabled.bg` #160b0b | 1.82 | 1 | pass |
| progress stripes (decorative pattern) | loading | ui | `color.action.primary.bg` #7d1310 | `color.interaction.pressed.bg` #420f0c | 1.51 | 1 | pass |
| filterable-table default text | default | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| filterable-table hover text | hover | text | `color.text.default` #e99499 | `color.interaction.hover.bg` #1c0a09 | 8.35 | 4.5 | pass |
| filterable-table focus-visible text | focus-visible | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| filterable-table focus-visible outline | focus-visible | ui | `color.interaction.focus.ring` #e53935 | `color.surface.default` #100c0c | 4.60 | 3 | pass |
| filterable-table selected text | selected | text | `color.interaction.selection.text` #f4eeee | `color.interaction.selection.bg` #531310 | 12.47 | 4.5 | pass |
| filterable-table no-results text | no-results | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| toolbar controls on the chrome surface | — | text | `color.text.default` #e99499 | `color.surface.chrome` #090707 | 8.77 | 4.5 | pass |
| search box and filter button boundaries on the chrome surface | — | ui | `color.border.control` #a3676b | `color.surface.chrome` #090707 | 4.51 | 3 | pass |
| column headers, empty-state heading and current page | — | text | `color.text.bright` #ffa2a7 | `color.surface.default` #100c0c | 10.17 | 4.5 | pass |
| count, chip remove glyph and empty-state text | — | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| Tested badge | — | text | `color.status.success.on-fill` #0c0909 | `color.status.success.fill` #86a46f | 7.13 | 4.5 | pass |
| Demonstrated badge | — | text | `color.status.info.on-fill` #0c0909 | `color.status.info.fill` #7e9ebb | 7.08 | 4.5 | pass |
| New button and checked boxes | — | text | `color.action.primary.text` #f4eeee | `color.action.primary.bg` #7d1310 | 9.28 | 4.5 | pass |
| ring on a selected row | selected | ui | `color.interaction.focus.ring-container` #ffa2a7 | `color.interaction.selection.bg` #531310 | 7.48 | 3 | pass |
| chip and checkbox boundaries | — | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| disabled Prev link (exempt; house floor 3:1) | — | ui | `color.text.disabled` #8a5559 | `color.surface.default` #100c0c | 3.26 | 3 | pass |
| header rule (decorative) | — | ui | `color.border.strong` #9e231f | `color.surface.default` #100c0c | 2.51 | 1 | pass |
| panel edge and toolbar separator (decorative) | — | ui | `color.border.default` #531310 | `color.surface.default` #100c0c | 1.36 | 1 | pass |
| row, toolbar and footer rules (decorative) | — | ui | `color.border.divider` #2b0e0d | `color.surface.default` #100c0c | 1.08 | 1 | pass |
| form-builder default text | default | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| form-builder default border | default | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| Editor text | — | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| Definition status | — | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| i3-window-frame default text | default | text | `color.text.muted` #bd787d | `color.surface.chrome` #090707 | 5.88 | 4.5 | pass |
| i3-window-frame focus-visible text | focus-visible | text | `color.text.bright` #ffa2a7 | `color.surface.chrome-alt` #1c0a09 | 10.01 | 4.5 | pass |
| i3-window-frame focus-visible outline | focus-visible | ui | `color.interaction.focus.ring-container` #ffa2a7 | `color.surface.chrome-alt` #1c0a09 | 10.01 | 3 | pass |
| i3-window-frame selected text | selected | text | `color.interaction.selection.text` #f4eeee | `color.interaction.selection.bg` #531310 | 12.47 | 4.5 | pass |
| i3-window-frame container-inactive text | container-inactive | text | `color.text.muted` #bd787d | `color.surface.chrome` #090707 | 5.88 | 4.5 | pass |
| i3-window-frame error text | error | text | `color.status.danger.on-fill` #f9faf9 | `color.status.danger.fill` #dc282e | 4.58 | 4.5 | pass |
| workspace names and status text on the bar | — | text | `color.text.default` #e99499 | `color.surface.chrome` #090707 | 8.77 | 4.5 | pass |
| clock on the bar | — | text | `color.text.bright` #ffa2a7 | `color.surface.chrome` #090707 | 10.50 | 4.5 | pass |
| focused title | — | text | `color.text.bright` #ffa2a7 | `color.surface.chrome-alt` #1c0a09 | 10.01 | 4.5 | pass |
| focused window frame on the desktop | — | ui | `color.border.active` #e53935 | `color.surface.desktop` #000000 | 4.97 | 3 | pass |
| floating window frame on the desktop | — | ui | `color.border.overlay` #e53935 | `color.surface.desktop` #000000 | 4.97 | 3 | pass |
| container ring on the window surface | focus-visible | ui | `color.interaction.focus.ring-container` #ffa2a7 | `color.surface.canvas` #000000 | 10.98 | 3 | pass |
| ring on a workspace or tab | — | ui | `color.interaction.focus.ring` #e53935 | `color.surface.chrome` #090707 | 4.75 | 3 | pass |
| visible-tab indicator on the title row | — | ui | `color.border.selected-indicator` #e53935 | `color.surface.chrome` #090707 | 4.75 | 3 | pass |
| urgent fill against the bar | error | ui | `color.status.danger.fill` #dc282e | `color.surface.chrome` #090707 | 4.20 | 3 | pass |
| visible-workspace fill against the bar | — | ui | `color.interaction.selection.bg` #531310 | `color.surface.chrome` #090707 | 1.41 | 1 | pass |
| inactive tab indicator | container-inactive | ui | `color.border.selected-indicator-inactive` #9e231f | `color.surface.chrome` #090707 | 2.60 | 1 | pass |
| unfocused window frames (decorative) | — | ui | `color.border.default` #531310 | `color.surface.desktop` #000000 | 1.47 | 1 | pass |
| status block separators (decorative) | — | ui | `color.text.muted` #bd787d | `color.surface.chrome` #090707 | 5.88 | 1 | pass |
| status glyphs (decorative) | — | ui | `color.icon.decorative` #a3676b | `color.surface.chrome` #090707 | 4.51 | 1 | pass |
| icon-button default text | default | text | `color.action.tertiary.text` #e99499 | `color.action.secondary.bg` #000000 | 9.16 | 4.5 | pass |
| icon-button hover text | hover | text | `color.action.tertiary.text` #e99499 | `color.action.tertiary.hover-bg` #1c0a09 | 8.35 | 4.5 | pass |
| icon-button focus-visible text | focus-visible | text | `color.action.tertiary.text` #e99499 | `color.action.secondary.bg` #000000 | 9.16 | 4.5 | pass |
| icon-button focus-visible outline | focus-visible | ui | `color.interaction.focus.ring` #e53935 | `color.action.secondary.bg` #000000 | 4.97 | 3 | pass |
| icon-button active text | active | text | `color.action.tertiary.text` #e99499 | `color.interaction.pressed.bg` #420f0c | 7.02 | 4.5 | pass |
| icon-button active border | active | ui | `color.border.active` #e53935 | `color.interaction.pressed.bg` #420f0c | 3.81 | 3 | pass |
| icon-button toggled text | toggled | text | `color.interaction.selection.text` #f4eeee | `color.interaction.selection.bg` #531310 | 12.47 | 4.5 | pass |
| icon-button toggled+focus-visible text | toggled+focus-visible | text | `color.interaction.selection.text` #f4eeee | `color.interaction.selection.bg` #531310 | 12.47 | 4.5 | pass |
| icon-button toggled+focus-visible outline | toggled+focus-visible | ui | `color.interaction.focus.ring-container` #ffa2a7 | `color.interaction.selection.bg` #531310 | 7.48 | 3 | pass |
| toggled border against the panel | toggled | ui | `color.border.selected-indicator` #e53935 | `color.surface.default` #100c0c | 4.60 | 3 | pass |
| toggled fill against the panel (decorative) | toggled | ui | `color.interaction.selection.bg` #531310 | `color.surface.default` #100c0c | 1.36 | 3 | waived: aria-pressed, the filled icon form and the 1px selected-indicator border carry the state; the fill is not the boundary |
| disabled icon (exempt; house floor 3:1) | disabled | ui | `color.text.disabled` #8a5559 | `color.interaction.disabled.bg` #160b0b | 3.24 | 3 | pass |
| disabled border (exempt) | disabled | ui | `color.border.disabled` #7d1310 | `color.interaction.disabled.bg` #160b0b | 1.82 | 1 | pass |
| kbd default text | default | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| kbd default border | default | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| Primary content | — | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| Supporting content | — | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| Control boundary | — | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| link default text | default | text | `color.text.link` #f73f35 | `color.surface.canvas` #000000 | 5.72 | 4.5 | pass |
| link hover text | hover | text | `color.text.link-hover` #f4eeee | `color.interaction.hover.bg-strong` #630f0d | 11.41 | 4.5 | pass |
| link focus-visible text | focus-visible | text | `color.text.link` #f73f35 | `color.surface.canvas` #000000 | 5.72 | 4.5 | pass |
| link focus-visible outline | focus-visible | ui | `color.interaction.focus.ring` #e53935 | `color.surface.canvas` #000000 | 4.97 | 3 | pass |
| link visited text | visited | text | `color.text.link` #f73f35 | `color.surface.canvas` #000000 | 5.72 | 4.5 | pass |
| link current text | current | text | `color.text.bright` #ffa2a7 | `color.surface.canvas` #000000 | 10.98 | 4.5 | pass |
| link current border | current | ui | `color.border.selected-indicator` #e53935 | `color.surface.canvas` #000000 | 4.97 | 3 | pass |
| underline on the canvas | — | ui | `color.text.link-underline` #dc282e | `color.surface.canvas` #000000 | 4.39 | 3 | pass |
| underline on the panel | — | ui | `color.text.link-underline` #dc282e | `color.surface.default` #100c0c | 4.06 | 3 | pass |
| link text on the panel | — | text | `color.text.link` #f73f35 | `color.surface.default` #100c0c | 5.29 | 4.5 | pass |
| link text on raised surfaces | — | text | `color.text.link` #f73f35 | `color.surface.raised` #160b0b | 5.26 | 4.5 | pass |
| ring on the hover fill | hover | ui | `color.interaction.focus.ring` #e53935 | `color.interaction.hover.bg-strong` #630f0d | 3.10 | 3 | pass |
| list default text | default | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| list default border | default | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| list hover text | hover | text | `color.text.default` #e99499 | `color.interaction.hover.bg` #1c0a09 | 8.35 | 4.5 | pass |
| list focus-visible text | focus-visible | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| list focus-visible outline | focus-visible | ui | `color.interaction.focus.ring` #e53935 | `color.surface.default` #100c0c | 4.60 | 3 | pass |
| list selected text | selected | text | `color.interaction.selection.text` #f4eeee | `color.interaction.selection.bg` #531310 | 12.47 | 4.5 | pass |
| list selected+focus-visible text | selected+focus-visible | text | `color.interaction.selection.text` #f4eeee | `color.interaction.selection.bg` #531310 | 12.47 | 4.5 | pass |
| list selected+focus-visible outline | selected+focus-visible | ui | `color.interaction.focus.ring-container` #ffa2a7 | `color.interaction.selection.bg` #531310 | 7.48 | 3 | pass |
| list selected+container-inactive text | selected+container-inactive | text | `color.interaction.selection.inactive-text` #e99499 | `color.interaction.selection.inactive-bg` #420f0c | 7.02 | 4.5 | pass |
| metadata column on the list surface | — | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| metadata column on the hover fill | hover | text | `color.text.muted` #bd787d | `color.interaction.hover.bg` #1c0a09 | 5.60 | 4.5 | pass |
| disabled option text (exempt; house floor 3:1) | disabled | ui | `color.text.disabled` #8a5559 | `color.surface.default` #100c0c | 3.26 | 3 | pass |
| row divider (decorative) | — | ui | `color.border.divider` #2b0e0d | `color.surface.default` #100c0c | 1.08 | 1 | pass |
| loading-indicator default text | default | text | `color.text.default` #e99499 | `color.surface.canvas` #000000 | 9.16 | 4.5 | pass |
| loading-indicator reduced-motion text | reduced-motion | text | `color.text.default` #e99499 | `color.surface.canvas` #000000 | 9.16 | 4.5 | pass |
| glyph on the canvas | — | text | `color.text.muted` #bd787d | `color.surface.canvas` #000000 | 6.15 | 4.5 | pass |
| block label on the panel surface | — | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| block glyph on the panel surface | — | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| block edge (decorative) | — | ui | `color.border.divider` #2b0e0d | `color.surface.canvas` #000000 | 1.17 | 1 | pass |
| menu default text | default | text | `color.text.default` #e99499 | `color.surface.raised` #160b0b | 8.43 | 4.5 | pass |
| menu default border | default | ui | `color.border.overlay` #e53935 | `color.surface.raised` #160b0b | 4.57 | 3 | pass |
| menu hover text | hover | text | `color.text.link-hover` #f4eeee | `color.interaction.hover.bg-strong` #630f0d | 11.41 | 4.5 | pass |
| menu focus-visible text | focus-visible | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| menu focus-visible border | focus-visible | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| menu focus-visible outline | focus-visible | ui | `color.interaction.focus.ring` #e53935 | `color.surface.default` #100c0c | 4.60 | 3 | pass |
| menu open text | open | text | `color.text.default` #e99499 | `color.interaction.pressed.bg` #420f0c | 7.02 | 4.5 | pass |
| menu open border | open | ui | `color.border.active` #e53935 | `color.interaction.pressed.bg` #420f0c | 3.81 | 3 | pass |
| menu closed text | closed | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| menu closed border | closed | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| menu checked text | checked | text | `color.text.bright` #ffa2a7 | `color.surface.raised` #160b0b | 10.10 | 4.5 | pass |
| menu selected text | selected | text | `color.interaction.selection.text` #f4eeee | `color.interaction.selection.bg` #531310 | 12.47 | 4.5 | pass |
| menu selected outline | selected | ui | `color.interaction.focus.ring-container` #ffa2a7 | `color.interaction.selection.bg` #531310 | 7.48 | 3 | pass |
| shortcut hint on the raised surface | — | text | `color.text.muted` #bd787d | `color.surface.raised` #160b0b | 5.66 | 4.5 | pass |
| disabled item text (exempt; house floor 3:1) | disabled | ui | `color.text.disabled` #8a5559 | `color.surface.raised` #160b0b | 3.24 | 3 | pass |
| item separator (decorative; role separator carries the grouping) | — | ui | `color.border.divider` #2b0e0d | `color.surface.raised` #160b0b | 1.08 | 1 | pass |
| submenu chevron (decorative; aria-haspopup carries the meaning) | — | ui | `color.icon.decorative` #a3676b | `color.surface.raised` #160b0b | 4.33 | 1 | pass |
| multiselect default text | default | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| multiselect default border | default | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| Primary content | — | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| Supporting content | — | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| Control boundary | — | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| number-field default text | default | text | `color.text.default` #e99499 | `color.surface.input` #000000 | 9.16 | 4.5 | pass |
| number-field default border | default | ui | `color.border.control` #a3676b | `color.surface.input` #000000 | 4.71 | 3 | pass |
| number-field hover text | hover | text | `color.text.default` #e99499 | `color.interaction.hover.bg` #1c0a09 | 8.35 | 4.5 | pass |
| number-field hover border | hover | ui | `color.border.control` #a3676b | `color.interaction.hover.bg` #1c0a09 | 4.29 | 3 | pass |
| number-field focus-visible text | focus-visible | text | `color.text.default` #e99499 | `color.surface.input` #000000 | 9.16 | 4.5 | pass |
| number-field focus-visible border | focus-visible | ui | `color.border.active` #e53935 | `color.surface.input` #000000 | 4.97 | 3 | pass |
| number-field focus-visible outline | focus-visible | ui | `color.interaction.focus.ring` #e53935 | `color.surface.input` #000000 | 4.97 | 3 | pass |
| number-field invalid text | invalid | text | `color.text.default` #e99499 | `color.surface.input` #000000 | 9.16 | 4.5 | pass |
| number-field invalid border | invalid | ui | `color.status.danger.border` #e53935 | `color.surface.input` #000000 | 4.97 | 3 | pass |
| number-field invalid+focus-visible text | invalid+focus-visible | text | `color.text.default` #e99499 | `color.surface.input` #000000 | 9.16 | 4.5 | pass |
| number-field invalid+focus-visible border | invalid+focus-visible | ui | `color.status.danger.border` #e53935 | `color.surface.input` #000000 | 4.97 | 3 | pass |
| number-field invalid+focus-visible outline | invalid+focus-visible | ui | `color.interaction.focus.ring-container` #ffa2a7 | `color.surface.input` #000000 | 10.98 | 3 | pass |
| number-field read-only text | read-only | text | `color.text.default` #e99499 | `color.surface.canvas` #000000 | 9.16 | 4.5 | pass |
| unit affix on the input surface | — | text | `color.text.muted` #bd787d | `color.surface.input` #000000 | 6.15 | 4.5 | pass |
| stepper glyphs on the input surface | — | text | `color.action.tertiary.text` #e99499 | `color.surface.input` #000000 | 9.16 | 4.5 | pass |
| stepper glyphs on their hover fill | hover | text | `color.action.tertiary.text` #e99499 | `color.action.tertiary.hover-bg` #1c0a09 | 8.35 | 4.5 | pass |
| validation message on the panel surface | — | text | `color.status.danger.text` #f73f35 | `color.surface.default` #100c0c | 5.29 | 4.5 | pass |
| help text on the panel surface | — | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| disabled text (exempt; house floor 3:1) | disabled | ui | `color.text.disabled` #8a5559 | `color.interaction.disabled.bg` #160b0b | 3.24 | 3 | pass |
| disabled border (exempt) | disabled | ui | `color.border.disabled` #7d1310 | `color.interaction.disabled.bg` #160b0b | 1.82 | 1 | pass |
| affix and stepper dividers (decorative) | — | ui | `color.border.divider` #2b0e0d | `color.surface.input` #000000 | 1.17 | 1 | pass |
| read-only bottom edge (decorative; the field has no box) | read-only | ui | `color.border.divider` #2b0e0d | `color.surface.canvas` #000000 | 1.17 | 1 | pass |
| pagination default text | default | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| pagination hover text | hover | text | `color.text.default` #e99499 | `color.interaction.hover.bg` #1c0a09 | 8.35 | 4.5 | pass |
| pagination focus-visible text | focus-visible | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| pagination focus-visible outline | focus-visible | ui | `color.interaction.focus.ring` #e53935 | `color.surface.default` #100c0c | 4.60 | 3 | pass |
| pagination current text | current | text | `color.text.on-selection` #f4eeee | `color.interaction.selection.bg` #531310 | 12.47 | 4.5 | pass |
| ring on the current page when focused | current | ui | `color.interaction.focus.ring-container` #ffa2a7 | `color.interaction.selection.bg` #531310 | 7.48 | 3 | pass |
| disabled previous/next (exempt; house floor 3:1) | disabled | ui | `color.text.disabled` #8a5559 | `color.surface.default` #100c0c | 3.26 | 3 | pass |
| gap ellipsis on the panel surface | — | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| popover default text | default | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| popover default border | default | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| Primary content | — | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| Supporting content | — | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| Control boundary | — | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| progress default text | default | text | `color.text.default` #e99499 | `color.surface.canvas` #000000 | 9.16 | 4.5 | pass |
| progress default border | default | ui | `color.border.control` #a3676b | `color.surface.canvas` #000000 | 4.71 | 3 | pass |
| progress error text | error | text | `color.status.danger.text` #f73f35 | `color.surface.canvas` #000000 | 5.72 | 4.5 | pass |
| progress error border | error | ui | `color.status.danger.border` #e53935 | `color.surface.canvas` #000000 | 4.97 | 3 | pass |
| label on the canvas | — | text | `color.text.bright` #ffa2a7 | `color.surface.canvas` #000000 | 10.98 | 4.5 | pass |
| detail line on the canvas | — | text | `color.text.muted` #bd787d | `color.surface.canvas` #000000 | 6.15 | 4.5 | pass |
| value fill against the track | — | ui | `color.action.primary.bg` #7d1310 | `color.interaction.pressed.bg` #420f0c | 1.51 | 3 | waived: the fill measures 1.51:1 against the track; the value is carried by the visible percentage text and the native value, and the track boundary (border.control, 4.45:1) marks the full extent |
| stripes (decorative pattern) | busy | ui | `color.action.primary.bg` #7d1310 | `color.interaction.pressed.bg` #420f0c | 1.51 | 1 | pass |
| error fill against the track | error | ui | `color.status.danger.fill` #dc282e | `color.interaction.pressed.bg` #420f0c | 3.36 | 3 | pass |
| radio-group default border | default | ui | `color.border.control` #a3676b | `color.surface.input` #000000 | 4.71 | 3 | pass |
| radio-group hover border | hover | ui | `color.border.control` #a3676b | `color.interaction.hover.bg` #1c0a09 | 4.29 | 3 | pass |
| radio-group focus-visible border | focus-visible | ui | `color.border.control` #a3676b | `color.surface.input` #000000 | 4.71 | 3 | pass |
| radio-group focus-visible outline | focus-visible | ui | `color.interaction.focus.ring` #e53935 | `color.surface.input` #000000 | 4.97 | 3 | pass |
| radio-group checked text | checked | text | `color.action.primary.text` #f4eeee | `color.action.primary.bg` #7d1310 | 9.28 | 4.5 | pass |
| radio-group checked+focus-visible text | checked+focus-visible | text | `color.action.primary.text` #f4eeee | `color.action.primary.bg` #7d1310 | 9.28 | 4.5 | pass |
| radio-group checked+focus-visible outline | checked+focus-visible | ui | `color.interaction.focus.ring-container` #ffa2a7 | `color.action.primary.bg` #7d1310 | 5.57 | 3 | pass |
| radio-group invalid border | invalid | ui | `color.status.danger.border` #e53935 | `color.surface.input` #000000 | 4.97 | 3 | pass |
| radio-group required border | required | ui | `color.border.control` #a3676b | `color.surface.input` #000000 | 4.71 | 3 | pass |
| option text on the panel surface | — | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| legend on the panel surface | — | text | `color.text.bright` #ffa2a7 | `color.surface.default` #100c0c | 10.17 | 4.5 | pass |
| checked radio edge against the panel (the border stays border.control) | checked | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| inner square on the hovered checked fill | hover | ui | `color.action.primary.text` #f4eeee | `color.action.primary.hover-bg` #911410 | 7.92 | 3 | pass |
| validation message on the panel surface | — | text | `color.status.danger.text` #f73f35 | `color.surface.default` #100c0c | 5.29 | 4.5 | pass |
| disabled option text (exempt; house floor 3:1) | disabled | ui | `color.text.disabled` #8a5559 | `color.surface.default` #100c0c | 3.26 | 3 | pass |
| disabled inner square (exempt; house floor 3:1) | disabled | ui | `color.text.disabled` #8a5559 | `color.interaction.disabled.bg` #160b0b | 3.24 | 3 | pass |
| disabled border (exempt) | disabled | ui | `color.border.disabled` #7d1310 | `color.interaction.disabled.bg` #160b0b | 1.82 | 1 | pass |
| range default text | default | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| range default border | default | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| Primary content | — | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| Supporting content | — | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| Control boundary | — | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| repeater default text | default | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| repeater default border | default | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| Primary content | — | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| Supporting content | — | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| Control boundary | — | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| search-field default text | default | text | `color.text.default` #e99499 | `color.surface.input` #000000 | 9.16 | 4.5 | pass |
| search-field default border | default | ui | `color.border.control` #a3676b | `color.surface.input` #000000 | 4.71 | 3 | pass |
| search-field hover text | hover | text | `color.text.default` #e99499 | `color.interaction.hover.bg` #1c0a09 | 8.35 | 4.5 | pass |
| search-field hover border | hover | ui | `color.border.control` #a3676b | `color.interaction.hover.bg` #1c0a09 | 4.29 | 3 | pass |
| search-field focus-visible text | focus-visible | text | `color.text.default` #e99499 | `color.surface.input` #000000 | 9.16 | 4.5 | pass |
| search-field focus-visible border | focus-visible | ui | `color.border.active` #e53935 | `color.surface.input` #000000 | 4.97 | 3 | pass |
| search-field focus-visible outline | focus-visible | ui | `color.interaction.focus.ring` #e53935 | `color.surface.input` #000000 | 4.97 | 3 | pass |
| search-field placeholder-shown text | placeholder-shown | text | `color.text.placeholder` #a3676b | `color.surface.input` #000000 | 4.71 | 4.5 | pass |
| search-field placeholder-shown border | placeholder-shown | ui | `color.border.control` #a3676b | `color.surface.input` #000000 | 4.71 | 3 | pass |
| search-field loading text | loading | text | `color.text.default` #e99499 | `color.surface.input` #000000 | 9.16 | 4.5 | pass |
| search-field loading border | loading | ui | `color.border.control` #a3676b | `color.surface.input` #000000 | 4.71 | 3 | pass |
| search-field no-results text | no-results | text | `color.text.default` #e99499 | `color.surface.input` #000000 | 9.16 | 4.5 | pass |
| search-field no-results border | no-results | ui | `color.border.control` #a3676b | `color.surface.input` #000000 | 4.71 | 3 | pass |
| magnifier on the input surface | — | ui | `color.icon.default` #e99499 | `color.surface.input` #000000 | 9.16 | 3 | pass |
| loading glyph and kbd hint on the input surface | — | text | `color.text.muted` #bd787d | `color.surface.input` #000000 | 6.15 | 4.5 | pass |
| clear glyph on the input surface | — | text | `color.action.tertiary.text` #e99499 | `color.surface.input` #000000 | 9.16 | 4.5 | pass |
| clear glyph on its hover fill | hover | text | `color.action.tertiary.text` #e99499 | `color.action.tertiary.hover-bg` #1c0a09 | 8.35 | 4.5 | pass |
| help and no-results status on the panel surface | — | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| kbd hint edge (decorative) | — | ui | `color.border.default` #531310 | `color.surface.input` #000000 | 1.47 | 1 | pass |
| disabled text (exempt; house floor 3:1) | disabled | ui | `color.text.disabled` #8a5559 | `color.interaction.disabled.bg` #160b0b | 3.24 | 3 | pass |
| disabled border (exempt) | disabled | ui | `color.border.disabled` #7d1310 | `color.interaction.disabled.bg` #160b0b | 1.82 | 1 | pass |
| segmented-control default text | default | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| segmented-control default border | default | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| Primary content | — | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| Supporting content | — | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| Control boundary | — | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| select default text | default | text | `color.text.default` #e99499 | `color.surface.input` #000000 | 9.16 | 4.5 | pass |
| select default border | default | ui | `color.border.control` #a3676b | `color.surface.input` #000000 | 4.71 | 3 | pass |
| select hover text | hover | text | `color.text.default` #e99499 | `color.interaction.hover.bg` #1c0a09 | 8.35 | 4.5 | pass |
| select hover border | hover | ui | `color.border.control` #a3676b | `color.interaction.hover.bg` #1c0a09 | 4.29 | 3 | pass |
| select focus-visible text | focus-visible | text | `color.text.default` #e99499 | `color.surface.input` #000000 | 9.16 | 4.5 | pass |
| select focus-visible border | focus-visible | ui | `color.border.active` #e53935 | `color.surface.input` #000000 | 4.97 | 3 | pass |
| select focus-visible outline | focus-visible | ui | `color.interaction.focus.ring` #e53935 | `color.surface.input` #000000 | 4.97 | 3 | pass |
| select required text | required | text | `color.text.default` #e99499 | `color.surface.input` #000000 | 9.16 | 4.5 | pass |
| select required border | required | ui | `color.border.control` #a3676b | `color.surface.input` #000000 | 4.71 | 3 | pass |
| select invalid text | invalid | text | `color.text.default` #e99499 | `color.surface.input` #000000 | 9.16 | 4.5 | pass |
| select invalid border | invalid | ui | `color.status.danger.border` #e53935 | `color.surface.input` #000000 | 4.97 | 3 | pass |
| select invalid+focus-visible text | invalid+focus-visible | text | `color.text.default` #e99499 | `color.surface.input` #000000 | 9.16 | 4.5 | pass |
| select invalid+focus-visible border | invalid+focus-visible | ui | `color.status.danger.border` #e53935 | `color.surface.input` #000000 | 4.97 | 3 | pass |
| select invalid+focus-visible outline | invalid+focus-visible | ui | `color.interaction.focus.ring-container` #ffa2a7 | `color.surface.input` #000000 | 10.98 | 3 | pass |
| chevron on the input surface | — | ui | `color.icon.default` #e99499 | `color.surface.input` #000000 | 9.16 | 3 | pass |
| option text in the themed list | — | text | `color.text.default` #e99499 | `color.surface.raised` #160b0b | 8.43 | 4.5 | pass |
| group label in the themed list | — | text | `color.text.muted` #bd787d | `color.surface.raised` #160b0b | 5.66 | 4.5 | pass |
| selected option | — | text | `color.interaction.selection.text` #f4eeee | `color.interaction.selection.bg` #531310 | 12.47 | 4.5 | pass |
| validation message on the panel surface | — | text | `color.status.danger.text` #f73f35 | `color.surface.default` #100c0c | 5.29 | 4.5 | pass |
| help text on the panel surface | — | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| disabled text (exempt; house floor 3:1) | disabled | ui | `color.text.disabled` #8a5559 | `color.interaction.disabled.bg` #160b0b | 3.24 | 3 | pass |
| disabled border (exempt) | disabled | ui | `color.border.disabled` #7d1310 | `color.interaction.disabled.bg` #160b0b | 1.82 | 1 | pass |
| settings-panel default text | default | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| settings-panel focus-visible text | focus-visible | text | `color.text.default` #e99499 | `color.surface.input` #000000 | 9.16 | 4.5 | pass |
| settings-panel focus-visible border | focus-visible | ui | `color.border.control` #a3676b | `color.surface.input` #000000 | 4.71 | 3 | pass |
| settings-panel focus-visible outline | focus-visible | ui | `color.interaction.focus.ring` #e53935 | `color.surface.input` #000000 | 4.97 | 3 | pass |
| settings-panel invalid text | invalid | text | `color.text.default` #e99499 | `color.surface.input` #000000 | 9.16 | 4.5 | pass |
| settings-panel invalid border | invalid | ui | `color.status.danger.border` #e53935 | `color.surface.input` #000000 | 4.97 | 3 | pass |
| settings-panel busy text | busy | text | `color.action.primary.text` #f4eeee | `color.action.primary.bg` #7d1310 | 9.28 | 4.5 | pass |
| title, legend and the current section on the panel surface | — | text | `color.text.bright` #ffa2a7 | `color.surface.default` #100c0c | 10.17 | 4.5 | pass |
| lead and help text on the panel surface | — | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| validation message on the panel surface | invalid | text | `color.status.danger.text` #f73f35 | `color.surface.default` #100c0c | 5.29 | 4.5 | pass |
| current-section indicator bar | — | ui | `color.border.selected-indicator` #e53935 | `color.surface.default` #100c0c | 4.60 | 3 | pass |
| control boundaries on the panel surface | — | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| Reset button boundary | — | ui | `color.action.secondary.border` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| panel edge (decorative) | — | ui | `color.border.default` #531310 | `color.surface.default` #100c0c | 1.36 | 1 | pass |
| sidebar, group and footer rules (decorative) | — | ui | `color.border.divider` #2b0e0d | `color.surface.default` #100c0c | 1.08 | 1 | pass |
| sidebar-nav default text | default | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| sidebar-nav hover text | hover | text | `color.text.default` #e99499 | `color.interaction.hover.bg` #1c0a09 | 8.35 | 4.5 | pass |
| sidebar-nav focus-visible text | focus-visible | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| sidebar-nav focus-visible outline | focus-visible | ui | `color.interaction.focus.ring` #e53935 | `color.surface.default` #100c0c | 4.60 | 3 | pass |
| sidebar-nav current text | current | text | `color.text.bright` #ffa2a7 | `color.surface.default` #100c0c | 10.17 | 4.5 | pass |
| sidebar-nav current border | current | ui | `color.border.selected-indicator` #e53935 | `color.surface.default` #100c0c | 4.60 | 3 | pass |
| sidebar-nav selected text | selected | text | `color.interaction.selection.text` #f4eeee | `color.interaction.selection.bg` #531310 | 12.47 | 4.5 | pass |
| sidebar-nav selected+container-inactive text | selected+container-inactive | text | `color.interaction.selection.inactive-text` #e99499 | `color.interaction.selection.inactive-bg` #420f0c | 7.02 | 4.5 | pass |
| sidebar-nav expanded text | expanded | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| sidebar-nav collapsed text | collapsed | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| ring on a selected item when focused | selected | ui | `color.interaction.focus.ring-container` #ffa2a7 | `color.interaction.selection.bg` #531310 | 7.48 | 3 | pass |
| group chevron (decorative; aria-expanded carries the state) | — | ui | `color.icon.decorative` #a3676b | `color.surface.default` #100c0c | 4.36 | 1 | pass |
| rail edge (decorative) | — | ui | `color.border.divider` #2b0e0d | `color.surface.default` #100c0c | 1.08 | 1 | pass |
| skeleton default text | default | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| skeleton default border | default | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| Primary content | — | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| Supporting content | — | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| Control boundary | — | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| skip-link default text | default | text | `color.text.bright` #ffa2a7 | `color.surface.raised` #160b0b | 10.10 | 4.5 | pass |
| skip-link default border | default | ui | `color.border.overlay` #e53935 | `color.surface.raised` #160b0b | 4.57 | 3 | pass |
| skip-link focus-visible text | focus-visible | text | `color.text.bright` #ffa2a7 | `color.surface.raised` #160b0b | 10.10 | 4.5 | pass |
| skip-link focus-visible border | focus-visible | ui | `color.border.overlay` #e53935 | `color.surface.raised` #160b0b | 4.57 | 3 | pass |
| skip-link focus-visible outline | focus-visible | ui | `color.interaction.focus.ring` #e53935 | `color.surface.raised` #160b0b | 4.57 | 3 | pass |
| ring on the raised surface | focus-visible | ui | `color.interaction.focus.ring` #e53935 | `color.surface.raised` #160b0b | 4.57 | 3 | pass |
| switch default border | default | ui | `color.border.control` #a3676b | `color.surface.input` #000000 | 4.71 | 3 | pass |
| switch hover border | hover | ui | `color.border.control` #a3676b | `color.interaction.hover.bg` #1c0a09 | 4.29 | 3 | pass |
| switch focus-visible border | focus-visible | ui | `color.border.control` #a3676b | `color.surface.input` #000000 | 4.71 | 3 | pass |
| switch focus-visible outline | focus-visible | ui | `color.interaction.focus.ring` #e53935 | `color.surface.input` #000000 | 4.97 | 3 | pass |
| switch checked text | checked | text | `color.action.primary.text` #f4eeee | `color.action.primary.bg` #7d1310 | 9.28 | 4.5 | pass |
| switch checked+focus-visible text | checked+focus-visible | text | `color.action.primary.text` #f4eeee | `color.action.primary.bg` #7d1310 | 9.28 | 4.5 | pass |
| switch checked+focus-visible outline | checked+focus-visible | ui | `color.interaction.focus.ring-container` #ffa2a7 | `color.action.primary.bg` #7d1310 | 5.57 | 3 | pass |
| thumb on the off track | — | ui | `color.text.default` #e99499 | `color.surface.input` #000000 | 9.16 | 3 | pass |
| thumb on the hovered off track | hover | ui | `color.text.default` #e99499 | `color.interaction.hover.bg` #1c0a09 | 8.35 | 3 | pass |
| thumb on the hovered on track | hover | ui | `color.action.primary.text` #f4eeee | `color.action.primary.hover-bg` #911410 | 7.92 | 3 | pass |
| on track edge against the panel (the border stays border.control) | checked | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| visible name on the panel surface | — | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| Off / On word on the panel surface | — | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| disabled thumb (exempt; house floor 3:1) | disabled | ui | `color.text.disabled` #8a5559 | `color.interaction.disabled.bg` #160b0b | 3.24 | 3 | pass |
| disabled name (exempt; house floor 3:1) | disabled | ui | `color.text.disabled` #8a5559 | `color.surface.default` #100c0c | 3.26 | 3 | pass |
| disabled border (exempt) | disabled | ui | `color.border.disabled` #7d1310 | `color.interaction.disabled.bg` #160b0b | 1.82 | 1 | pass |
| table default text | default | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| table hover text | hover | text | `color.text.default` #e99499 | `color.interaction.hover.bg` #1c0a09 | 8.35 | 4.5 | pass |
| table selected text | selected | text | `color.interaction.selection.text` #f4eeee | `color.interaction.selection.bg` #531310 | 12.47 | 4.5 | pass |
| table selected+focus-visible text | selected+focus-visible | text | `color.interaction.selection.text` #f4eeee | `color.interaction.selection.bg` #531310 | 12.47 | 4.5 | pass |
| table selected+focus-visible outline | selected+focus-visible | ui | `color.interaction.focus.ring-container` #ffa2a7 | `color.interaction.selection.bg` #531310 | 7.48 | 3 | pass |
| table selected+container-inactive text | selected+container-inactive | text | `color.interaction.selection.inactive-text` #e99499 | `color.interaction.selection.inactive-bg` #420f0c | 7.02 | 4.5 | pass |
| table sorted text | sorted | text | `color.text.bright` #ffa2a7 | `color.surface.default` #100c0c | 10.17 | 4.5 | pass |
| table empty text | empty | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| table loading text | loading | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| header text | — | text | `color.text.bright` #ffa2a7 | `color.surface.default` #100c0c | 10.17 | 4.5 | pass |
| sticky header text on the raised surface | — | text | `color.text.bright` #ffa2a7 | `color.surface.raised` #160b0b | 10.10 | 4.5 | pass |
| caption | — | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| ring on a sort button | — | ui | `color.interaction.focus.ring` #e53935 | `color.surface.default` #100c0c | 4.60 | 3 | pass |
| header rule (decorative; thead carries the structure) | — | ui | `color.border.strong` #9e231f | `color.surface.default` #100c0c | 2.51 | 1 | pass |
| row divider (decorative) | — | ui | `color.border.divider` #2b0e0d | `color.surface.default` #100c0c | 1.08 | 1 | pass |
| tabs default text | default | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| tabs hover text | hover | text | `color.text.default` #e99499 | `color.interaction.hover.bg` #1c0a09 | 8.35 | 4.5 | pass |
| tabs focus-visible text | focus-visible | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| tabs focus-visible outline | focus-visible | ui | `color.interaction.focus.ring` #e53935 | `color.surface.default` #100c0c | 4.60 | 3 | pass |
| tabs selected text | selected | text | `color.text.bright` #ffa2a7 | `color.surface.default` #100c0c | 10.17 | 4.5 | pass |
| tabs selected border | selected | ui | `color.border.selected-indicator` #e53935 | `color.surface.default` #100c0c | 4.60 | 3 | pass |
| tabs selected+focus-visible text | selected+focus-visible | text | `color.text.bright` #ffa2a7 | `color.surface.default` #100c0c | 10.17 | 4.5 | pass |
| tabs selected+focus-visible border | selected+focus-visible | ui | `color.border.selected-indicator` #e53935 | `color.surface.default` #100c0c | 4.60 | 3 | pass |
| tabs selected+focus-visible outline | selected+focus-visible | ui | `color.interaction.focus.ring` #e53935 | `color.surface.default` #100c0c | 4.60 | 3 | pass |
| tabs selected+container-inactive text | selected+container-inactive | text | `color.text.bright` #ffa2a7 | `color.surface.default` #100c0c | 10.17 | 4.5 | pass |
| inactive indicator (decorative; the selected tab keeps aria-selected and bright text) | selected+container-inactive | ui | `color.border.selected-indicator-inactive` #9e231f | `color.surface.default` #100c0c | 2.51 | 1 | pass |
| disabled tab text (exempt; house floor 3:1) | disabled | ui | `color.text.disabled` #8a5559 | `color.surface.default` #100c0c | 3.26 | 3 | pass |
| tablist bottom rule (decorative) | — | ui | `color.border.divider` #2b0e0d | `color.surface.default` #100c0c | 1.08 | 1 | pass |
| terminal default text | default | text | `color.terminal.fg` #e99499 | `color.terminal.bg` #0c0909 | 8.65 | 4.5 | pass |
| terminal default border | default | ui | `color.border.control` #a3676b | `color.terminal.bg` #0c0909 | 4.45 | 3 | pass |
| terminal focus-visible text | focus-visible | text | `color.terminal.fg` #e99499 | `color.terminal.bg` #0c0909 | 8.65 | 4.5 | pass |
| terminal focus-visible outline | focus-visible | ui | `color.interaction.focus.ring-container` #ffa2a7 | `color.terminal.bg` #0c0909 | 10.37 | 3 | pass |
| terminal selected text | selected | text | `color.terminal.selection-text` #f4eeee | `color.terminal.selection-bg` #911410 | 7.92 | 4.5 | pass |
| terminal busy text | busy | text | `color.terminal.bg` #0c0909 | `color.terminal.cursor` #e99499 | 8.65 | 4.5 | pass |
| terminal current text | current | text | `color.terminal.fg` #e99499 | `color.code.current-line` #1c0a09 | 8.35 | 4.5 | pass |
| hollow cursor outline at rest | — | ui | `color.terminal.cursor` #e99499 | `color.terminal.bg` #0c0909 | 8.65 | 3 | pass |
| cursor on the cursor line | current | ui | `color.terminal.cursor` #e99499 | `color.code.current-line` #1c0a09 | 8.35 | 3 | pass |
| prompt segment text on slot 4 | — | text | `color.terminal.selection-text` #f4eeee | `color.terminal.ansi.4` #8c1212 | 8.28 | 4.5 | pass |
| branch segment text on slot 3 | — | text | `color.terminal.bg` #0c0909 | `color.terminal.ansi.3` #d4868b | 7.16 | 4.5 | pass |
| prompt arrow (the segment's own colour, decorative) | — | ui | `color.terminal.ansi.4` #8c1212 | `color.terminal.bg` #0c0909 | 2.09 | 1 | pass |
| dim text and grid labels | — | text | `color.text.muted` #bd787d | `color.terminal.bg` #0c0909 | 5.81 | 4.5 | pass |
| inverse text | — | text | `color.terminal.bg` #0c0909 | `color.terminal.fg` #e99499 | 8.65 | 4.5 | pass |
| slot 2 | — | text | `color.terminal.ansi.2` #bd787d | `color.terminal.bg` #0c0909 | 5.81 | 4.5 | pass |
| slot 3 | — | text | `color.terminal.ansi.3` #d4868b | `color.terminal.bg` #0c0909 | 7.16 | 4.5 | pass |
| slot 5 | — | text | `color.terminal.ansi.5` #f73f35 | `color.terminal.bg` #0c0909 | 5.40 | 4.5 | pass |
| slot 7 | — | text | `color.terminal.ansi.7` #ffa2a7 | `color.terminal.bg` #0c0909 | 10.37 | 4.5 | pass |
| slot 11 | — | text | `color.terminal.ansi.11` #b37175 | `color.terminal.bg` #0c0909 | 5.23 | 4.5 | pass |
| slot 1 (3.42:1) | — | text | `color.terminal.ansi.1` #c81a1a | `color.terminal.bg` #0c0909 | 3.42 | 4.5 | waived: heritage slot; documented as failing, programs choose slot semantics |
| slot 4 (2.09:1) | — | text | `color.terminal.ansi.4` #8c1212 | `color.terminal.bg` #0c0909 | 2.09 | 4.5 | waived: heritage slot; documented as failing, programs choose slot semantics |
| slot 6 (3.26:1) | — | text | `color.terminal.ansi.6` #9e474a | `color.terminal.bg` #0c0909 | 3.26 | 4.5 | waived: heritage slot; documented as failing, programs choose slot semantics |
| slot 8 (1.86:1) | — | text | `color.terminal.ansi.8` #7d1310 | `color.terminal.bg` #0c0909 | 1.86 | 4.5 | waived: heritage slot; documented as failing, programs choose slot semantics |
| slot 9 (2.70:1) | — | text | `color.terminal.ansi.9` #ab1612 | `color.terminal.bg` #0c0909 | 2.70 | 4.5 | waived: heritage slot; documented as failing, programs choose slot semantics |
| slot 10 (2.92:1) | — | text | `color.terminal.ansi.10` #ad2721 | `color.terminal.bg` #0c0909 | 2.92 | 4.5 | waived: heritage slot; documented as failing, programs choose slot semantics |
| slot 12 (2.12:1) | — | text | `color.terminal.ansi.12` #871f19 | `color.terminal.bg` #0c0909 | 2.12 | 4.5 | waived: heritage slot; documented as failing, programs choose slot semantics |
| slot 13 (4.43:1) | — | text | `color.terminal.ansi.13` #e82132 | `color.terminal.bg` #0c0909 | 4.43 | 4.5 | waived: heritage slot; documented as failing, programs choose slot semantics |
| slot 14 (4.28:1) | — | text | `color.terminal.ansi.14` #e0292f | `color.terminal.bg` #0c0909 | 4.28 | 4.5 | waived: heritage slot; documented as failing, programs choose slot semantics |
| slot 15 (4.45:1; not in the foundations list) | — | text | `color.terminal.ansi.15` #a3676b | `color.terminal.bg` #0c0909 | 4.45 | 4.5 | waived: heritage slot; documented as failing, programs choose slot semantics |
| slot 0 (1.00:1; it is the background) | — | text | `color.terminal.ansi.0` #0c0909 | `color.terminal.bg` #0c0909 | 1.00 | 4.5 | waived: heritage slot; slot 0 is the terminal background and is never a foreground on it |
| text-field default text | default | text | `color.text.default` #e99499 | `color.surface.input` #000000 | 9.16 | 4.5 | pass |
| text-field default border | default | ui | `color.border.control` #a3676b | `color.surface.input` #000000 | 4.71 | 3 | pass |
| text-field hover text | hover | text | `color.text.default` #e99499 | `color.interaction.hover.bg` #1c0a09 | 8.35 | 4.5 | pass |
| text-field hover border | hover | ui | `color.border.control` #a3676b | `color.interaction.hover.bg` #1c0a09 | 4.29 | 3 | pass |
| text-field focus-visible text | focus-visible | text | `color.text.default` #e99499 | `color.surface.input` #000000 | 9.16 | 4.5 | pass |
| text-field focus-visible border | focus-visible | ui | `color.border.active` #e53935 | `color.surface.input` #000000 | 4.97 | 3 | pass |
| text-field focus-visible outline | focus-visible | ui | `color.interaction.focus.ring` #e53935 | `color.surface.input` #000000 | 4.97 | 3 | pass |
| text-field placeholder-shown text | placeholder-shown | text | `color.text.placeholder` #a3676b | `color.surface.input` #000000 | 4.71 | 4.5 | pass |
| text-field placeholder-shown border | placeholder-shown | ui | `color.border.control` #a3676b | `color.surface.input` #000000 | 4.71 | 3 | pass |
| text-field invalid text | invalid | text | `color.text.default` #e99499 | `color.surface.input` #000000 | 9.16 | 4.5 | pass |
| text-field invalid border | invalid | ui | `color.status.danger.border` #e53935 | `color.surface.input` #000000 | 4.97 | 3 | pass |
| text-field invalid+focus-visible text | invalid+focus-visible | text | `color.text.default` #e99499 | `color.surface.input` #000000 | 9.16 | 4.5 | pass |
| text-field invalid+focus-visible border | invalid+focus-visible | ui | `color.status.danger.border` #e53935 | `color.surface.input` #000000 | 4.97 | 3 | pass |
| text-field invalid+focus-visible outline | invalid+focus-visible | ui | `color.interaction.focus.ring-container` #ffa2a7 | `color.surface.input` #000000 | 10.98 | 3 | pass |
| text-field read-only text | read-only | text | `color.text.default` #e99499 | `color.surface.canvas` #000000 | 9.16 | 4.5 | pass |
| validation message on the panel surface | — | text | `color.status.danger.text` #f73f35 | `color.surface.default` #100c0c | 5.29 | 4.5 | pass |
| help text on the panel surface | — | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| disabled text (exempt; house floor 3:1) | disabled | ui | `color.text.disabled` #8a5559 | `color.interaction.disabled.bg` #160b0b | 3.24 | 3 | pass |
| disabled border (exempt) | disabled | ui | `color.border.disabled` #7d1310 | `color.interaction.disabled.bg` #160b0b | 1.82 | 1 | pass |
| read-only bottom edge (decorative; the field has no box) | read-only | ui | `color.border.divider` #2b0e0d | `color.surface.canvas` #000000 | 1.17 | 1 | pass |
| textarea default text | default | text | `color.text.default` #e99499 | `color.surface.input` #000000 | 9.16 | 4.5 | pass |
| textarea default border | default | ui | `color.border.control` #a3676b | `color.surface.input` #000000 | 4.71 | 3 | pass |
| textarea hover text | hover | text | `color.text.default` #e99499 | `color.interaction.hover.bg` #1c0a09 | 8.35 | 4.5 | pass |
| textarea hover border | hover | ui | `color.border.control` #a3676b | `color.interaction.hover.bg` #1c0a09 | 4.29 | 3 | pass |
| textarea focus-visible text | focus-visible | text | `color.text.default` #e99499 | `color.surface.input` #000000 | 9.16 | 4.5 | pass |
| textarea focus-visible border | focus-visible | ui | `color.border.active` #e53935 | `color.surface.input` #000000 | 4.97 | 3 | pass |
| textarea focus-visible outline | focus-visible | ui | `color.interaction.focus.ring` #e53935 | `color.surface.input` #000000 | 4.97 | 3 | pass |
| textarea placeholder-shown text | placeholder-shown | text | `color.text.placeholder` #a3676b | `color.surface.input` #000000 | 4.71 | 4.5 | pass |
| textarea placeholder-shown border | placeholder-shown | ui | `color.border.control` #a3676b | `color.surface.input` #000000 | 4.71 | 3 | pass |
| textarea invalid text | invalid | text | `color.text.default` #e99499 | `color.surface.input` #000000 | 9.16 | 4.5 | pass |
| textarea invalid border | invalid | ui | `color.status.danger.border` #e53935 | `color.surface.input` #000000 | 4.97 | 3 | pass |
| textarea invalid+focus-visible text | invalid+focus-visible | text | `color.text.default` #e99499 | `color.surface.input` #000000 | 9.16 | 4.5 | pass |
| textarea invalid+focus-visible border | invalid+focus-visible | ui | `color.status.danger.border` #e53935 | `color.surface.input` #000000 | 4.97 | 3 | pass |
| textarea invalid+focus-visible outline | invalid+focus-visible | ui | `color.interaction.focus.ring-container` #ffa2a7 | `color.surface.input` #000000 | 10.98 | 3 | pass |
| textarea read-only text | read-only | text | `color.text.default` #e99499 | `color.surface.canvas` #000000 | 9.16 | 4.5 | pass |
| validation message on the panel surface | — | text | `color.status.danger.text` #f73f35 | `color.surface.default` #100c0c | 5.29 | 4.5 | pass |
| help and count on the panel surface | — | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| disabled text (exempt; house floor 3:1) | disabled | ui | `color.text.disabled` #8a5559 | `color.interaction.disabled.bg` #160b0b | 3.24 | 3 | pass |
| disabled border (exempt) | disabled | ui | `color.border.disabled` #7d1310 | `color.interaction.disabled.bg` #160b0b | 1.82 | 1 | pass |
| read-only bottom edge (decorative; the field has no box) | read-only | ui | `color.border.divider` #2b0e0d | `color.surface.canvas` #000000 | 1.17 | 1 | pass |
| time-picker default text | default | text | `color.text.default` #e99499 | `color.surface.input` #000000 | 9.16 | 4.5 | pass |
| time-picker default border | default | ui | `color.border.control` #a3676b | `color.surface.input` #000000 | 4.71 | 3 | pass |
| time-picker hover text | hover | text | `color.text.default` #e99499 | `color.interaction.hover.bg` #1c0a09 | 8.35 | 4.5 | pass |
| time-picker hover border | hover | ui | `color.border.control` #a3676b | `color.interaction.hover.bg` #1c0a09 | 4.29 | 3 | pass |
| time-picker focus-visible text | focus-visible | text | `color.text.default` #e99499 | `color.surface.input` #000000 | 9.16 | 4.5 | pass |
| time-picker focus-visible border | focus-visible | ui | `color.border.active` #e53935 | `color.surface.input` #000000 | 4.97 | 3 | pass |
| time-picker focus-visible outline | focus-visible | ui | `color.interaction.focus.ring` #e53935 | `color.surface.input` #000000 | 4.97 | 3 | pass |
| time-picker invalid text | invalid | text | `color.text.default` #e99499 | `color.surface.input` #000000 | 9.16 | 4.5 | pass |
| time-picker invalid border | invalid | ui | `color.status.danger.border` #e53935 | `color.surface.input` #000000 | 4.97 | 3 | pass |
| validation message on the canvas | — | text | `color.status.danger.text` #f73f35 | `color.surface.canvas` #000000 | 5.72 | 4.5 | pass |
| help text on the canvas | — | text | `color.text.muted` #bd787d | `color.surface.canvas` #000000 | 6.15 | 4.5 | pass |
| disabled text (exempt; house floor 3:1) | disabled | ui | `color.text.disabled` #8a5559 | `color.interaction.disabled.bg` #160b0b | 3.24 | 3 | pass |
| disabled border (exempt) | disabled | ui | `color.border.disabled` #7d1310 | `color.interaction.disabled.bg` #160b0b | 1.82 | 1 | pass |
| timeline default text | default | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| timeline default border | default | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| Primary content | — | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| Supporting content | — | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| Control boundary | — | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| toast default text | default | text | `color.text.default` #e99499 | `color.surface.canvas` #000000 | 9.16 | 4.5 | pass |
| toast default border | default | ui | `color.border.overlay` #e53935 | `color.surface.canvas` #000000 | 4.97 | 3 | pass |
| toast hover text | hover | text | `color.text.link-hover` #f4eeee | `color.interaction.hover.bg-strong` #630f0d | 11.41 | 4.5 | pass |
| toast focus-visible text | focus-visible | text | `color.action.tertiary.text` #e99499 | `color.surface.canvas` #000000 | 9.16 | 4.5 | pass |
| toast focus-visible outline | focus-visible | ui | `color.interaction.focus.ring` #e53935 | `color.surface.canvas` #000000 | 4.97 | 3 | pass |
| toast stacked text | stacked | text | `color.text.muted` #bd787d | `color.surface.canvas` #000000 | 6.15 | 4.5 | pass |
| toast stacked border | stacked | ui | `color.border.overlay` #e53935 | `color.surface.canvas` #000000 | 4.97 | 3 | pass |
| toast reduced-motion text | reduced-motion | text | `color.text.default` #e99499 | `color.surface.canvas` #000000 | 9.16 | 4.5 | pass |
| toast reduced-motion border | reduced-motion | ui | `color.border.overlay` #e53935 | `color.surface.canvas` #000000 | 4.97 | 3 | pass |
| glyph on the plain toast | — | text | `color.status.info.text` #7e9ebb | `color.surface.canvas` #000000 | 7.50 | 4.5 | pass |
| action text on the plain toast | — | text | `color.text.link` #f73f35 | `color.surface.canvas` #000000 | 5.72 | 4.5 | pass |
| warning glyph on its tint | — | text | `color.status.warning.text` #c9973f | `color.status.warning.tint` #1f1a0c | 6.59 | 4.5 | pass |
| warning border on its tint | — | ui | `color.status.warning.border` #c9973f | `color.status.warning.tint` #1f1a0c | 6.59 | 3 | pass |
| text on the warning tint | — | text | `color.text.default` #e99499 | `color.status.warning.tint` #1f1a0c | 7.56 | 4.5 | pass |
| success glyph on its tint | — | text | `color.status.success.text` #86a46f | `color.status.success.tint` #0f1a0e | 6.43 | 4.5 | pass |
| success border on its tint | — | ui | `color.status.success.border` #86a46f | `color.status.success.tint` #0f1a0e | 6.43 | 3 | pass |
| text on the success tint | — | text | `color.text.default` #e99499 | `color.status.success.tint` #0f1a0e | 7.80 | 4.5 | pass |
| info glyph on its tint | — | text | `color.status.info.text` #7e9ebb | `color.status.info.tint` #0f141c | 6.59 | 4.5 | pass |
| info border on its tint | — | ui | `color.status.info.border` #7e9ebb | `color.status.info.tint` #0f141c | 6.59 | 3 | pass |
| text on the info tint | — | text | `color.text.default` #e99499 | `color.status.info.tint` #0f141c | 8.06 | 4.5 | pass |
| neutral glyph on its tint | — | text | `color.status.neutral.text` #bd787d | `color.status.neutral.tint` #241010 | 5.31 | 4.5 | pass |
| neutral border on its tint | — | ui | `color.status.neutral.border` #a3676b | `color.status.neutral.tint` #241010 | 4.07 | 3 | pass |
| text on the neutral tint | — | text | `color.text.default` #e99499 | `color.status.neutral.tint` #241010 | 7.92 | 4.5 | pass |
| text, glyph and buttons on the danger fill | — | text | `color.status.danger.on-fill` #f9faf9 | `color.status.danger.fill` #dc282e | 4.58 | 4.5 | pass |
| the danger fill is its own boundary against the canvas | — | ui | `color.status.danger.fill` #dc282e | `color.surface.canvas` #000000 | 4.39 | 3 | pass |
| danger border on the danger fill (decorative) | — | ui | `color.status.danger.border` #e53935 | `color.status.danger.fill` #dc282e | 1.13 | 1 | pass |
| toolbar default text | default | text | `color.text.default` #e99499 | `color.surface.chrome` #090707 | 8.77 | 4.5 | pass |
| toolbar hover text | hover | text | `color.text.default` #e99499 | `color.interaction.hover.bg` #1c0a09 | 8.35 | 4.5 | pass |
| toolbar focus-visible text | focus-visible | text | `color.text.default` #e99499 | `color.surface.chrome` #090707 | 8.77 | 4.5 | pass |
| toolbar focus-visible outline | focus-visible | ui | `color.interaction.focus.ring` #e53935 | `color.surface.chrome` #090707 | 4.75 | 3 | pass |
| toolbar toggled text | toggled | text | `color.text.bright` #ffa2a7 | `color.interaction.pressed.bg` #420f0c | 8.41 | 4.5 | pass |
| toolbar toggled border | toggled | ui | `color.border.active` #e53935 | `color.interaction.pressed.bg` #420f0c | 3.81 | 3 | pass |
| disabled button text (exempt; house floor 3:1) | disabled | ui | `color.text.disabled` #8a5559 | `color.surface.chrome` #090707 | 3.37 | 3 | pass |
| separator (decorative; role separator carries the grouping) | — | ui | `color.border.default` #531310 | `color.surface.chrome` #090707 | 1.41 | 1 | pass |
| toolbar bottom edge (decorative) | — | ui | `color.border.divider` #2b0e0d | `color.surface.chrome` #090707 | 1.12 | 1 | pass |
| tooltip default text | default | text | `color.text.default` #e99499 | `color.surface.raised` #160b0b | 8.43 | 4.5 | pass |
| tooltip default border | default | ui | `color.border.overlay` #e53935 | `color.surface.raised` #160b0b | 4.57 | 3 | pass |
| tooltip hover text | hover | text | `color.action.tertiary.text` #e99499 | `color.action.tertiary.hover-bg` #1c0a09 | 8.35 | 4.5 | pass |
| tooltip focus-visible text | focus-visible | text | `color.action.tertiary.text` #e99499 | `color.surface.canvas` #000000 | 9.16 | 4.5 | pass |
| tooltip focus-visible outline | focus-visible | ui | `color.interaction.focus.ring` #e53935 | `color.surface.canvas` #000000 | 4.97 | 3 | pass |
| tooltip open text | open | text | `color.text.default` #e99499 | `color.surface.raised` #160b0b | 8.43 | 4.5 | pass |
| tooltip open border | open | ui | `color.border.overlay` #e53935 | `color.surface.raised` #160b0b | 4.57 | 3 | pass |
| shortcut on the tooltip | — | text | `color.text.bright` #ffa2a7 | `color.surface.raised` #160b0b | 10.10 | 4.5 | pass |
| shortcut key border on the tooltip | — | ui | `color.border.control` #a3676b | `color.surface.raised` #160b0b | 4.33 | 3 | pass |
| trigger text on the canvas | — | text | `color.action.tertiary.text` #e99499 | `color.surface.canvas` #000000 | 9.16 | 4.5 | pass |
| tree default text | default | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| tree default border | default | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| Primary content | — | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| Supporting content | — | text | `color.text.muted` #bd787d | `color.surface.default` #100c0c | 5.69 | 4.5 | pass |
| Control boundary | — | ui | `color.border.control` #a3676b | `color.surface.default` #100c0c | 4.36 | 3 | pass |
| wizard default text | default | text | `color.text.default` #e99499 | `color.surface.canvas` #000000 | 9.16 | 4.5 | pass |
| wizard hover text | hover | text | `color.text.default` #e99499 | `color.interaction.hover.bg` #1c0a09 | 8.35 | 4.5 | pass |
| wizard focus-visible text | focus-visible | text | `color.text.bright` #ffa2a7 | `color.surface.canvas` #000000 | 10.98 | 4.5 | pass |
| wizard focus-visible outline | focus-visible | ui | `color.interaction.focus.ring` #e53935 | `color.surface.canvas` #000000 | 4.97 | 3 | pass |
| wizard current text | current | text | `color.text.bright` #ffa2a7 | `color.surface.canvas` #000000 | 10.98 | 4.5 | pass |
| wizard current border | current | ui | `color.border.selected-indicator` #e53935 | `color.surface.canvas` #000000 | 4.97 | 3 | pass |
| wizard invalid text | invalid | text | `color.status.danger.text` #f73f35 | `color.surface.canvas` #000000 | 5.72 | 4.5 | pass |
| wizard invalid border | invalid | ui | `color.status.danger.border` #e53935 | `color.surface.canvas` #000000 | 4.97 | 3 | pass |
| wizard busy text | busy | text | `color.action.primary.text` #f4eeee | `color.action.primary.bg` #7d1310 | 9.28 | 4.5 | pass |
| complete marker on the canvas | — | text | `color.status.success.text` #86a46f | `color.surface.canvas` #000000 | 7.55 | 4.5 | pass |
| panel title on the panel surface | — | text | `color.text.bright` #ffa2a7 | `color.surface.default` #100c0c | 10.17 | 4.5 | pass |
| panel text on the panel surface | — | text | `color.text.default` #e99499 | `color.surface.default` #100c0c | 8.48 | 4.5 | pass |
| step message on the panel surface | — | text | `color.status.danger.text` #f73f35 | `color.surface.default` #100c0c | 5.29 | 4.5 | pass |
| panel edge (decorative) | — | ui | `color.border.default` #531310 | `color.surface.canvas` #000000 | 1.47 | 1 | pass |
| back button text | — | text | `color.action.secondary.text` #ffa2a7 | `color.surface.canvas` #000000 | 10.98 | 4.5 | pass |
| back button boundary | — | ui | `color.action.secondary.border` #a3676b | `color.surface.canvas` #000000 | 4.71 | 3 | pass |
| next button boundary | — | ui | `color.action.primary.border` #9e231f | `color.surface.canvas` #000000 | 2.71 | 3 | waived: the primary button is identified by its text at 9.28:1 on its fill and by its position; the fill measures 1.86:1 and its border 2.56:1 against the canvas, a documented property of the actions family, not of the wizard |
| ring on the next button's fill | focus-visible | ui | `color.interaction.focus.ring-container` #ffa2a7 | `color.action.primary.bg` #7d1310 | 5.57 | 3 | pass |
| future step text (exempt; house floor 3:1) | disabled | ui | `color.text.disabled` #8a5559 | `color.surface.canvas` #000000 | 3.52 | 3 | pass |

## Portability

### Scope of a port

A port themes only what the host documents as customisable. Editor colouring,
application chrome, terminal colours and plugin surfaces are separate scopes;
a file that changes one does not claim the others. A port never patches
binaries, injects unsupported hacks, overwrites a user's whole settings file or
replaces keybindings, credentials or unrelated preferences. It installs under a
unique name beside the host's defaults and documents how to roll back.

### What every port must reproduce

1. The surface ladder: canvas, default, raised, overlay, input.
2. The text ladder: default, bright, prose, muted, subtle, disabled.
3. Focus as a ring distinct from selection as a fill, with the ring recoloured
   on fills.
4. Selection, inactive selection and hover as three distinguishable fills.
5. The status hues with their glyphs where the host renders glyphs.
6. Zero radii and 1px borders where the host allows geometry; otherwise a
   recorded deviation.
7. The monospace family by name, honouring the user's size.

Every required role is mapped, explicitly inherited from the host, marked
unsupported with an explanation, or intentionally out of scope. A percentage
of coverage is meaningful only with its denominator.

### Fallbacks by host capability

| Host capability | Fallback |
| --- | --- |
| No dashed outlines (many native toolkits) | solid 1px ring in `focus.ring`; record the deviation |
| No separate inactive selection | selection fill everywhere; record it |
| No read-only styling hook | treat as default text with a dotted bottom edge if borders are available, else as default |
| No pattern fills (charts) | keep the glyph or label channel; never colour alone |
| Sixteen-colour terminal | `heritage-ansi` for fidelity or the `extended` slots when approved; document bold-as-bright and whether 256/24-bit colours bypass the slots |
| Fixed radii in the host | document; do not fight the toolkit |
| Host forbids removing its focus indicator | keep the host's indicator; never draw two |

### Web consumers

The site's `exports/tokens.css` exposes every role as a custom property.
Consume roles (`--color-text-default`), never primitives. A framework
integration maps roles onto the framework's theme keys and lists what it
cannot express; `agents/consume.md` gives the reading order and the deviation
report format, and `theme.lock.json` pins the revision.

### Native ports (future)

A port lives at `ports/<slug>/` with `port.json`, `mapping.json`, `src/`,
`dist/` and `evidence/`, created from `templates/port/` only when work begins.
Statuses are `experimental`, `verified` and `deprecated`; reference
implementations and roadmap candidates are not statuses. `verified` requires a
real import into the recorded application version with matching evidence and a
token digest equal to the current default profile. A successful XML or JSON
parse is a structural pass, not verification. Never hand-edit a generated hex;
fix the mapping or the approved token and regenerate.

For JetBrains, an editor scheme (`.icls`) and a UI theme plugin are separate
capabilities; the scheme alone does not recolour toolbars or dialogs. For
Notepad++, a style-theme XML, the dark-mode chrome setting and User Defined
Languages are three mechanisms. Consult the vendor documentation for the
tested version before mapping.

### The reference implementation

`j3w1/j3w1.github.io` is the flagship web implementation and the source of the
observed values. It is not an upstream that this repository scrapes at build
time, and there is no automatic synchronisation in either direction. A later
integration pins a release of this theme in that site through a separate
reviewed change and records its own site-specific values (window-manager
geometry, wallpaper gradients) as such.

## Actions

Buttons, links, icon buttons, button groups, segmented controls, destructive treatment.

### Button (`button`)

The native push button in the four action tones; primary is the only filled button at rest, destructive fills on hover and always needs a confirmation or an undo. Family: actions. Priority: R1. Maturity: stable. States: default, hover, focus-visible, active, disabled, loading.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.action.primary.bg` | `#7d1310` | use |
| root.text | `color.action.primary.text` | `#f4eeee` | use |
| root.border | `color.action.primary.border` | `#9e231f` | use |
| root.bg-hover | `color.action.primary.hover-bg` | `#911410` | use |
| root.bg-active | `color.action.primary.pressed-bg` | `#630f0d` | use |
| root.ring | `color.interaction.focus.ring-container` | `#ffa2a7` | use |
| secondary.bg | `color.action.secondary.bg` | `rgb(0 0 0 / 0%)` | use |
| secondary.text | `color.action.secondary.text` | `#ffa2a7` | use |
| secondary.border | `color.action.secondary.border` | `#a3676b` | use |
| secondary.bg-hover | `color.action.secondary.hover-bg` | `#630f0d` | use |
| secondary.bg-active | `color.action.secondary.pressed-bg` | `#420f0c` | use |
| secondary.ring | `color.interaction.focus.ring` | `#e53935` | use |
| tertiary.text | `color.action.tertiary.text` | `#e99499` | use |
| tertiary.bg-hover | `color.action.tertiary.hover-bg` | `#1c0a09` | use |
| tertiary.bg-active | `color.interaction.pressed.bg` | `#420f0c` | use |
| destructive.text | `color.action.destructive.text` | `#f73f35` | use |
| destructive.border | `color.action.destructive.border` | `#e53935` | use |
| destructive.bg-hover | `color.action.destructive.hover-bg` | `#dc282e` | use |
| destructive.text-hover | `color.action.destructive.hover-text` | `#f9faf9` | use |
| destructive.bg-active | `color.action.destructive.pressed-bg` | `#ae1914` | use |
| destructive.bg-filled | `color.action.destructive.filled-bg` | `#dc282e` | use |
| destructive.text-filled | `color.action.destructive.filled-text` | `#f9faf9` | use |
| root.border-active | `color.action.primary.border` | `#9e231f` | use |
| secondary.border-active | `color.border.active` | `#e53935` | use |
| tertiary.border-active | `color.border.active` | `#e53935` | use |
| destructive.border-active | `color.action.destructive.border` | `#e53935` | use |
| root.text-disabled | `color.text.disabled` | `#8a5559` | use |
| root.bg-disabled | `color.interaction.disabled.bg` | `#160b0b` | use |
| root.border-disabled | `color.border.disabled` | `#7d1310` | use |
| loading.glyph | `color.action.primary.text` | `#f4eeee` | use |

#### Purpose

The push button that triggers an action. Four tones and no more: `primary`
is the single filled button in a view, `secondary` is the outline for every
other action, `tertiary` is the ghost for low-emphasis and toolbar actions,
`destructive` is an outline in the danger tone that fills on hover. A
destructive button also requires a confirmation step or an undo affordance;
the colour is not the safeguard. Navigation is a link, not a button.

#### Anatomy

A native `<button>`; optional leading icon; the label. The loading glyph
occupies the icon slot so the size never changes. A filled destructive
modifier (`.button-destructive-filled`, {color.action.destructive.filled-bg}
with {color.action.destructive.filled-text}) exists only inside confirm
dialogs.

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | primary: {color.action.primary.bg} fill, {color.action.primary.text}, 1px {color.action.primary.border}; secondary: 1px {color.action.secondary.border}, {color.action.secondary.text}; tertiary: {color.action.tertiary.text}, no border; destructive: 1px {color.action.destructive.border}, {color.action.destructive.text} | — |
| hover | primary → {color.action.primary.hover-bg}; secondary → {color.action.secondary.hover-bg}; tertiary → {color.action.tertiary.hover-bg}; destructive fills {color.action.destructive.hover-bg} with {color.action.destructive.hover-text} | cursor: pointer; hover-capable pointers only |
| focus-visible | ring 1px dashed at −2px: {color.interaction.focus.ring-container} on the primary fill, {color.interaction.focus.ring} on the outline tones, {color.action.destructive.hover-text} on the destructive hover fill | the ring |
| active | primary → {color.action.primary.pressed-bg}; secondary → {color.action.secondary.pressed-bg}; tertiary → {color.interaction.pressed.bg}; destructive → {color.action.destructive.pressed-bg}; primary keeps {color.action.primary.border}, secondary and tertiary use {color.border.active}, destructive keeps {color.action.destructive.border} (D-015) | border change on secondary and tertiary; no translation or scale |
| disabled | {color.text.disabled} on {color.interaction.disabled.bg} with 1px {color.border.disabled}, every tone alike; no hover or active styling | `disabled`; cursor: not-allowed; never opacity |
| loading | size unchanged; the static `⋯` glyph replaces the icon; the button keeps its tone and stays focusable but ignores activation | glyph; `aria-busy` |

Precedence: disabled > loading > active > hover; focus-visible is always
drawn.

The primary edge stays unchanged when pressed (D-015). Its exact role is
`root.border-active` in the part map, just as `root.border` names the rest
edge. The default-tone state contrast map checks the text against the
pressed fill; it does not substitute `color.border.active` for this
unchanged edge. The existing primary-action contrast declaration identifies
the labelled button through its text, not through the fill's boundary.

#### Keyboard

Native. Enter and Space activate; nothing else is bound. A loading button
stays in the tab order so focus is not lost mid-action, and the host ignores
activation until `aria-busy` clears.

#### Accessibility

The accessible name is the label text; an icon-only button is the
icon-button component. Never `aria-disabled` on a button that should be
skipped, and never `disabled` on a button whose reason a user needs to
discover: prefer keeping it enabled and explaining on activation. Contrast:
primary 9.28:1, secondary 10.37:1, tertiary 8.65:1, destructive 5.40:1 at
rest and 4.58:1 on the hover fill; every outline border ≥ 4.45:1; rings ≥
3:1 on every fill they sit on. The target is at least 24×24 CSS pixels in
`compact` density with 4px between neighbours.

#### Portability

Fill, border and outline express every state; no gradient, shadow or
transform is used. Hosts without dashed outlines draw a solid 1px ring in the
same colour and record the deviation. Hosts that cannot recolour the ring on
a fill draw it outside the fill at +1px.

#### Non-examples

Rounded corners or pill shapes. Two filled buttons in one view. A red
filled destructive button at rest. A hover that only changes text colour.
Translating or scaling the button on press. A spinner inside the button.
Disabling with opacity. A `<div>` or `<a>` styled as a button. A gradient or
shadow to suggest depth.

### Icon button (`icon-button`)

A square ghost button whose only content is a 16px line icon; it always carries an accessible name and, as a toggle, aria-pressed. Family: actions. Priority: R1. Maturity: stable. States: default, hover, focus-visible, active, disabled, toggled, toggled+focus-visible.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.action.secondary.bg` | `rgb(0 0 0 / 0%)` | use |
| root.text | `color.action.tertiary.text` | `#e99499` | use |
| root.bg-hover | `color.action.tertiary.hover-bg` | `#1c0a09` | use |
| root.bg-active | `color.interaction.pressed.bg` | `#420f0c` | use |
| root.border-active | `color.border.active` | `#e53935` | use |
| root.ring | `color.interaction.focus.ring` | `#e53935` | use |
| root.bg-toggled | `color.interaction.selection.bg` | `#531310` | use |
| root.text-toggled | `color.interaction.selection.text` | `#f4eeee` | use |
| root.border-toggled | `color.border.selected-indicator` | `#e53935` | use |
| root.ring-toggled | `color.interaction.focus.ring-container` | `#ffa2a7` | use |
| root.text-disabled | `color.text.disabled` | `#8a5559` | use |
| root.bg-disabled | `color.interaction.disabled.bg` | `#160b0b` | use |
| root.border-disabled | `color.border.disabled` | `#7d1310` | use |
| icon.stroke | `color.icon.default` | `#e99499` | use |

#### Purpose

A button whose whole content is one icon: toolbar actions, close and clear
affordances, and boolean toggles such as pin, mute or wrap. It is the
tertiary tone constrained to a square. Any icon button needs an accessible
name and a tooltip; if the label must be visible it is a button.

#### Anatomy

A native `<button>` sized to the density control height in both axes with a
single 16px inline SVG inside. The border is 1px and transparent at rest so
the active and toggled borders do not shift the layout.

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | {color.action.tertiary.text} icon on nothing; transparent 1px border | — |
| hover | background → {color.action.tertiary.hover-bg} | cursor: pointer; hover-capable pointers only |
| focus-visible | ring 1px dashed {color.interaction.focus.ring} at −2px | the ring |
| active | background → {color.interaction.pressed.bg}; border → {color.border.active} | border change; no translation |
| disabled | {color.text.disabled} on {color.interaction.disabled.bg} with 1px {color.border.disabled}; no hover | `disabled`; cursor: not-allowed |
| toggled | fill {color.interaction.selection.bg} with {color.interaction.selection.text}; border {color.border.selected-indicator}; the icon draws in its filled form | `aria-pressed="true"`; the filled icon shape; the border |
| toggled+focus-visible | the selection fill and a ring in {color.interaction.focus.ring-container} | both visible at once |

Precedence: disabled > toggled > active > hover; focus-visible is always
drawn.

#### Keyboard

Native. The toggle variant flips `aria-pressed` on Enter and Space and does
not move focus. Inside a toolbar the host applies roving tabindex; the
button itself binds nothing.

#### Accessibility

`aria-label` (or `aria-labelledby`) names the button; the tooltip repeats
the same text and is not the name. The SVG is `aria-hidden="true"` and
`focusable="false"`. A toggle uses `aria-pressed`, whose value is announced;
the name never changes with the state ("Mute", not "Mute" / "Unmute").
Contrast: icon 8.65:1 at rest, 12.47:1 on the selection fill; ring 4.69:1,
container ring 7.48:1 on the selection fill. Target ≥ 24×24 CSS pixels with
4px between neighbours in `compact` density.

#### Portability

Square, fill, border, outline; the icon is currentColor so it follows every
state without its own colour. Hosts without inline SVG use their icon theme at
16px and record the substitution. Hosts without dashed outlines draw a solid
1px ring.

#### Non-examples

A circle. An icon button without an accessible name. A toggle that changes
its label instead of `aria-pressed`. A hover that recolours the icon alone.
An emoji or icon-font glyph in place of the SVG. A toggled state shown by
colour alone with a line icon. A shadow or scale on press.

### Link (`link`)

The native anchor for navigation; always underlined in the link-underline role, hover fills with the strong hover surface, the current page carries a 2px indicator bar. Family: actions. Priority: R1. Maturity: stable. States: default, hover, focus-visible, visited, current.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.text | `color.text.link` | `#f73f35` | use |
| root.underline | `color.text.link-underline` | `#dc282e` | use |
| root.text-hover | `color.text.link-hover` | `#f4eeee` | use |
| root.bg-hover | `color.interaction.hover.bg-strong` | `#630f0d` | use |
| root.ring | `color.interaction.focus.ring` | `#e53935` | use |
| root.text-visited | `color.text.link` | `#f73f35` | use |
| root.text-current | `color.text.bright` | `#ffa2a7` | use |
| root.indicator | `color.border.selected-indicator` | `#e53935` | use |
| icon.stroke | `color.icon.default` | `#e99499` | use |

#### Purpose

Navigation to another document, section or resource. A link never performs
an action on the current page; that is a button. Links are recognisable by
the persistent underline as well as strong red {color.text.link}, distinct
from rose body text and headings (D-024).

#### Anatomy

A native `<a href>`. Inline: text plus the 1px underline. Standalone: the
same with a trailing arrow icon and a little horizontal padding so the hover
fill has room. Current: `aria-current="page"`, brighter text and a 2px bar
replacing the underline.

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | {color.text.link}; underline 1px {color.text.link-underline} offset 2px | the underline |
| hover | background → {color.interaction.hover.bg-strong}; text → {color.text.link-hover}; underline 2px in the text colour | underline thickens 1px → 2px; cursor: pointer |
| focus-visible | ring 1px dashed {color.interaction.focus.ring} at −2px; no change to the underline | the ring |
| visited | {color.text.link}; underline unchanged | the underline continues to mark navigation; browsing history does not reduce its visibility |
| current | text → {color.text.bright}; the underline becomes a 2px {color.border.selected-indicator} bottom border; no hover fill | `aria-current="page"`; the bar's 2px weight |

Precedence: current > hover; focus-visible is always drawn.

#### Keyboard

Native. Enter follows the link; Space never does. Skip links and in-page
links move focus to their target, which needs `tabindex="-1"` when it is not
focusable.

#### Accessibility

The link text describes the destination on its own ("Release notes", never
"here"). Same destination, same text. `aria-current="page"` marks the
current document in navigation and nothing else. Links that open a new
window say so in text. Link text meets 4.5:1 on canvas, panel and raised
surfaces; the underline meets 3:1. The generated contrast report records exact
ratios for the current profile, including hover text and focus. Inline targets are exempt
from the 24×24 minimum; standalone links reach it through their line box.

#### Portability

Colour, underline thickness, background and outline; the arrow is an inline
SVG in currentColor. Hosts that cannot thicken an underline invert the link on
hover instead and record it. Hosts without `aria-current` expose the current
link as selected.

#### Non-examples

A link without an underline at rest. Blue or purple links. A hover that only
recolours the text. A link that opens a dialog or submits a form. An
underline drawn with a border on an inline link. A visited colour that fails
4.5:1. A button styled to look like a link. Text such as "click here".

### Button group (`button-group`)

Related independent native actions with individual tab stops. Family: actions. Priority: R2. Maturity: stable. States: default, focus-visible, disabled.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.default` | `#100c0c` | use |
| root.text | `color.text.default` | `#e99499` | use |
| root.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| heading.text | `color.text.bright` | `#ffa2a7` | use |
| secondary.text | `color.text.muted` | `#bd787d` | use |
| control.bg | `color.surface.input` | `#000000` | use |
| control.focus | `color.interaction.focus.ring` | `#e53935` | use |
| dependency.role-border-width-default | `border.width.default` | `1px` | use |
| dependency.role-color-border-disabled | `color.border.disabled` | `#7d1310` | use |
| dependency.role-color-interaction-disabled-bg | `color.interaction.disabled.bg` | `#160b0b` | use |
| dependency.role-color-text-disabled | `color.text.disabled` | `#8a5559` | use |
| dependency.role-density-comfortable-control-height | `density.comfortable.control-height` | `32px` | use |
| dependency.role-density-compact-control-height | `density.compact.control-height` | `24px` | use |
| dependency.role-radius-none | `radius.none` | `0px` | use |
| dependency.role-space-12 | `space.12` | `12px` | use |
| dependency.role-space-2 | `space.2` | `2px` | use |
| dependency.role-space-4 | `space.4` | `4px` | use |
| dependency.role-space-8 | `space.8` | `8px` | use |

#### Purpose

Group related independent actions without turning them into an exclusive choice. Each button keeps its native type, accessible name and tab stop. Application effects belong to the host.

#### Anatomy

- **group:** Labelled group that wraps its buttons.
- **actions:** Independent buttons in source order.

#### States

| State | Presentation | Non-colour channel |
| --- | --- | --- |
| default | Named content on the default surface. | Native text and structure. |
| focus-visible | Dashed focus ring. | Native keyboard focus position. |
| disabled | Disabled semantic foreground, background and border at full opacity. | Native disabled state blocks activation. |

#### Keyboard

- **Tab / Shift+Tab:** Visit each enabled button.
- **Enter / Space:** Activate the focused native button.

#### Accessibility

Use role=group with a visible or accessible label. Buttons retain distinct names and native disabled behavior. A button group does not add arrow-key navigation or selection semantics.

#### Portability

Wrap labels and actions without changing source order. Use logical spacing in RTL. Preserve native target sizes at both densities; only bounded data regions may scroll horizontally. Use equivalent native semantics and approved roles; record any unsupported behavior as a mapping deviation. The host owns application data, persistence, permissions and services.

#### Non-examples

Unlabelled controls, color-only status, drag-only operations, hidden required instructions, stolen focus or an invented service success.

### Segmented control (`segmented-control`)

A short exclusive choice implemented with labelled native radios. Family: actions. Priority: R2. Maturity: stable. States: default, checked, focus-visible, disabled.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.default` | `#100c0c` | use |
| root.text | `color.text.default` | `#e99499` | use |
| root.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| heading.text | `color.text.bright` | `#ffa2a7` | use |
| secondary.text | `color.text.muted` | `#bd787d` | use |
| control.bg | `color.surface.input` | `#000000` | use |
| control.focus | `color.interaction.focus.ring` | `#e53935` | use |
| dependency.role-border-width-default | `border.width.default` | `1px` | use |
| dependency.role-border-width-emphasis | `border.width.emphasis` | `2px` | use |
| dependency.role-color-action-primary-bg | `color.action.primary.bg` | `#7d1310` | use |
| dependency.role-color-action-primary-hover-bg | `color.action.primary.hover-bg` | `#911410` | use |
| dependency.role-color-action-primary-text | `color.action.primary.text` | `#f4eeee` | use |
| dependency.role-color-border-disabled | `color.border.disabled` | `#7d1310` | use |
| dependency.role-color-interaction-disabled-bg | `color.interaction.disabled.bg` | `#160b0b` | use |
| dependency.role-color-interaction-focus-ring-container | `color.interaction.focus.ring-container` | `#ffa2a7` | use |
| dependency.role-color-interaction-hover-bg | `color.interaction.hover.bg` | `#1c0a09` | use |
| dependency.role-color-interaction-selection-bg | `color.interaction.selection.bg` | `#531310` | use |
| dependency.role-color-interaction-selection-text | `color.interaction.selection.text` | `#f4eeee` | use |
| dependency.role-color-status-danger-border | `color.status.danger.border` | `#e53935` | use |
| dependency.role-color-status-danger-text | `color.status.danger.text` | `#f73f35` | use |
| dependency.role-color-text-disabled | `color.text.disabled` | `#8a5559` | use |
| dependency.role-density-comfortable-control-height | `density.comfortable.control-height` | `32px` | use |
| dependency.role-density-compact-control-height | `density.compact.control-height` | `24px` | use |
| dependency.role-focus-offset | `focus.offset` | `-2px` | use |
| dependency.role-focus-ring | `focus.ring` | `1px dashed #e53935` | use |
| dependency.role-font-line-height-ui-md | `font.line-height.ui-md` | `18px` | use |
| dependency.role-font-line-height-ui-sm | `font.line-height.ui-sm` | `16px` | use |
| dependency.role-font-size-ui-md | `font.size.ui-md` | `13px` | use |
| dependency.role-font-size-ui-sm | `font.size.ui-sm` | `12px` | use |
| dependency.role-icon-size-md | `icon.size.md` | `16px` | use |
| dependency.role-radius-none | `radius.none` | `0px` | use |
| dependency.role-space-12 | `space.12` | `12px` | use |
| dependency.role-space-16 | `space.16` | `16px` | use |
| dependency.role-space-2 | `space.2` | `2px` | use |
| dependency.role-space-24 | `space.24` | `24px` | use |
| dependency.role-space-4 | `space.4` | `4px` | use |
| dependency.role-space-8 | `space.8` | `8px` | use |

#### Purpose

Choose one value from a short labelled set using native radios. This changes a value; use tabs for associated content panels. Native names, required checks, disabled fieldsets and form reset are preserved.

#### Anatomy

- **fieldset:** Legend names the choice.
- **options:** Native radios sharing one name, each with a visible label.

#### States

| State | Presentation | Non-colour channel |
| --- | --- | --- |
| default | Named content on the default surface. | Native text and structure. |
| checked | Selected fill and a check indicator. | Native checked state. |
| focus-visible | Dashed focus ring. | Native keyboard focus position. |
| disabled | Disabled semantic foreground, background and border at full opacity. | Native disabled state blocks activation. |

#### Keyboard

- **Tab / Shift+Tab:** Enter the native radio group once and leave it.
- **Arrow keys / Space:** Use native radio selection and wrapping.

#### Accessibility

Keep native radios and labels. A checked value has both a visible native indicator and its programmatic checked state. Do not hide all radios from assistive technology or put role=tab on the labels.

#### Portability

Wrap labels and actions without changing source order. Use logical spacing in RTL. Preserve native target sizes at both densities; only bounded data regions may scroll horizontally. Use equivalent native semantics and approved roles; record any unsupported behavior as a mapping deviation. The host owns application data, persistence, permissions and services.

#### Non-examples

Unlabelled controls, color-only status, drag-only operations, hidden required instructions, stolen focus or an invented service success.

## Basic forms

Text, password, number, search, textarea, select, checkbox, radio, switch, range, fieldsets, labels, help text, validation.

### Field (`field`)

The label, control, help and message wrapper that every form control sits in; it fixes the stacking order, the required mark, the description links and the invalid message. Family: forms-basic. Priority: R1. Maturity: stable. States: default, required, invalid, disabled.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.text | `color.text.default` | `#e99499` | use |
| label.text | `color.text.bright` | `#ffa2a7` | use |
| label.required-mark | `color.status.danger.text` | `#f73f35` | use |
| control.bg | `color.surface.input` | `#000000` | use |
| control.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| control.border-invalid | `color.status.danger.border` | `#e53935` | use |
| control.text-disabled | `color.text.disabled` | `#8a5559` | use |
| control.bg-disabled | `color.interaction.disabled.bg` | `#160b0b` | use |
| control.border-disabled | `color.border.disabled` | `#7d1310` | use |
| help.text | `color.text.muted` | `#bd787d` | use |
| message.text | `color.status.danger.text` | `#f73f35` | use |

#### Purpose

The wrapper that gives every control the same label, description and error
treatment. Controls specify their box; the field specifies what sits above
and below it and how they are linked. Every form control in this theme is
rendered inside a field or a fieldset.

#### Anatomy

Label above the control, always stacked; the control slot; help text
below in {color.text.muted}; the validation message, which takes the help
slot visually while help stays in the DOM and `aria-describedby` lists both.
The required mark is present in the markup and shown only when the control
is required.

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | label {color.text.bright}; help {color.text.muted}; control as its own component | — |
| required | `*` after the label in {color.status.danger.text} plus visually hidden "required" | `required` attribute; the mark |
| invalid | the control's 2px {color.status.danger.border}; message with the ✕ glyph in {color.status.danger.text}; help visually hidden | border width 1 → 2px; glyph; `aria-invalid` |
| disabled | label and control text {color.text.disabled}; control {color.interaction.disabled.bg} with 1px {color.border.disabled}; help unchanged | `disabled`; cursor: not-allowed |

Precedence: disabled > invalid; required is shown in every state.

#### Keyboard

The field adds no behaviour. Clicking the label focuses the control
(native `for`). Focus order inside the field is the control's own.

#### Accessibility

Every control has a programmatic label; placeholder text is never the
label. Help and message are linked with `aria-describedby` in that order so
the description is read before the error. `aria-invalid="true"` is set only
after interaction or submit. The message keeps its glyph so the state is not
colour-only. Contrast: label 10.10:1, help 5.66:1, message 5.26:1 on the
panel surface; disabled label 3.24:1 (exempt).

#### Portability

Plain block layout; the only pseudo-element is nothing, the mark is real
markup. Hosts without `aria-describedby` put help and message in the
control's tooltip or accessible description and record it.

#### Non-examples

A floating label inside the control. A label to the left of the control.
Placeholder text as the only label. An error shown by a red border alone. A
message that replaces the help text in the DOM. A required mark that is only
a colour change. An asterisk with no text alternative. Two controls in one
field.

### Text field (`text-field`)

Single-line free-text entry, covering every native input type that renders as a line box; search, number and password are variants, not separate components. Family: forms-basic. Priority: R1. Maturity: stable. States: default, hover, focus-visible, placeholder-shown, filled, required, invalid, invalid+focus-visible, invalid+hover, disabled, disabled+filled, read-only, read-only+focus-visible, loading.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.input` | `#000000` | use |
| root.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| root.bg-hover | `color.interaction.hover.bg` | `#1c0a09` | use |
| root.border-focus | `color.border.active` | `#e53935` | use |
| root.ring | `color.interaction.focus.ring` | `#e53935` | use |
| root.ring-invalid | `color.interaction.focus.ring-container` | `#ffa2a7` | use |
| root.border-invalid | `color.status.danger.border` | `#e53935` | use |
| root.border-disabled | `color.border.disabled` | `#7d1310` | use |
| root.bg-disabled | `color.interaction.disabled.bg` | `#160b0b` | use |
| root.border-readonly | `color.border.divider` | `#2b0e0d` | use-and-report (D-008) |
| input.text | `color.text.default` | `#e99499` | use |
| input.text-disabled | `color.text.disabled` | `#8a5559` | use |
| input.placeholder | `color.text.placeholder` | `#a3676b` | use |
| input.caret | `color.code.caret` | `#e99499` | use |
| input.selection-bg | `color.interaction.text-selection.bg` | `#911410` | use |
| input.selection-text | `color.interaction.text-selection.text` | `#f4eeee` | use |
| label.text | `color.text.bright` | `#ffa2a7` | use |
| label.required-mark | `color.status.danger.text` | `#f73f35` | use |
| help.text | `color.text.muted` | `#bd787d` | use |
| message.text | `color.status.danger.text` | `#f73f35` | use |
| affix.text | `color.text.muted` | `#bd787d` | use |
| affix.border | `color.border.divider` | `#2b0e0d` | use-and-report (D-008) |
| action.text | `color.action.tertiary.text` | `#e99499` | use |
| action.bg-hover | `color.action.tertiary.hover-bg` | `#1c0a09` | use |
| loading.glyph | `color.text.muted` | `#bd787d` | use |

#### Purpose

Single-line free-text entry. Every native `type` that renders as a line box
(`text`, `password`, `search`, `number`, `email`, `url`, `tel`) is this
component; date and time inputs are the date-picker and time-picker
components because their popups are host surfaces.

#### Anatomy

Label above the control; the control box; optional prefix and suffix inside
the box separated by a {color.border.divider} rule; the input; optional
trailing actions (clear, reveal, stepper); help text below; the validation
message, which takes the help slot visually while help stays in the DOM and
`aria-describedby` lists both.

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | 1px {color.border.control} on {color.surface.input}; text {color.text.default} | — |
| hover | background → {color.interaction.hover.bg}; border unchanged | cursor: text |
| focus-visible | border → {color.border.active}; ring 1px dashed {color.interaction.focus.ring} at −2px | the ring |
| placeholder-shown | placeholder in {color.text.placeholder}, italic | italic |
| filled | as default with a value | — |
| required | `*` after the label in {color.status.danger.text} plus visually hidden "required" | `required` attribute; the mark |
| invalid | border 2px {color.status.danger.border}; message with the ✕ glyph | border width 1 → 2px; glyph; `aria-invalid` |
| invalid+focus-visible | the 2px danger border and a 1px dashed ring in {color.interaction.focus.ring-container} at −4px (the control form, recoloured; never the 2px container form) | double boundary |
| invalid+hover | as invalid with the hover background | cursor |
| disabled | text {color.text.disabled}; border {color.border.disabled}; background {color.interaction.disabled.bg}; no hover; the label and help keep their colours | `disabled`; cursor: not-allowed |
| disabled+filled | as disabled with a value | `disabled` |
| read-only | no box: 1px dotted {color.border.divider} bottom edge on {color.surface.canvas}; text {color.text.default}; focusable and selectable | `readonly`; dotted edge |
| read-only+focus-visible | the dotted {color.border.divider} edge unchanged, plus the dashed ring at −2px | the ring |
| loading | size unchanged; a static `⋯` glyph in {color.text.muted} inside the box after the input and before any trailing action; input remains editable | glyph; `aria-busy` |

Precedence when several apply: disabled > loading > invalid > hover;
focus-visible is always drawn.

#### Keyboard

Focus order is label (not focusable) → input → clear or reveal → stepper
buttons. The clear button is `type="button"` with `aria-label="Clear <label>"`;
the reveal button toggles `type` between `password` and `text` and its own
`aria-pressed`. Escape in the search variant clears a non-empty value.

#### Accessibility

A programmatic label is required (`<label for>`). Help and message are linked
through `aria-describedby` in that order. `aria-invalid="true"` is set only
after the user interacted with the field or submitted the form, never on first
paint. The message is announced on submit through the form's live region, not
on every keystroke. Contrast: text 8.65:1 on the input surface, placeholder
5.81:1, disabled text 3.33:1 (exempt), control border 4.45:1 (≥ 3:1), invalid
border 4.69:1, danger message 5.40:1. The box is at least 24×24 CSS pixels in
`compact` density.

#### Portability

Everything is expressible with border, background and outline; the only
pseudo-element is the required mark. Toolkits without dashed outlines draw a
solid 1px ring in the same colour and record the deviation. Toolkits without a
read-only style treat read-only as default text and record it.

#### Non-examples

Rounded corners. A floating or animated label. An underline-only field with no
box. A glow instead of the dashed ring. Placeholder text in red. Disabling by
opacity. Using colour alone for the invalid state. A hover that changes the
text colour.

### Textarea (`textarea`)

Multi-line free-text entry in the same box as the text field; resizes vertically only and can show a character count. Family: forms-basic. Priority: R1. Maturity: stable. States: default, hover, focus-visible, placeholder-shown, filled, required, invalid, invalid+focus-visible, disabled, read-only.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.input` | `#000000` | use |
| root.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| root.bg-hover | `color.interaction.hover.bg` | `#1c0a09` | use |
| root.border-focus | `color.border.active` | `#e53935` | use |
| root.ring | `color.interaction.focus.ring` | `#e53935` | use |
| root.ring-invalid | `color.interaction.focus.ring-container` | `#ffa2a7` | use |
| root.border-invalid | `color.status.danger.border` | `#e53935` | use |
| root.border-disabled | `color.border.disabled` | `#7d1310` | use |
| root.bg-disabled | `color.interaction.disabled.bg` | `#160b0b` | use |
| root.border-readonly | `color.border.divider` | `#2b0e0d` | use-and-report (D-008) |
| root.bg-readonly | `color.surface.canvas` | `#000000` | use |
| input.text | `color.text.default` | `#e99499` | use |
| input.text-disabled | `color.text.disabled` | `#8a5559` | use |
| input.placeholder | `color.text.placeholder` | `#a3676b` | use |
| input.caret | `color.code.caret` | `#e99499` | use |
| input.selection-bg | `color.interaction.text-selection.bg` | `#911410` | use |
| input.selection-text | `color.interaction.text-selection.text` | `#f4eeee` | use |
| label.text | `color.text.bright` | `#ffa2a7` | use |
| label.required-mark | `color.status.danger.text` | `#f73f35` | use |
| help.text | `color.text.muted` | `#bd787d` | use |
| count.text | `color.text.muted` | `#bd787d` | use |
| count.text-over | `color.status.danger.text` | `#f73f35` | use |
| message.text | `color.status.danger.text` | `#f73f35` | use |

#### Purpose

Free text longer than a line: descriptions, messages, notes. It shares the
text field's box, states and message so a form reads as one system. Code
goes in the code-editor component, not a textarea.

#### Anatomy

Label above; the box with the native `<textarea>` inside, at least three
rows tall, resizable downwards only; the optional count; help; the
validation message that takes the help slot visually while help stays in the
DOM.

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | 1px {color.border.control} on {color.surface.input}; text {color.text.default} | — |
| hover | background → {color.interaction.hover.bg}; border unchanged | cursor: text |
| focus-visible | border → {color.border.active}; ring 1px dashed {color.interaction.focus.ring} at −2px | the ring |
| placeholder-shown | placeholder in {color.text.placeholder}, italic | italic |
| filled | as default with a value | — |
| required | `*` after the label in {color.status.danger.text} plus visually hidden "required" | `required`; the mark |
| invalid | border 2px {color.status.danger.border}; message with the ✕ glyph; a count past its limit turns {color.status.danger.text} with the glyph | border width 1 → 2px; glyph; `aria-invalid` |
| invalid+focus-visible | the 2px danger border and the ring in {color.interaction.focus.ring-container} at −4px | double boundary |
| disabled | text {color.text.disabled}; border {color.border.disabled}; background {color.interaction.disabled.bg}; no resize handle | `disabled`; cursor: not-allowed |
| read-only | no box: 1px dotted {color.border.divider} bottom edge on {color.surface.canvas}; no resize handle; selectable | `readonly`; dotted edge |

Precedence: disabled > invalid > hover; focus-visible is always drawn.

#### Keyboard

Native editing. Enter inserts a line break and never submits. Tab leaves
the control; a host that needs literal tabs provides a separate command and
documents it. The count is not focusable.

#### Accessibility

A programmatic label is required. Help, count and message are linked
through `aria-describedby` in that order. The count is `aria-live="polite"`
and throttled so it does not announce every keystroke; the limit is stated
in the help text so it is known before typing. `aria-invalid="true"` only
after interaction or submit. Contrast as the text field: text 8.65:1,
placeholder 5.81:1, control border 4.45:1, invalid border 4.69:1, message
5.40:1.

#### Portability

Border, background, outline and `resize: vertical`. Hosts without a resize
handle grow the box with content up to a maximum and record it. Hosts
without dashed outlines draw a solid 1px ring.

#### Non-examples

Horizontal or free resizing. A box narrower than its label column. A count
that only changes colour past the limit. A glow instead of the dashed ring.
Rounded corners. An auto-growing box with no maximum. Tab inserting a
character. Placeholder text as the label. Disabling by opacity.

### Select (`select`)

A themed single-choice combobox or multiple-choice list backed by a native select for forms and the no-JavaScript fallback. Family: forms-basic. Priority: R1. Maturity: stable. States: default, hover, focus-visible, required, invalid, invalid+focus-visible, disabled.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.input` | `#000000` | use |
| root.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| root.bg-hover | `color.interaction.hover.bg` | `#1c0a09` | use |
| root.border-focus | `color.border.active` | `#e53935` | use |
| root.ring | `color.interaction.focus.ring` | `#e53935` | use |
| root.ring-invalid | `color.interaction.focus.ring-container` | `#ffa2a7` | use |
| root.border-invalid | `color.status.danger.border` | `#e53935` | use |
| root.border-disabled | `color.border.disabled` | `#7d1310` | use |
| root.bg-disabled | `color.interaction.disabled.bg` | `#160b0b` | use |
| control.text | `color.text.default` | `#e99499` | use |
| control.text-disabled | `color.text.disabled` | `#8a5559` | use |
| chevron.stroke | `color.icon.default` | `#e99499` | use |
| option.bg | `color.surface.raised` | `#160b0b` | use |
| option.text | `color.text.default` | `#e99499` | use |
| option.bg-selected | `color.interaction.selection.bg` | `#531310` | use |
| option.text-selected | `color.interaction.selection.text` | `#f4eeee` | use |
| group.text | `color.text.muted` | `#bd787d` | use |
| label.text | `color.text.bright` | `#ffa2a7` | use |
| label.required-mark | `color.status.danger.text` | `#f73f35` | use |
| help.text | `color.text.muted` | `#bd787d` | use |
| message.text | `color.status.danger.text` | `#f73f35` | use |

#### Purpose

Choosing one option, or several, from a fixed list. D-025 requires theme-owned
choice surfaces in web applications. The native select remains the form-value
and constraint source. A searchable or asynchronous list is the separate
combobox component.

#### Anatomy

Label above; a theme-owned trigger and listbox for single choices, or an
always-visible multiselectable listbox. Each selected option has a check mark
and the canonical selection fill. The hidden native `<select>` retains its
name, options, current values and defaults. Help and validation are linked
to the visible control. Without JavaScript the original native control is visible.

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | 1px {color.border.control} on {color.surface.input}; value {color.text.default}; chevron {color.icon.default} | the chevron |
| hover | background → {color.interaction.hover.bg}; border unchanged | cursor: default; hover-capable pointers only |
| focus-visible | border → {color.border.active}; ring 1px dashed {color.interaction.focus.ring} at −2px | the ring |
| required | `*` after the label in {color.status.danger.text} plus visually hidden "required" | `required`; the mark |
| invalid | border 2px {color.status.danger.border}; message with the ✕ glyph | border width 1 → 2px; glyph; `aria-invalid` |
| invalid+focus-visible | the 2px danger border and the ring in {color.interaction.focus.ring-container} at −4px | double boundary |
| disabled | text and chevron {color.text.disabled}; border {color.border.disabled}; background {color.interaction.disabled.bg} | `disabled`; cursor: not-allowed |

Inside the theme-owned list, a selected option is
{color.interaction.selection.bg} with {color.interaction.selection.text} and
group labels are {color.text.muted} on {color.surface.raised}. Precedence:
disabled > invalid > hover; focus-visible is always drawn.

#### Keyboard

The single control follows the select-only combobox pattern: arrows open and
move the active option, Home/End reach the ends, typing finds an option, and
Enter/Space commits. Escape and Tab close without committing. Multiple choices
use arrows, Space or click to toggle, Shift with arrows for a range, and
Ctrl/Command+A to select or clear all enabled choices. No modifier is required
for ordinary multiple selection. The native fallback uses host keyboard behavior.

#### Accessibility

A programmatic label is required; the first option is a real choice or a
placeholder option that is `disabled` and `selected` with text such as
"Choose a workspace", never an empty string. `aria-invalid="true"` only after
interaction or submit. Help and message through `aria-describedby`. The
themed popup uses the declared contrast roles. The visible control receives
the label, required/invalid state and descriptions. A failed native constraint
focuses that visible control and exposes a theme-owned error. Disabled options,
optgroups and fieldsets remain unavailable. Current values and defaults survive
reconnection, programmatic updates and form reset. Automated evidence records
its actual environments; it does not imply a manual screen-reader pass.

#### Portability

Use the packaged behavior and complete copy closure for web implementations.
The supported enhancement can apply the same renderer to existing selects
without moving them or duplicating form values. Native platforms keep the
mappings listed above; no-JavaScript pages expose the native fallback.

#### Non-examples

An unthemed browser popup presented as a fully themed implementation. A background-image chevron in
a literal colour. Rounded corners. A select whose first option is blank. A
select that triggers navigation on change. Invalid shown by colour alone.
A glow instead of the ring. Disabling by opacity.

### Checkbox (`checkbox`)

The native checkbox drawn as a 16px square that fills with the primary action colour and a ✓ or – glyph; single, or a fieldset group of independent choices. Family: forms-basic. Priority: R1. Maturity: stable. States: default, hover, focus-visible, checked, mixed, checked+focus-visible, checked+disabled, disabled, invalid, required.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| box.bg | `color.surface.input` | `#000000` | use |
| box.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| box.bg-hover | `color.interaction.hover.bg` | `#1c0a09` | use |
| box.ring | `color.interaction.focus.ring` | `#e53935` | use |
| box.bg-checked | `color.action.primary.bg` | `#7d1310` | use |
| box.bg-checked-hover | `color.action.primary.hover-bg` | `#911410` | use |
| box.glyph | `color.action.primary.text` | `#f4eeee` | use |
| box.ring-checked | `color.interaction.focus.ring-container` | `#ffa2a7` | use |
| box.border-invalid | `color.status.danger.border` | `#e53935` | use |
| box.bg-disabled | `color.interaction.disabled.bg` | `#160b0b` | use |
| box.border-disabled | `color.border.disabled` | `#7d1310` | use |
| box.glyph-disabled | `color.text.disabled` | `#8a5559` | use |
| text.default | `color.text.default` | `#e99499` | use |
| text.disabled | `color.text.disabled` | `#8a5559` | use |
| legend.text | `color.text.bright` | `#ffa2a7` | use |
| legend.required-mark | `color.status.danger.text` | `#f73f35` | use |
| message.text | `color.status.danger.text` | `#f73f35` | use |

#### Purpose

An independent yes/no choice, or several of them under one legend. A
setting that takes effect immediately is a switch; one of several exclusive
options is a radio group. A parent checkbox over a partly selected set shows
the mixed state.

#### Anatomy

The box is the native input itself with `appearance: none`, so it keeps
native focus, form and keyboard behaviour. The ✓ and – glyphs are inline
SVGs stacked over it in the same 16px cell. The option row is a `<label>`,
at least 24px tall, which makes the text part of the target. A group is a
`<fieldset>` with a `<legend>`.

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | 16px box, 1px {color.border.control} on {color.surface.input}; text {color.text.default} | — |
| hover | box background → {color.interaction.hover.bg}; checked box → {color.action.primary.hover-bg} | cursor: pointer; hover-capable pointers only |
| focus-visible | ring 1px dashed {color.interaction.focus.ring} at −2px around the box | the ring |
| checked | box fills {color.action.primary.bg}; ✓ in {color.action.primary.text}; border stays {color.border.control} | the ✓ glyph; `checked` |
| mixed | box fills {color.action.primary.bg}; – in {color.action.primary.text} | the – glyph; `indeterminate` / `aria-checked="mixed"` |
| checked+focus-visible | the fill and a ring in {color.interaction.focus.ring-container} | glyph and ring |
| checked+disabled | box {color.interaction.disabled.bg} with 1px {color.border.disabled}; ✓ and text in {color.text.disabled} | glyph; `disabled`; cursor: not-allowed |
| disabled | box {color.interaction.disabled.bg} with 1px {color.border.disabled}; text {color.text.disabled} | `disabled`; cursor: not-allowed |
| invalid | box border 2px {color.status.danger.border}; message with the ✕ glyph | border width 1 → 2px; glyph; `aria-invalid` |
| required | `*` after the text or legend in {color.status.danger.text} plus visually hidden "required" | `required`; the mark |

Precedence: disabled > invalid > checked > hover; focus-visible is always
drawn.

#### Keyboard

Native. Space toggles; Enter does nothing. Every checkbox in a group is a
separate tab stop, unlike radios. A mixed parent becomes checked on the
first Space and unchecked on the second, never mixed again by keyboard.

#### Accessibility

The text is the label through the wrapping `<label>`; a group has its
legend read before each option. `indeterminate` is a DOM property with no
attribute, so the host mirrors it to `data-state-mixed` for styling and the
state reads as "mixed" natively. A required group marks the legend, not
every option, and validates on submit. Contrast: text 8.43:1, glyph 9.28:1
on the fill, box edge 4.45:1 on the input surface and 4.33:1 against the
panel when checked, ring 4.69:1, container ring 5.57:1 on the fill.

#### Portability

A square with a border and a fill plus two glyphs. Hosts that draw their own
indicator set it square, 16px, and use the same fill and glyph colours; a
host without a mixed indicator draws the dash itself or records the gap.

#### Non-examples

Rounded or circular boxes. A tick drawn with a border trick or an icon
font. A checked state shown by border colour alone with no glyph. A box
smaller than 16px or a row shorter than 24px. The mixed state used as a
third value. A checkbox that saves on change. Disabling by opacity. A div
with `role="checkbox"`.

### Radio group (`radio-group`)

A fieldset of native radios for one exclusive choice; each radio is a 16px square that fills with the primary action colour and an inner square when checked. Family: forms-basic. Priority: R1. Maturity: stable. States: default, hover, focus-visible, checked, checked+focus-visible, disabled, invalid, required.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| box.bg | `color.surface.input` | `#000000` | use |
| box.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| box.bg-hover | `color.interaction.hover.bg` | `#1c0a09` | use |
| box.ring | `color.interaction.focus.ring` | `#e53935` | use |
| box.bg-checked | `color.action.primary.bg` | `#7d1310` | use |
| box.bg-checked-hover | `color.action.primary.hover-bg` | `#911410` | use |
| box.dot | `color.action.primary.text` | `#f4eeee` | use |
| box.ring-checked | `color.interaction.focus.ring-container` | `#ffa2a7` | use |
| box.border-invalid | `color.status.danger.border` | `#e53935` | use |
| box.bg-disabled | `color.interaction.disabled.bg` | `#160b0b` | use |
| box.border-disabled | `color.border.disabled` | `#7d1310` | use |
| box.dot-disabled | `color.text.disabled` | `#8a5559` | use |
| text.default | `color.text.default` | `#e99499` | use |
| text.disabled | `color.text.disabled` | `#8a5559` | use |
| legend.text | `color.text.bright` | `#ffa2a7` | use |
| legend.required-mark | `color.status.danger.text` | `#f73f35` | use |
| message.text | `color.status.danger.text` | `#f73f35` | use |

#### Purpose

One choice out of a few visible options. Below two options a checkbox or
switch is used; above roughly six, a select. The radios are square, as every
control in this theme is, and are told apart from checkboxes by the inner
square instead of the ✓ glyph and by arrow-key movement instead of separate
tab stops.

#### Anatomy

A `<fieldset>` whose `<legend>` asks the question; a list of `<label>` rows
each holding the native input, drawn as a 16px square with `appearance:
none`, and its text; the validation message at the end. The inner square is
a real element stacked over the input in the same cell.

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | 16px square, 1px {color.border.control} on {color.surface.input}; text {color.text.default} | — |
| hover | box background → {color.interaction.hover.bg}; a checked box → {color.action.primary.hover-bg} | cursor: pointer; hover-capable pointers only |
| focus-visible | ring 1px dashed {color.interaction.focus.ring} at −2px around the box | the ring |
| checked | box fills {color.action.primary.bg}; an 8px inner square in {color.action.primary.text}; border stays {color.border.control} | the inner square; `checked` |
| checked+focus-visible | the fill and a ring in {color.interaction.focus.ring-container} | inner square and ring |
| disabled | boxes {color.interaction.disabled.bg} with 1px {color.border.disabled}; inner square and text {color.text.disabled} | `disabled`; cursor: not-allowed |
| invalid | every box border 2px {color.status.danger.border}; message with the ✕ glyph | border width 1 → 2px; glyph; `aria-invalid` |
| required | `*` after the legend in {color.status.danger.text} plus visually hidden "required" | `required`; the mark |

Precedence: disabled > invalid > checked > hover; focus-visible is always
drawn.

#### Keyboard

Native. The group is one tab stop; the arrow keys move both focus and the
check, so moving through a group changes the value. That is why a radio
group never triggers navigation or a request on change; the value is read on
submit or through an explicit action.

#### Accessibility

The legend is read before each option and is the group's name. Options
share a `name`. The message is linked with `aria-describedby` on the
fieldset and `aria-invalid` is set on the inputs after submit. A group with
no default checked radio must have a required mark or a "none" option, so
the empty state is intentional. Contrast: text 8.43:1, inner square 9.28:1
on the fill, box edge 4.45:1 on the input surface and 4.33:1 against the
panel when checked, ring 4.69:1, container ring 5.57:1.

#### Portability

Square, border, fill and one inner square; no pseudo-element is needed.
Hosts that insist on a circular radio indicator record the deviation rather
than replacing the control with a custom widget.

#### Non-examples

Circular radios. A checked state shown by border colour alone with no inner
square. Radios that submit or navigate on change. A group with no legend or
with the legend rendered as a label on one option. Separate tab stops per
radio. One radio on its own. Disabling by opacity. A div with
`role="radiogroup"` built from spans.

### Switch (`switch`)

An on/off control that takes effect immediately; a button with role switch and aria-checked, drawn as a square-cornered track with a square thumb that fills with the primary colour when on. Family: forms-basic. Priority: R1. Maturity: stable. States: default, hover, focus-visible, checked, checked+focus-visible, disabled, checked+disabled.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| track.bg | `color.surface.input` | `#000000` | use |
| track.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| track.bg-hover | `color.interaction.hover.bg` | `#1c0a09` | use |
| track.ring | `color.interaction.focus.ring` | `#e53935` | use |
| track.bg-checked | `color.action.primary.bg` | `#7d1310` | use |
| track.bg-checked-hover | `color.action.primary.hover-bg` | `#911410` | use |
| track.ring-checked | `color.interaction.focus.ring-container` | `#ffa2a7` | use |
| track.bg-disabled | `color.interaction.disabled.bg` | `#160b0b` | use |
| track.border-disabled | `color.border.disabled` | `#7d1310` | use |
| thumb.bg | `color.text.default` | `#e99499` | use |
| thumb.bg-checked | `color.action.primary.text` | `#f4eeee` | use |
| thumb.bg-disabled | `color.text.disabled` | `#8a5559` | use |
| name.text | `color.text.default` | `#e99499` | use |
| state.text | `color.text.muted` | `#bd787d` | use |
| name.text-disabled | `color.text.disabled` | `#8a5559` | use |

#### Purpose

A setting that turns on or off the moment it is toggled: notifications,
wrap lines, follow system theme. A choice that is only applied on submit is
a checkbox. The switch is a button because it acts; it is not a form field.

#### Anatomy

A `<button role="switch">`; inside it the track, a 32×16px box with square
corners, and the thumb, a 10px square that sits at the start when off and
at the end when on. The with-labels variant adds a visible name before the
button and an "Off" / "On" word after the track.

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | track 1px {color.border.control} on {color.surface.input}; thumb {color.text.default} at the start | thumb position |
| hover | track background → {color.interaction.hover.bg}; on track → {color.action.primary.hover-bg} | cursor: pointer; hover-capable pointers only |
| focus-visible | ring 1px dashed {color.interaction.focus.ring} at −2px on the track | the ring |
| checked | track fills {color.action.primary.bg}; thumb {color.action.primary.text} moves to the end; border stays {color.border.control}; the word reads "On" | `aria-checked="true"`; thumb position; the word |
| checked+focus-visible | the fill and a ring in {color.interaction.focus.ring-container} on the track | position and ring |
| disabled | track {color.interaction.disabled.bg} with 1px {color.border.disabled}; thumb and name {color.text.disabled} | `disabled`; cursor: not-allowed |
| checked+disabled | as disabled with the thumb at the end | `disabled`; `aria-checked="true"`; thumb position |

The thumb moves without animation by default; a host may transition
`transform` for at most 120ms under `prefers-reduced-motion:
no-preference`. Precedence: disabled > checked > hover; focus-visible is
always drawn.

#### Keyboard

Space and Enter toggle. Nothing else is bound; there is no arrow-key
movement between switches. Focus never moves as a result of toggling.

#### Accessibility

`role="switch"` on a native button gives the correct name, role and value
with the button's own keyboard handling; only `aria-checked` is scripted.
The name states what is on ("Wrap long lines"), never the current value;
the "Off" / "On" word is `aria-hidden` because assistive technology reads
`aria-checked`. The state is also carried by thumb position and, in the
with-labels variant, the word. Contrast: thumb 8.65:1 on the off track,
9.28:1 on the on track; track edge 4.45:1; ring 4.69:1 off, 5.57:1 on.

#### Portability

Two nested rectangles, a border and a fill; the thumb position is layout,
not a transform, so it survives hosts with no animation. Hosts with a native
switch use it, square it, and map the two fills.

#### Non-examples

A pill-shaped track or a round thumb. A switch that only takes effect on
submit. A checkbox input styled as a switch. Colour as the only difference
between on and off. A green on colour. A label that reads "On" as the name.
Disabling by opacity. A sliding animation longer than 120ms or one that
runs under reduced motion.

### Fieldset (`fieldset`)

The native fieldset that groups related fields under a legend inside a 1px divider border; disabling it disables everything inside. Family: forms-basic. Priority: R1. Maturity: stable. States: default, disabled.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.border | `color.border.divider` | `#2b0e0d` | use-and-report (D-008) |
| root.bg | `color.surface.default` | `#100c0c` | use |
| legend.text | `color.text.bright` | `#ffa2a7` | use |
| body.text | `color.text.default` | `#e99499` | use |
| legend.text-disabled | `color.text.disabled` | `#8a5559` | use |
| field.bg | `color.surface.input` | `#000000` | use |
| field.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| field.bg-disabled | `color.interaction.disabled.bg` | `#160b0b` | use |
| field.border-disabled | `color.border.disabled` | `#7d1310` | use |
| field.text-disabled | `color.text.disabled` | `#8a5559` | use |
| help.text | `color.text.muted` | `#bd787d` | use |

#### Purpose

Grouping fields that belong together: an address, a set of credentials,
the options of one feature. The legend names the group for sighted users
and assistive technology alike, and disabling the fieldset disables every
control inside it in one step. Checkbox and radio groups use their own
components, which are fieldsets too.

#### Anatomy

The native `<fieldset>` with a 1px {color.border.divider} border and 16px
padding; the `<legend>` in {color.text.bright} sitting in the top border;
the fields stacked inside. The border is a section rule, decorative by
design; the legend and the spacing carry the grouping.

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | 1px {color.border.divider} border; legend {color.text.bright}; fields as their own components | the border and the legend |
| disabled | legend and labels {color.text.disabled}; every control inside takes its own disabled treatment ({color.interaction.disabled.bg}, {color.border.disabled}); the border unchanged | `disabled` on the fieldset; cursor: not-allowed on the controls |

#### Keyboard

None of its own. Disabled fieldsets remove their controls from the tab
order natively, so a disabled group is skipped rather than walked.

#### Accessibility

The legend is the accessible name of the group and is announced before the
first field inside; it says what the group is, not what to do. A group
never nests deeper than two levels. Disabling a group hides its controls
from keyboard users, so a disabled group states why in text before it or
keeps its controls enabled. Contrast: legend 10.10:1, labels 8.43:1, help
5.66:1 on the panel surface.

#### Portability

A border and a heading. Hosts without a legend-in-border draw the heading
above the box and record it. Disabled propagation is native on the web and
in GTK and Qt; a host without it disables each control.

#### Non-examples

A fieldset with no legend or with a legend hidden visually. A card used
instead of a fieldset for form controls. Rounded corners. A 2px or
`border.control` border on the group, which would read as a control. A
legend in accent red. Disabling only the legend. Nesting three levels deep.

### Number field (`number-field`)

The text-field box around a native number input with right-aligned tabular digits, an optional unit affix and two square stepper buttons that replace the host spinner. Family: forms-basic. Priority: R1. Maturity: stable. States: default, hover, focus-visible, invalid, invalid+focus-visible, disabled, read-only.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.input` | `#000000` | use |
| root.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| root.bg-hover | `color.interaction.hover.bg` | `#1c0a09` | use |
| root.border-focus | `color.border.active` | `#e53935` | use |
| root.ring | `color.interaction.focus.ring` | `#e53935` | use |
| root.ring-invalid | `color.interaction.focus.ring-container` | `#ffa2a7` | use |
| root.border-invalid | `color.status.danger.border` | `#e53935` | use |
| root.border-disabled | `color.border.disabled` | `#7d1310` | use |
| root.bg-disabled | `color.interaction.disabled.bg` | `#160b0b` | use |
| root.border-readonly | `color.border.divider` | `#2b0e0d` | use-and-report (D-008) |
| root.bg-readonly | `color.surface.canvas` | `#000000` | use |
| input.text | `color.text.default` | `#e99499` | use |
| input.text-disabled | `color.text.disabled` | `#8a5559` | use |
| input.caret | `color.code.caret` | `#e99499` | use |
| unit.text | `color.text.muted` | `#bd787d` | use |
| unit.border | `color.border.divider` | `#2b0e0d` | use-and-report (D-008) |
| action.text | `color.action.tertiary.text` | `#e99499` | use |
| action.bg-hover | `color.action.tertiary.hover-bg` | `#1c0a09` | use |
| action.border | `color.border.divider` | `#2b0e0d` | use-and-report (D-008) |
| label.text | `color.text.bright` | `#ffa2a7` | use |
| help.text | `color.text.muted` | `#bd787d` | use |
| message.text | `color.status.danger.text` | `#f73f35` | use |

#### Purpose

Entering a bounded number: a gap in pixels, a timeout in milliseconds, a
count. The theme replaces the host spinner, which is tiny and inconsistent,
with two square buttons that are real tab stops. Free-form numeric text such
as a version or an identifier is a text field with `inputmode`.

#### Anatomy

Label above; the box; the input with digits aligned to the end; the
optional unit; the stepper with + over −, each button half the control
height and at least 24px wide; help stating the range; the validation
message.

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | 1px {color.border.control} on {color.surface.input}; digits {color.text.default}; unit {color.text.muted}; stepper glyphs {color.action.tertiary.text} | — |
| hover | background → {color.interaction.hover.bg}; a hovered stepper button → {color.action.tertiary.hover-bg} | cursor: text on the input, pointer on the buttons |
| focus-visible | border → {color.border.active}; ring 1px dashed {color.interaction.focus.ring} at −2px on the box, or on the focused stepper button | the ring |
| invalid | border 2px {color.status.danger.border}; message with the ✕ glyph | border width 1 → 2px; glyph; `aria-invalid` |
| invalid+focus-visible | the 2px danger border and the ring in {color.interaction.focus.ring-container} at −4px | double boundary |
| disabled | text, unit and glyphs {color.text.disabled}; border {color.border.disabled}; background {color.interaction.disabled.bg}; buttons disabled | `disabled`; cursor: not-allowed |
| read-only | no box: 1px dotted {color.border.divider} bottom edge on {color.surface.canvas}; the stepper is hidden | `readonly`; dotted edge; no buttons |

Precedence: disabled > invalid > hover; focus-visible is always drawn.

#### Keyboard

Native arrow stepping inside the input; the stepper buttons are ordinary
buttons after it in the tab order and call `stepUp()` / `stepDown()`.
Typing a value outside the range is allowed and reported as invalid on
blur or submit, never clamped silently.

#### Accessibility

A programmatic label is required. The unit is `aria-hidden` and repeated in
the label or help ("Inner gap (px)"), so it is never lost. Stepper buttons
are named "Increase <label>" and "Decrease <label>". Help states min, max
and step before the user types. `aria-invalid` only after interaction or
submit. Contrast: digits 8.65:1, unit 5.81:1, glyphs 8.65:1, control border
4.45:1, invalid border 4.69:1.

#### Portability

The text field's box plus two buttons; the host spinner is hidden with the
engine's pseudo-elements and `appearance: textfield`. Hosts whose spin
buttons cannot be replaced style them square in the same colours and record
it.

#### Non-examples

The host's default spinner. Round or overlapping stepper buttons. A value
clamped silently on blur. Left-aligned digits. A unit typed into the value.
A number field for phone numbers, postcodes or identifiers. Invalid shown by
colour alone. Disabling by opacity.

### Search field (`search-field`)

The text-field box around a native search input with a magnifier, a clear button that appears with a value, an optional shortcut hint, a loading glyph and a no-results status line. Family: forms-basic. Priority: R1. Maturity: stable. States: default, hover, focus-visible, placeholder-shown, filled, loading, disabled, no-results.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.input` | `#000000` | use |
| root.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| root.bg-hover | `color.interaction.hover.bg` | `#1c0a09` | use |
| root.border-focus | `color.border.active` | `#e53935` | use |
| root.ring | `color.interaction.focus.ring` | `#e53935` | use |
| root.border-disabled | `color.border.disabled` | `#7d1310` | use |
| root.bg-disabled | `color.interaction.disabled.bg` | `#160b0b` | use |
| input.text | `color.text.default` | `#e99499` | use |
| input.text-disabled | `color.text.disabled` | `#8a5559` | use |
| input.placeholder | `color.text.placeholder` | `#a3676b` | use |
| input.caret | `color.code.caret` | `#e99499` | use |
| icon.stroke | `color.icon.default` | `#e99499` | use |
| loading.glyph | `color.text.muted` | `#bd787d` | use |
| clear.text | `color.action.tertiary.text` | `#e99499` | use |
| clear.bg-hover | `color.action.tertiary.hover-bg` | `#1c0a09` | use |
| kbd.text | `color.text.muted` | `#bd787d` | use |
| kbd.border | `color.border.default` | `#531310` | use |
| label.text | `color.text.bright` | `#ffa2a7` | use |
| help.text | `color.text.muted` | `#bd787d` | use |
| status.text | `color.text.muted` | `#bd787d` | use |

#### Purpose

Filtering a list or table or searching a site. It is the text field's box
with the parts a search needs: an icon that says what the box is for, a
clear button, a place for the shortcut that reaches it, a loading glyph
while results arrive and a status line when nothing matches. A search that
offers suggestions as you type is the combobox component.

#### Anatomy

Label above (or visually hidden); the box with the magnifier, the input,
the clear button and the optional hint; help; the status line. The loading
glyph takes the magnifier's slot so the box never changes size.

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | 1px {color.border.control} on {color.surface.input}; magnifier {color.icon.default}; text {color.text.default} | the magnifier |
| hover | background → {color.interaction.hover.bg}; the clear button → {color.action.tertiary.hover-bg} when hovered | cursor: text |
| focus-visible | border → {color.border.active}; ring 1px dashed {color.interaction.focus.ring} at −2px; the hint hides | the ring |
| placeholder-shown | placeholder in {color.text.placeholder}, italic; no clear button | italic; the clear button is absent |
| filled | as default with a value; the clear button is present | the clear button |
| loading | the static `⋯` glyph in {color.text.muted} replaces the magnifier; the input stays editable | glyph; `aria-busy` |
| disabled | text and icon {color.text.disabled}; border {color.border.disabled}; background {color.interaction.disabled.bg}; no clear button | `disabled`; cursor: not-allowed |
| no-results | the status line "No results for …" in {color.text.muted} below the help; the box unchanged | the status text; `role="status"` |

Precedence: disabled > loading > hover; focus-visible is always drawn.
A search field is never invalid; an empty result is a status, not an error.

#### Keyboard

Escape clears a non-empty value and keeps focus, which is native for
`type="search"` in most engines and scripted where it is not. The clear
button is a separate tab stop only while it is visible. The shortcut in the
hint is bound by the host and never captured while another field has focus.

#### Accessibility

A programmatic label is required even when hidden. The landmark
`role="search"` lets users jump to it. The status line is `role="status"`
so "No results" is announced once, after the query settles, and the result
count is announced there too ("12 results"). The magnifier and hint are
`aria-hidden`; the clear button is named "Clear <label>". Contrast: text
8.65:1, placeholder 5.81:1, magnifier 8.65:1, glyph 5.81:1, hint 5.81:1,
control border 4.45:1.

#### Portability

The text field plus icons and a status line. Hosts hide their own cancel
button; hosts that cannot draw the hint omit it and keep the shortcut in the
tooltip.

#### Non-examples

Rounded or pill-shaped search boxes. A magnifier button that submits
instead of an icon. A spinner while loading. Results announced on every
keystroke. "No results" in red or with the ✕ glyph. A clear button that is
always visible. A search field without a label. Disabling by opacity.

### Range (`range`)

A bounded native numeric slider with visible output. Family: forms-basic. Priority: R2. Maturity: stable. States: default, focus-visible, disabled.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.default` | `#100c0c` | use |
| root.text | `color.text.default` | `#e99499` | use |
| root.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| heading.text | `color.text.bright` | `#ffa2a7` | use |
| secondary.text | `color.text.muted` | `#bd787d` | use |
| control.bg | `color.surface.input` | `#000000` | use |
| control.focus | `color.interaction.focus.ring` | `#e53935` | use |
| dependency.role-border-width-default | `border.width.default` | `1px` | use |
| dependency.role-color-action-primary-bg | `color.action.primary.bg` | `#7d1310` | use |
| dependency.role-color-border-disabled | `color.border.disabled` | `#7d1310` | use |
| dependency.role-color-interaction-disabled-bg | `color.interaction.disabled.bg` | `#160b0b` | use |
| dependency.role-color-text-disabled | `color.text.disabled` | `#8a5559` | use |
| dependency.role-density-comfortable-control-height | `density.comfortable.control-height` | `32px` | use |
| dependency.role-density-compact-control-height | `density.compact.control-height` | `24px` | use |
| dependency.role-radius-none | `radius.none` | `0px` | use |
| dependency.role-space-12 | `space.12` | `12px` | use |
| dependency.role-space-2 | `space.2` | `2px` | use |
| dependency.role-space-8 | `space.8` | `8px` | use |

#### Purpose

Set a numeric value with a native range input and a visible output. The input retains min, max, step, disabled, name/value submission and form reset. Platform track geometry is a native rendering boundary; the semantic accent and focus roles remain explicit.

#### Anatomy

- **label:** Visible label linked to the input.
- **input:** Native range control with caller-specified bounds.
- **output:** Current numeric value linked using for.

#### States

| State | Presentation | Non-colour channel |
| --- | --- | --- |
| default | Named content on the default surface. | Native text and structure. |
| focus-visible | Dashed focus ring. | Native keyboard focus position. |
| disabled | Disabled semantic foreground, background and border at full opacity. | Native disabled state blocks activation. |

#### Keyboard

- **Tab / Shift+Tab:** Reach and leave the slider.
- **Arrow keys / Home / End:** Use native stepping, minimum and maximum behavior.

#### Accessibility

Retain the input's accessible name and native value semantics. The visible output does not replace its label. Put units in the label or help text. No value changes solely from receiving focus.

#### Portability

Wrap labels and actions without changing source order. Use logical spacing in RTL. Preserve native target sizes at both densities; only bounded data regions may scroll horizontally. Use equivalent native semantics and approved roles; record any unsupported behavior as a mapping deviation. The host owns application data, persistence, permissions and services.

#### Non-examples

Unlabelled controls, color-only status, drag-only operations, hidden required instructions, stolen focus or an invented service success.

## Advanced forms

Combobox, multiselect, date and time, file selection, repeaters, wizard, bounded form-builder specimen.

### Combobox (`combobox`)

A text input that filters a listbox of suggestions; the APG combobox with list autocomplete, rendered with the list open. Family: forms-advanced. Priority: R1. Maturity: stable. States: default, hover, focus-visible, open, closed, selected, invalid, invalid+focus-visible, disabled, no-results.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.input` | `#000000` | use |
| root.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| root.bg-hover | `color.interaction.hover.bg` | `#1c0a09` | use |
| root.border-focus | `color.border.active` | `#e53935` | use |
| root.ring | `color.interaction.focus.ring` | `#e53935` | use |
| root.ring-invalid | `color.interaction.focus.ring-container` | `#ffa2a7` | use |
| root.border-invalid | `color.status.danger.border` | `#e53935` | use |
| root.border-disabled | `color.border.disabled` | `#7d1310` | use |
| root.bg-disabled | `color.interaction.disabled.bg` | `#160b0b` | use |
| input.text | `color.text.default` | `#e99499` | use |
| input.text-disabled | `color.text.disabled` | `#8a5559` | use |
| input.placeholder | `color.text.placeholder` | `#a3676b` | use |
| input.caret | `color.code.caret` | `#e99499` | use |
| toggle.text | `color.action.tertiary.text` | `#e99499` | use |
| toggle.bg-hover | `color.action.tertiary.hover-bg` | `#1c0a09` | use |
| label.text | `color.text.bright` | `#ffa2a7` | use |
| listbox.bg | `color.surface.raised` | `#160b0b` | use |
| listbox.border | `color.border.overlay` | `#e53935` | use-and-report (D-008) |
| option.text | `color.text.default` | `#e99499` | use |
| option.bg-hover | `color.interaction.hover.bg-strong` | `#630f0d` | use |
| option.text-hover | `color.text.link-hover` | `#f4eeee` | use |
| option.bg-selected | `color.interaction.selection.bg` | `#531310` | use |
| option.text-selected | `color.interaction.selection.text` | `#f4eeee` | use |
| option.ring-selected | `color.interaction.focus.ring-container` | `#ffa2a7` | use |
| group.text | `color.text.subtle` | `#ad7175` | use |
| group.border | `color.border.divider` | `#2b0e0d` | use-and-report (D-008) |
| empty.text | `color.text.muted` | `#bd787d` | use |
| help.text | `color.text.muted` | `#bd787d` | use |
| message.text | `color.status.danger.text` | `#f73f35` | use |

#### Purpose

A text input whose value is completed from a list of suggestions. It is the
right control when the set is large or open-ended (fonts, hosts, tags); a
closed set of a dozen values is the select component. The demonstration
renders the list open so every option state is visible without a script.

#### Anatomy

Label above the box; the box with the input and an optional toggle; the
listbox directly below, on {color.surface.raised} behind a 1px
{color.border.overlay}; options in {color.text.default}, an optional group
heading in {color.text.subtle} above a {color.border.divider} rule; the empty
message in {color.text.muted}; help and validation message below, as in the
text field.

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | 1px {color.border.control} on {color.surface.input}; list on {color.surface.raised} with a 1px {color.border.overlay} | — |
| hover | box background → {color.interaction.hover.bg}; the hovered option → {color.interaction.hover.bg-strong} with {color.text.link-hover} | cursor: text on the box, default on the list |
| focus-visible | box border → {color.border.active}; ring 1px dashed {color.interaction.focus.ring} at −2px | the ring |
| open | list visible below the box; toggle glyph rotated 180° | `aria-expanded="true"`; the toggle glyph |
| closed | list absent; the box unchanged | `aria-expanded="false"`; nothing below the box |
| selected | the active option filled {color.interaction.selection.bg} with {color.interaction.selection.text} and a 1px dashed ring in {color.interaction.focus.ring-container} | `aria-selected="true"`; `aria-activedescendant`; the ring |
| invalid | box border 2px {color.status.danger.border}; message with the ✕ glyph | border width 1 → 2px; glyph; `aria-invalid` |
| invalid+focus-visible | the 2px danger border and the ring in {color.interaction.focus.ring-container} at −4px | double boundary |
| disabled | text {color.text.disabled}; border {color.border.disabled}; background {color.interaction.disabled.bg}; list never opens | `disabled`; cursor: not-allowed |
| no-results | the list holds only the empty row in {color.text.muted}, italic | `aria-disabled` row; italic; a polite announcement of zero matches |

Precedence when several apply: disabled > invalid > selected > hover;
focus-visible is always drawn on the box, and the selection ring on the
option.

#### Keyboard

DOM focus never leaves the input. Down and Up open the list and move the
active option, which the input reports through `aria-activedescendant`; Enter
accepts it; Escape closes the list and, when the list is already closed,
clears the value. Alt+Down and Alt+Up open and close without changing the
active option. Home and End move the caret. Tab closes the list and keeps
whatever is typed. Typing filters the list on every keystroke; the number of
matches is announced through a polite live region, not by re-reading the list.

#### Accessibility

The input carries `role="combobox"`, `aria-autocomplete="list"`,
`aria-expanded`, `aria-controls` pointing at the listbox and, while an option
is active, `aria-activedescendant`. The listbox has an accessible name
(`aria-label` or `aria-labelledby` the field label); options are `role="option"`
with `aria-selected` on the active one only; groups are `role="group"` with
`aria-labelledby` their heading. The empty row is a disabled option so the
list is never empty; the zero-match count is announced through the same polite
live region as every other count. Contrast: option text 8.43:1 on the
raised surface, selection text 12.47:1 on the selection fill, the selection ring
7.48:1 on the fill, the list border 4.57:1 on the raised surface, group
headings 4.96:1.

#### Portability

Everything is expressible with background, border and outline; the toggle
rotation is a transform inside the motion budget. Toolkits with a native
completion popup use it and map the option fill to the selection roles; the
list border is the popover frame. Toolkits without `aria-activedescendant`
move real focus to the option and record the deviation.

#### Non-examples

A select restyled as a combobox. Moving DOM focus into the list on Down. A
list that floats over the page with a shadow or a rounded frame. Highlighting
the active option with a colour change of the text alone. A no-results state
that silently shows the full list. Opening on hover. A list that slides or
fades in for longer than 150 ms.

### Date picker (`date-picker`)

A themed date editor and calendar, alone or as a from/to pair, backed by native form values and constraints. Family: forms-advanced. Priority: R1. Maturity: stable. States: default, hover, focus-visible, invalid, invalid+focus-visible, disabled, read-only.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.input` | `#000000` | use |
| root.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| root.bg-hover | `color.interaction.hover.bg` | `#1c0a09` | use |
| root.border-focus | `color.border.active` | `#e53935` | use |
| root.ring | `color.interaction.focus.ring` | `#e53935` | use |
| root.ring-invalid | `color.interaction.focus.ring-container` | `#ffa2a7` | use |
| root.border-invalid | `color.status.danger.border` | `#e53935` | use |
| root.border-disabled | `color.border.disabled` | `#7d1310` | use |
| root.bg-disabled | `color.interaction.disabled.bg` | `#160b0b` | use |
| root.border-readonly | `color.border.divider` | `#2b0e0d` | use-and-report (D-008) |
| root.bg-readonly | `color.surface.canvas` | `#000000` | use |
| input.text | `color.text.default` | `#e99499` | use |
| input.text-disabled | `color.text.disabled` | `#8a5559` | use |
| input.caret | `color.code.caret` | `#e99499` | use |
| label.text | `color.text.bright` | `#ffa2a7` | use |
| legend.text | `color.text.bright` | `#ffa2a7` | use |
| separator.text | `color.text.muted` | `#bd787d` | use |
| help.text | `color.text.muted` | `#bd787d` | use |
| message.text | `color.status.danger.text` | `#f73f35` | use |
| calendar.bg | `color.surface.raised` | `#160b0b` | use |
| calendar.border | `color.border.overlay` | `#e53935` | use-and-report (D-008) |
| calendar.text | `color.text.default` | `#e99499` | use |
| calendar.selected-bg | `color.interaction.selection.bg` | `#531310` | use |
| calendar.selected-text | `color.interaction.selection.text` | `#f4eeee` | use |

#### Purpose

Choose a date by explicit text entry or a themed calendar. A range is two separately labeled date controls. D-025 requires theme-owned web controls rather than browser popup styling.

#### Anatomy

A label, editor, calendar trigger, format hint and validation message. The native date input remains the successful form control and owns its value, defaults and constraints. It is hidden only after enhancement.

#### States

| State | Treatment |
| --- | --- |
| default | Input surface, default text and control border. |
| hover | The declared hover fill. |
| focus-visible | Active border and the canonical focus ring. |
| invalid | Danger border and a linked message. |
| invalid+focus-visible | Danger border plus the container focus ring. |
| disabled | Disabled colors; editor and calendar actions unavailable. |
| read-only | Readable canvas text and bottom edge; calendar action unavailable. |

Use the declared state tokens: default input surface and control border; hover fill; the canonical focus ring; danger border and message for invalid input; disabled colors and unavailable actions. Read-only values remain readable and cannot open a picker or change through step buttons. The date read-only treatment retains the canvas surface and unobtrusive bottom edge.

#### Keyboard

Alt+Down opens the calendar. Arrows move by a day or week, Home/End reach week boundaries, and PageUp/PageDown change months. Enter/Space chooses a focused date. Escape closes and restores editor focus. Tab leaves normally; the calendar is a nonmodal popup.

#### Accessibility

The visible editor receives the original label, descriptions, required state and error state. Format hints are always available. Native min, max, step and required constraints remain authoritative. Invalid entry focuses the visible editor and exposes a theme-owned message. Calendar selection is a fill; keyboard focus is a separate ring. Disabled dates cannot be selected. The range validates that its end is not before its start. Form reset restores the native defaults.

Automated evidence records its actual browser, states and limitations; it is not a manual screen-reader acceptance.

#### Portability

The package and complete copy distribution include the renderer. The public control enhancement applies the same behavior to existing native markup. The no-JavaScript fallback and non-web platform mappings remain native.

#### Non-examples

An unthemed host popup presented as fully themed. A second named input that duplicates form values. Discarding native constraints, labels or reset behavior. Literal colors, rounded corners, or a selected fill with no distinguishable focus boundary.

### Time picker (`time-picker`)

A themed time editor with explicit step buttons, backed by native time values and constraints. Family: forms-advanced. Priority: R1. Maturity: stable. States: default, hover, focus-visible, invalid, disabled.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.input` | `#000000` | use |
| root.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| root.bg-hover | `color.interaction.hover.bg` | `#1c0a09` | use |
| root.border-focus | `color.border.active` | `#e53935` | use |
| root.ring | `color.interaction.focus.ring` | `#e53935` | use |
| root.border-invalid | `color.status.danger.border` | `#e53935` | use |
| root.border-disabled | `color.border.disabled` | `#7d1310` | use |
| root.bg-disabled | `color.interaction.disabled.bg` | `#160b0b` | use |
| input.text | `color.text.default` | `#e99499` | use |
| input.text-disabled | `color.text.disabled` | `#8a5559` | use |
| input.caret | `color.code.caret` | `#e99499` | use |
| label.text | `color.text.bright` | `#ffa2a7` | use |
| help.text | `color.text.muted` | `#bd787d` | use |
| message.text | `color.status.danger.text` | `#f73f35` | use |

#### Purpose

Enter a time explicitly or adjust it using themed step buttons. D-025 requires theme-owned web controls rather than browser popup styling.

#### Anatomy

A label, editor, increase/decrease buttons, format hint and validation message. The native time input remains the successful form control and owns its value, defaults and constraints. It is hidden only after enhancement.

#### States

| State | Treatment |
| --- | --- |
| default | Input surface, default text and control border. |
| hover | The declared hover fill. |
| focus-visible | Active border and the canonical focus ring. |
| invalid | Danger border and a linked message. |
| disabled | Disabled colors; editor and step actions unavailable. |

Use the declared state tokens: default input surface and control border; hover fill; the canonical focus ring; danger border and message for invalid input; disabled colors and unavailable actions. Read-only values remain readable and cannot open a picker or change through step buttons. The date read-only treatment retains the canvas surface and unobtrusive bottom edge.

#### Keyboard

Type HH:MM or HH:MM:SS in the editor. Tab reaches the named step buttons; Enter/Space activates them. Each step uses the native input step attribute. With step="any", use direct entry and the step buttons remain unavailable.

#### Accessibility

The visible editor receives the original label, descriptions, required state and error state. Format hints are always available. Native min, max, step and required constraints remain authoritative. Invalid entry focuses the visible editor and exposes a theme-owned message. No host clock popup or browser segment selection is used in the enhanced control. Form reset restores the native defaults.

Automated evidence records its actual browser, states and limitations; it is not a manual screen-reader acceptance.

#### Portability

The package and complete copy distribution include the renderer. The public control enhancement applies the same behavior to existing native markup. The no-JavaScript fallback and non-web platform mappings remain native.

#### Non-examples

An unthemed host popup presented as fully themed. A second named input that duplicates form values. Discarding native constraints, labels or reset behavior. Literal colors, rounded corners, or a selected fill with no distinguishable focus boundary.

### File input (`file-input`)

A native file input behind a label styled as a secondary button, optionally inside a drop zone, with a list of chosen files and per-file errors. Family: forms-advanced. Priority: R1. Maturity: stable. States: default, hover, focus-visible, drop-target, invalid, disabled, loading.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| button.bg | `color.action.secondary.bg` | `rgb(0 0 0 / 0%)` | use |
| button.text | `color.action.secondary.text` | `#ffa2a7` | use |
| button.border | `color.action.secondary.border` | `#a3676b` | use |
| button.bg-hover | `color.action.secondary.hover-bg` | `#630f0d` | use |
| button.bg-pressed | `color.action.secondary.pressed-bg` | `#420f0c` | use |
| button.ring | `color.interaction.focus.ring` | `#e53935` | use |
| button.border-invalid | `color.status.danger.border` | `#e53935` | use |
| button.text-disabled | `color.text.disabled` | `#8a5559` | use |
| button.border-disabled | `color.border.disabled` | `#7d1310` | use |
| button.bg-disabled | `color.interaction.disabled.bg` | `#160b0b` | use |
| zone.border | `color.interaction.drop-target` | `#e53935` | use |
| zone.bg-drop-target | `color.interaction.marquee` | `rgb(145 20 16 / 12%)` | use |
| zone.hint | `color.text.muted` | `#bd787d` | use |
| label.text | `color.text.bright` | `#ffa2a7` | use |
| status.text | `color.text.muted` | `#bd787d` | use |
| list.text | `color.text.default` | `#e99499` | use |
| list.divider | `color.border.divider` | `#2b0e0d` | use-and-report (D-008) |
| list.size | `color.text.subtle` | `#ad7175` | use |
| list.error | `color.status.danger.text` | `#f73f35` | use |
| remove.text | `color.action.tertiary.text` | `#e99499` | use |
| remove.bg-hover | `color.action.tertiary.hover-bg` | `#1c0a09` | use |
| progress.track | `color.interaction.pressed.bg` | `#420f0c` | use |
| progress.stripe | `color.action.primary.bg` | `#7d1310` | use |
| help.text | `color.text.muted` | `#bd787d` | use |
| message.text | `color.status.danger.text` | `#f73f35` | use |

#### Purpose

Choosing one or more files from the host. The native input keeps the host
chooser, its keyboard behaviour and its security model; the theme replaces
only the input's own rendering with a button-styled label, adds an optional
drop zone, and lists what was chosen with any per-file error.

#### Anatomy

The field label; the root row (or the drop zone) holding the button-styled
label and the status text; the hidden-but-focusable native input; an optional
list of chosen files, each with name, size in {color.text.subtle} and a
remove button, one of which may carry the ✕ glyph and an error in
{color.status.danger.text}; a static striped progress bar while uploading;
help and validation message below.

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | button: 1px {color.action.secondary.border} outline, text {color.action.secondary.text}; zone: 1px dashed {color.interaction.drop-target} | — |
| hover | button background → {color.action.secondary.hover-bg}; border unchanged | cursor: pointer |
| focus-visible | ring 1px dashed {color.interaction.focus.ring} at −2px on the button (the input has focus) | the ring |
| drop-target | root fill {color.interaction.marquee}; border 1px dashed {color.interaction.drop-target} | dashed pattern; the hint text changes to "Release to add" |
| invalid | button border 2px {color.status.danger.border}; message with the ✕ glyph; a failed row shows its own ✕ | border width 1 → 2px; glyphs; `aria-invalid` |
| disabled | button text {color.text.disabled}, border {color.border.disabled}, background {color.interaction.disabled.bg}; the zone's dashes take {color.border.disabled}; no hover | `disabled`; cursor: not-allowed |
| loading | a static bar of 45° stripes, {color.action.primary.bg} on {color.interaction.pressed.bg}, below the row; the button stays enabled | `aria-busy`; the status text reads "Uploading n files"; no animation |

Precedence when several apply: disabled > loading > invalid > drop-target >
hover; focus-visible is always drawn.

#### Keyboard

Tab reaches the native input, which is drawn as the button; Enter or Space
opens the host chooser; Escape in the chooser returns to the input. Each
remove button in the list is its own tab stop after the input, and Delete or
Backspace on it acts like Enter. Dropping is a pointer gesture only and never
the sole way to add a file.

#### Accessibility

The native input is hidden with the `sr-only` pattern, never `display: none`
or `visibility: hidden`, so it keeps focus and the host chooser. It is named
by both labels ("Attachments" and "Choose files"), and `aria-describedby`
lists help and message. The status text and the list are announced through a
polite live region after a choice; per-file errors are in the row text, not
only in colour. The drop zone has no role and no `tabindex`; `aria-busy` on
the root marks uploading. Contrast: button text 10.37:1 on the canvas and
6.84:1 on the hover fill, button border 4.45:1, drop-target border 4.69:1 on
the canvas and 4.52:1 on the marquee fill, status and help 5.81:1, sizes
5.10:1, danger text 5.40:1.

#### Portability

The button is the secondary button of the actions family; the zone is a
dashed border and a translucent fill; the stripes are a repeating gradient
(or a hatched brush). Toolkits without drag-and-drop omit the zone and keep
the button; toolkits without a translucent fill composite the marquee over the
canvas and record the value.

#### Non-examples

A hidden input with `display: none` and a button that clicks it by script. A
drop zone that is the only way to add files. A rounded, dotted, animated
"pulsing" zone. A spinner while uploading. A progress bar that animates its
stripes. Errors shown by a red file name alone. Disabling by opacity.

### Wizard (`wizard`)

A multi-step form with a stepper header, the current panel and back/next actions; native buttons with aria-current="step". Family: forms-advanced. Priority: R1. Maturity: stable. States: default, hover, focus-visible, current, disabled, invalid, busy.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| steps.divider | `color.border.divider` | `#2b0e0d` | use-and-report (D-008) |
| step.text | `color.text.default` | `#e99499` | use |
| step.bg-hover | `color.interaction.hover.bg` | `#1c0a09` | use |
| step.ring | `color.interaction.focus.ring` | `#e53935` | use |
| step.text-current | `color.text.bright` | `#ffa2a7` | use |
| step.indicator-current | `color.border.selected-indicator` | `#e53935` | use |
| step.text-disabled | `color.text.disabled` | `#8a5559` | use |
| step.marker-complete | `color.status.success.text` | `#86a46f` | use |
| step.marker-invalid | `color.status.danger.text` | `#f73f35` | use |
| step.indicator-invalid | `color.status.danger.border` | `#e53935` | use |
| panel.bg | `color.surface.default` | `#100c0c` | use |
| panel.border | `color.border.default` | `#531310` | use |
| title.text | `color.text.bright` | `#ffa2a7` | use |
| text.text | `color.text.default` | `#e99499` | use |
| message.text | `color.status.danger.text` | `#f73f35` | use |
| next.bg | `color.action.primary.bg` | `#7d1310` | use |
| next.text | `color.action.primary.text` | `#f4eeee` | use |
| next.bg-hover | `color.action.primary.hover-bg` | `#911410` | use |
| next.bg-pressed | `color.action.primary.pressed-bg` | `#630f0d` | use |
| next.border | `color.action.primary.border` | `#9e231f` | use |
| next.ring | `color.interaction.focus.ring-container` | `#ffa2a7` | use |
| back.text | `color.action.secondary.text` | `#ffa2a7` | use |
| back.border | `color.action.secondary.border` | `#a3676b` | use |
| back.bg-hover | `color.action.secondary.hover-bg` | `#630f0d` | use |
| busy.glyph | `color.action.primary.text` | `#f4eeee` | use |

#### Purpose

A form too long for one screen, split into ordered steps that are validated
one at a time. The stepper shows where the person is and which steps are
complete; the panel holds the current step; Back and Next move through the
sequence. Steps are not tabs: a future step is unreachable until the steps
before it pass.

#### Anatomy

The stepper, an ordered list of step buttons over a {color.border.divider}
rule; each step's marker and name; the panel on {color.surface.default} with
its "Step n of m" heading in {color.text.bright}; the actions row with Back
(secondary) and Next (primary, {color.action.primary.bg} with
{color.action.primary.text}).

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | steps in {color.text.default}; complete steps carry the ✓ in {color.status.success.text} | the ✓ glyph; the numbers |
| hover | step background → {color.interaction.hover.bg}; Next → {color.action.primary.hover-bg}; Back → {color.action.secondary.hover-bg} | cursor: pointer |
| focus-visible | ring 1px dashed {color.interaction.focus.ring} at −2px; on Next's fill the ring takes {color.interaction.focus.ring-container} | the ring |
| current | 2px {color.border.selected-indicator} under the step; name in {color.text.bright} | `aria-current="step"`; the indicator bar; the heading reads "Step n of m" |
| disabled | a future step in {color.text.disabled}; no hover | `disabled`; cursor: not-allowed; skipped by Tab |
| invalid | the step's marker becomes ✕ in {color.status.danger.text}; its indicator takes {color.status.danger.border}; the panel shows the message | glyph; message text; `aria-invalid` on the failing fields |
| busy | Next shows the static ⋯ glyph before its label and does not respond; size unchanged | `aria-busy` on the wizard; the glyph; cursor: progress |

Precedence when several apply: disabled > busy > invalid > current > hover;
focus-visible is always drawn.

#### Keyboard

Tab walks the enabled step buttons in order, then the panel's fields, then
Back and Next. Enter or Space on a reachable step goes to it; on Next it
validates the panel, and on failure moves focus to the first invalid field,
on success advances and moves focus to the new panel heading. Back never
validates. There is no arrow-key navigation between steps, because the
stepper is a list of buttons, not a tablist.

#### Accessibility

The wizard is a region named by `aria-label`; the stepper is an `<ol>` so
count and position are announced; the current step carries
`aria-current="step"`; complete steps append visually hidden ", complete";
unreachable steps are `disabled`, not `aria-disabled`, because they cannot be
activated. The panel is a `<section>` named by its heading, which receives
focus after each step change. Validation failures are announced through the
form's live region and the ✕ marker; `aria-busy` marks the wizard while Next
is in progress. Contrast: step text 8.65:1, current name 10.37:1, complete
marker 7.13:1, indicator 4.69:1, Next text 9.28:1 on its fill, ring 5.57:1 on
the primary fill, future step text 3.33:1 (exempt).

#### Portability

Everything is text, a 2px bottom border and the button tones; no icon font
(the markers are text glyphs). Toolkits with a native assistant use its page
list and map the indicator to the current page; toolkits with none draw the
stepper as a row of buttons and record any deviation in the indicator width.

#### Non-examples

Tabs restyled as steps. A progress bar in place of the stepper. Circles with
connecting lines that need a rounded radius. Future steps that look enabled
and reject the click. A spinner on Next. Colour-only completion marks.
Validation that blocks Back. Animated slides between panels.

### Form builder (`form-builder`)

A bounded reference editor for five canonical field kinds, with keyboard ordering, local preview and explicit JSON definition import and export. Family: forms-advanced. Priority: L. Maturity: draft. States: default, empty, invalid, disabled.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.default` | `#100c0c` | use |
| root.text | `color.text.default` | `#e99499` | use |
| root.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| status.text | `color.text.muted` | `#bd787d` | use |

#### Purpose

Build a bounded reference form from text, textarea, select, checkbox and radio
fields in the [interactive builder](https://j3w1.github.io/theme/builder/). D-021 remains proposed. This
draft component does not promote new tokens, roles, profiles or native ports.

#### Anatomy

Each field has a stable ID, label, help, required flag and, for select/radio,
stable option IDs and labels. Add and remove fields, edit definitions and move
them with explicit Move up / Move down buttons. Saving an edit validates the
complete proposed definition before replacing the current one.

The preview uses the same canonical field renderer and validation model as
the validation-recovery composition. Changing a definition clears preview
values and errors. Merely moving focus or typing a preview value does not
change the definition. Preview state is separate from schema state.

#### States

The empty state displays an explanation and an add action. An invalid import or definition
edit reports a corrective message and leaves the current definition intact.
Unavailable movement buttons are disabled at the list boundaries. The default state
shows the ordered definition and preview. The static specimen represents the
definition; interactive execution evidence is recorded separately.

#### Keyboard

All operations use native controls. No drag operation is required. Move actions
keep focus on the moved field's controls. Removing a field focuses the next
remaining field or the add control. Import and reset return to the add control.
Preview submit focuses its error summary; links target stable field control IDs.

#### Accessibility

Use the canonical label, help and error associations, required markers and
radio fieldset/legend semantics. Stable IDs survive JSON round trips and field
reordering. Imported strings are text, never markup. No manual screen-reader
pass is claimed by test presence.

#### Portability

Definition schema version 1 pins theme name, version, default profile and
constituent component content digest. Accept at most 64 KiB, 20 fields, 20
options per choice field, 120-character labels and 400-character help. Preview
text is limited to 2000 characters. Unknown keys, duplicate IDs, unsupported
kinds, invalid choices and mismatched theme identity are rejected atomically.
An old content identity requires an explicit migration outside this tool.

Everything stays in memory. Only explicit Download definition creates a file,
containing the schema and no entered values. Reset clears the definition and
preview. There is no autosave, network submission, schema execution, arbitrary
HTML/CSS/JavaScript or imported validation expression.

#### Non-examples

A production backend, conditional logic engine, drag-only editor, arbitrary
code generator, persistent account form, or native application port.

### Multiple selection (`multiselect`)

A searchable native checkbox group that preserves all selected form values. Family: forms-advanced. Priority: R2. Maturity: stable. States: default, checked, focus-visible, disabled, no-results.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.default` | `#100c0c` | use |
| root.text | `color.text.default` | `#e99499` | use |
| root.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| heading.text | `color.text.bright` | `#ffa2a7` | use |
| secondary.text | `color.text.muted` | `#bd787d` | use |
| control.bg | `color.surface.input` | `#000000` | use |
| control.focus | `color.interaction.focus.ring` | `#e53935` | use |
| dependency.role-border-width-default | `border.width.default` | `1px` | use |
| dependency.role-border-width-emphasis | `border.width.emphasis` | `2px` | use |
| dependency.role-color-action-primary-bg | `color.action.primary.bg` | `#7d1310` | use |
| dependency.role-color-action-primary-hover-bg | `color.action.primary.hover-bg` | `#911410` | use |
| dependency.role-color-action-primary-text | `color.action.primary.text` | `#f4eeee` | use |
| dependency.role-color-border-active | `color.border.active` | `#e53935` | use |
| dependency.role-color-border-disabled | `color.border.disabled` | `#7d1310` | use |
| dependency.role-color-interaction-disabled-bg | `color.interaction.disabled.bg` | `#160b0b` | use |
| dependency.role-color-interaction-focus-ring-container | `color.interaction.focus.ring-container` | `#ffa2a7` | use |
| dependency.role-color-interaction-hover-bg | `color.interaction.hover.bg` | `#1c0a09` | use |
| dependency.role-color-status-danger-border | `color.status.danger.border` | `#e53935` | use |
| dependency.role-color-status-danger-text | `color.status.danger.text` | `#f73f35` | use |
| dependency.role-color-text-disabled | `color.text.disabled` | `#8a5559` | use |
| dependency.role-density-comfortable-control-height | `density.comfortable.control-height` | `32px` | use |
| dependency.role-density-compact-control-height | `density.compact.control-height` | `24px` | use |
| dependency.role-focus-offset | `focus.offset` | `-2px` | use |
| dependency.role-focus-ring | `focus.ring` | `1px dashed #e53935` | use |
| dependency.role-font-line-height-ui-md | `font.line-height.ui-md` | `18px` | use |
| dependency.role-font-line-height-ui-sm | `font.line-height.ui-sm` | `16px` | use |
| dependency.role-font-size-ui-md | `font.size.ui-md` | `13px` | use |
| dependency.role-font-size-ui-sm | `font.size.ui-sm` | `12px` | use |
| dependency.role-icon-size-md | `icon.size.md` | `16px` | use |
| dependency.role-radius-none | `radius.none` | `0px` | use |
| dependency.role-space-12 | `space.12` | `12px` | use |
| dependency.role-space-2 | `space.2` | `2px` | use |
| dependency.role-space-24 | `space.24` | `24px` | use |
| dependency.role-space-4 | `space.4` | `4px` | use |
| dependency.role-space-8 | `space.8` | `8px` | use |

#### Purpose

Select zero or more named values using a labelled native checkbox list. Local search filters visible choices without clearing selections. Successful checkboxes retain repeated native form keys. Clear selection is an explicit action.

#### Anatomy

- **group:** Fieldset and legend name the choice.
- **filter:** Labelled search input filters option labels.
- **options:** Native checkboxes remain selected when filtered out.
- **status:** Visible count distinguishes matching options from selected values.

#### States

| State | Presentation | Non-colour channel |
| --- | --- | --- |
| default | Named content on the default surface. | Native text and structure. |
| checked | Selected fill and a check indicator. | Native checked state. |
| focus-visible | Dashed focus ring. | Native keyboard focus position. |
| disabled | Disabled semantic foreground, background and border at full opacity. | Native disabled state blocks activation. |
| no-results | Muted result count. | Zero-results text; existing selected values remain selected. |

#### Keyboard

- **Tab / Shift+Tab:** Visit search, clear and each visible enabled checkbox.
- **Space:** Toggle the focused checkbox.
- **Escape:** Clear a non-empty query without clearing selection.

#### Accessibility

Keep labels and native checkbox semantics. Hidden choices leave the focus order but retain checked form values. Announce matching and selected counts, including zero. Required selection policy belongs to the host; marking every checkbox required would require every option.

#### Portability

Wrap labels and actions without changing source order. Use logical spacing in RTL. Preserve native target sizes at both densities; only bounded data regions may scroll horizontally. Use equivalent native semantics and approved roles; record any unsupported behavior as a mapping deviation. The host owns application data, persistence, permissions and services.

#### Non-examples

Unlabelled controls, color-only status, drag-only operations, hidden required instructions, stolen focus or an invented service success.

### Repeater (`repeater`)

Repeat native field rows with stable identities and keyboard ordering. Family: forms-advanced. Priority: R2. Maturity: stable. States: default, empty, focus-visible, disabled.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.default` | `#100c0c` | use |
| root.text | `color.text.default` | `#e99499` | use |
| root.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| heading.text | `color.text.bright` | `#ffa2a7` | use |
| secondary.text | `color.text.muted` | `#bd787d` | use |
| control.bg | `color.surface.input` | `#000000` | use |
| control.focus | `color.interaction.focus.ring` | `#e53935` | use |
| dependency.role-border-width-default | `border.width.default` | `1px` | use |
| dependency.role-color-border-disabled | `color.border.disabled` | `#7d1310` | use |
| dependency.role-color-interaction-disabled-bg | `color.interaction.disabled.bg` | `#160b0b` | use |
| dependency.role-color-text-disabled | `color.text.disabled` | `#8a5559` | use |
| dependency.role-density-comfortable-control-height | `density.comfortable.control-height` | `32px` | use |
| dependency.role-density-compact-control-height | `density.compact.control-height` | `24px` | use |
| dependency.role-radius-none | `radius.none` | `0px` | use |
| dependency.role-space-12 | `space.12` | `12px` | use |
| dependency.role-space-2 | `space.2` | `2px` | use |
| dependency.role-space-24 | `space.24` | `24px` | use |
| dependency.role-space-8 | `space.8` | `8px` | use |

#### Purpose

Repeat native fields from trusted inert HTML. Add, remove and reorder preserve row identities. The host supplies fields and handles submission; the component does not execute a schema or provide persistence. A max attribute bounds the number of rows; the supplied example is limited to ten.

#### Anatomy

- **rows:** Ordered list of independently named field rows.
- **template:** Trusted native markup cloned on explicit Add.
- **actions:** Add, remove, move up and move down; no drag-only behavior.

#### States

| State | Presentation | Non-colour channel |
| --- | --- | --- |
| default | Named content on the default surface. | Native text and structure. |
| empty | A muted empty message. | Explicit text and retained Add action. |
| focus-visible | Dashed focus ring. | Native keyboard focus position. |
| disabled | Disabled semantic foreground, background and border at full opacity. | Native disabled state blocks activation. |

#### Keyboard

- **Tab / Shift+Tab:** Visit fields and row actions in source order.
- **Enter / Space:** Add, remove or move a row. Focus follows the affected row or nearest survivor.

#### Accessibility

Namespace IDs and label/ARIA references for each row. Row identities do not change during reordering. Actions have names and disabled boundary states. Removing a focused row restores focus to a surviving row or Add; announce each explicit operation.

#### Portability

Wrap labels and actions without changing source order. Use logical spacing in RTL. Preserve native target sizes at both densities; only bounded data regions may scroll horizontally. Use equivalent native semantics and approved roles; record any unsupported behavior as a mapping deviation. The host owns application data, persistence, permissions and services.

#### Non-examples

Unlabelled controls, color-only status, drag-only operations, hidden required instructions, stolen focus or an invented service success.

## Navigation

Tabs, breadcrumbs, pagination, sidebar, toolbar, menus, command palette, tree.

### Tabs (`tabs`)

A tablist of buttons that switches between panels in place; the selected tab carries a 2px indicator bar and bright text, never a fill. Family: navigation. Priority: R1. Maturity: stable. States: default, hover, focus-visible, selected, selected+focus-visible, selected+container-inactive, disabled.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| list.bg | `color.surface.default` | `#100c0c` | use |
| list.border | `color.border.divider` | `#2b0e0d` | use-and-report (D-008) |
| tab.text | `color.text.default` | `#e99499` | use |
| tab.text-selected | `color.text.bright` | `#ffa2a7` | use |
| tab.text-disabled | `color.text.disabled` | `#8a5559` | use |
| tab.bg-hover | `color.interaction.hover.bg` | `#1c0a09` | use |
| tab.bg-pressed | `color.interaction.pressed.bg` | `#420f0c` | use |
| tab.indicator | `color.border.selected-indicator` | `#e53935` | use |
| tab.indicator-inactive | `color.border.selected-indicator-inactive` | `#9e231f` | use |
| tab.ring | `color.interaction.focus.ring` | `#e53935` | use |
| panel.bg | `color.surface.default` | `#100c0c` | use |
| panel.text | `color.text.default` | `#e99499` | use |

#### Purpose

Switches between peer panels in place without leaving the page. Tabs are
navigation within one view; for moving between views use sidebar-nav, for
mutually exclusive options inside a form use radio-group or
segmented-control.

#### Anatomy

A tablist strip on {color.surface.default} with a 1px {color.border.divider}
rule beneath it; tab buttons in {color.text.default}; the selected tab in
{color.text.bright} with a 2px {color.border.selected-indicator} bottom bar
that sits on the rule; the panel below in {color.text.default}. Tabs carry no
fill and no box; the bar is the only ornament.

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | text {color.text.default}; transparent 2px bottom edge | — |
| hover | background → {color.interaction.hover.bg}; text unchanged | cursor: pointer |
| focus-visible | ring 1px dashed {color.interaction.focus.ring} at −2px | the ring |
| selected | text {color.text.bright}; 2px {color.border.selected-indicator} bar | `aria-selected="true"`; the bar; tabindex 0 |
| selected+focus-visible | the bar and the ring together | both visible |
| selected+container-inactive | bar → {color.border.selected-indicator-inactive}; text stays bright | lightness drop of the bar; the container's own inactive state |
| disabled | text {color.text.disabled}; no hover; bar hidden unless selected | `disabled` or `aria-disabled`; cursor: not-allowed |

Precedence: disabled > selected > hover; focus-visible is always drawn.
Pressing a tab uses {color.interaction.pressed.bg} for the duration of the
press only.

#### Keyboard

One tab stop for the whole strip: the selected tab has `tabindex="0"`, every
other tab `tabindex="-1"` (roving tabindex). Left and Right move focus and
wrap; Home and End jump to the ends; Enter and Space activate when activation
is manual. Automatic activation is the default in this theme because panels
are cheap; use manual activation when a panel is expensive to render. The
panel is a tab stop only when it has no focusable content.

#### Accessibility

The tablist carries `aria-label` or `aria-labelledby`; each tab has
`aria-controls` pointing at its panel and each panel `aria-labelledby`
pointing back. Vertical tablists set `aria-orientation="vertical"` and swap
the arrow keys. Contrast: tab text 8.43:1, selected text 10.10:1, indicator
4.57:1 (≥ 3:1) and ring 4.57:1 on the panel surface. Each tab is at least
24×24 CSS pixels in `compact` density with 4px between neighbours. The
indicator is never the only signal: `aria-selected` and the bright text
travel with it.

#### Portability

A border-bottom that changes colour and an outline are all the drawing needed.
Toolkits that cannot draw a partial bottom border draw a 2px rule across the
full tab width. Hosts that have their own tab strip (browsers, editors) keep
their keyboard model and take only the colours and the 2px bar.

#### Non-examples

Pill or filled tabs. A selected tab drawn with the selection fill. Rounded
corners on the tab or the bar. A tab strip that wraps to two lines. A tab that
changes only its text colour when selected. A glow instead of the dashed ring.
Every tab in the tab order. Disabling by opacity.

### Breadcrumbs (`breadcrumbs`)

The path from the root to the current page as an ordered list of links separated by slashes; the last item is the current page and not a link. Family: navigation. Priority: R1. Maturity: stable. States: default, hover, focus-visible, current, truncated.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.default` | `#100c0c` | use |
| link.text | `color.text.link` | `#f73f35` | use |
| link.underline | `color.text.link-underline` | `#dc282e` | use |
| link.text-hover | `color.text.link-hover` | `#f4eeee` | use |
| link.bg-hover | `color.interaction.hover.bg-strong` | `#630f0d` | use |
| link.ring | `color.interaction.focus.ring` | `#e53935` | use |
| current.text | `color.text.bright` | `#ffa2a7` | use |
| separator.text | `color.text.muted` | `#bd787d` | use |
| ellipsis.text | `color.text.default` | `#e99499` | use |
| ellipsis.bg-hover | `color.interaction.hover.bg` | `#1c0a09` | use |

#### Purpose

Shows where the current page sits in a hierarchy and lets the user jump to
any ancestor. Breadcrumbs are secondary navigation: they never replace the
sidebar or the page title, and they are omitted on the root page.

#### Anatomy

A `nav` labelled "Breadcrumb" wrapping an `ol`. Each ancestor is a link in
{color.text.link} with a 1px {color.text.link-underline}; a `/` separator in
{color.text.muted} follows every item but the last; the last item is the
current page in {color.text.bright} with `aria-current="page"`. When the
trail is deeper than the space allows, the middle levels collapse behind an
ellipsis button that names how many levels it hides.

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | links {color.text.link}, 1px underline; separators {color.text.muted}; current {color.text.bright} | underline on links; none on the current page |
| hover | link background → {color.interaction.hover.bg-strong}; text → {color.text.link-hover} | underline thickens 1px → 2px; cursor |
| focus-visible | ring 1px dashed {color.interaction.focus.ring} at −2px around the link | the ring |
| current | text {color.text.bright}; no underline; no hover | `aria-current="page"`; no underline |
| truncated | the middle levels are replaced by an `…` button in {color.text.default} | `aria-expanded="false"` on the button; its label counts the hidden levels |

#### Keyboard

Every ancestor link is a tab stop in trail order. The ellipsis button is a tab
stop that discloses the hidden levels in place on Enter or Space and sets its
own `aria-expanded`; focus stays on the button so the newly revealed links are
the next Tab presses.

#### Accessibility

`aria-label="Breadcrumb"` on the `nav`, translated. The separator is
`aria-hidden` so nothing voices "slash"; the list structure gives the count.
`aria-current="page"` marks the last item whether or not it is a link. Hidden
levels stay in the DOM under the ellipsis (`hidden` until disclosed) so a
find-in-page still reaches them. Contrast: link text 10.10:1, current
10.10:1, separator 5.66:1, underline 4.57:1 on the panel surface. Each link
is at least 24 CSS pixels tall through its line box and padding.

#### Portability

Plain inline links and text; the only requirement is an inline-start margin
on the separator that flips in RTL. Toolkits without an underline draw the
link in the link colour and record the deviation. The ellipsis disclosure is
optional; hosts without it show the full trail and let it wrap.

#### Non-examples

Chevrons, arrows or bullets as separators. A trail that ends with the site
name instead of the current page. The current page drawn as a link with an
underline. Automatic truncation by `text-overflow` that hides levels without
a way to reach them. A trail that scrolls horizontally. Uppercase or
letter-spaced items. Separators that are read aloud.

### Pagination (`pagination`)

Page links for a long result set; the current page takes the selection fill and aria-current, and the compact form reads "3 of 12" between previous and next. Family: navigation. Priority: R1. Maturity: stable. States: default, hover, focus-visible, current, disabled.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.default` | `#100c0c` | use |
| link.text | `color.text.default` | `#e99499` | use |
| link.bg-hover | `color.interaction.hover.bg` | `#1c0a09` | use |
| link.bg-pressed | `color.interaction.pressed.bg` | `#420f0c` | use |
| link.ring | `color.interaction.focus.ring` | `#e53935` | use |
| link.text-disabled | `color.text.disabled` | `#8a5559` | use |
| current.bg | `color.interaction.selection.bg` | `#531310` | use |
| current.text | `color.text.on-selection` | `#f4eeee` | use |
| current.ring | `color.interaction.focus.ring-container` | `#ffa2a7` | use |
| gap.text | `color.text.muted` | `#bd787d` | use |
| status.text | `color.text.default` | `#e99499` | use |
| status.emphasis | `color.text.bright` | `#ffa2a7` | use |

#### Purpose

Moves through a result set that is split into pages. Pagination is for sets
whose size is known; use a "load more" button or infinite scroll for feeds,
and a range field for jumping to an arbitrary offset.

#### Anatomy

A `nav` labelled "Pagination" holding a `ul`: previous, the page links with
an aria-hidden `…` gap where pages are skipped, next. Every page link is a
square target in {color.text.default} with no border; the current page is
filled with {color.interaction.selection.bg} and {color.text.on-selection}.
The compact variant keeps previous and next and replaces the numbers with
"3 of 12", the current number in {color.text.bright}.

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | text {color.text.default}; no fill; no border | — |
| hover | background → {color.interaction.hover.bg} | cursor: pointer |
| focus-visible | ring 1px dashed {color.interaction.focus.ring} at −2px; on the current page the ring is {color.interaction.focus.ring-container} | the ring |
| current | fill {color.interaction.selection.bg}; text {color.text.on-selection}; no hover | `aria-current="page"` |
| disabled | text {color.text.disabled}; no fill; no hover | `aria-disabled="true"`; no `href`; cursor: not-allowed |

Pressing a link uses {color.interaction.pressed.bg} for the duration of the
press only.

#### Keyboard

Plain links in document order: previous, the pages, next. A disabled end has
no `href`, so it is not a tab stop and cannot be activated. The current page
remains a link to itself so a keyboard user can reload it.

#### Accessibility

`aria-label="Pagination"` on the `nav`; a second pagination on the same page
gets a distinct label. Previous and next carry a visible word, not just a
glyph, and `aria-label` gives the target page number when it is known
("Previous page, 2"). The gap is `aria-hidden`; the list count tells a screen
reader how many links follow. Contrast: link text 8.43:1, current text 12.47:1
on the selection fill, ring-container 7.48:1 on the fill, disabled 3.24:1
(exempt). Targets are at least 24×24 CSS pixels in `compact` density.

#### Portability

Links with a background and an outline; nothing else. Hosts that cannot
remove `href` from a disabled link use a disabled button for the ends and
record it. The compact variant is plain text and two links and ports
anywhere.

#### Non-examples

Rounded or circular page buttons. A border around every page link. The
current page marked by colour or bold alone without `aria-current`. Previous
and next as glyph-only links without a label. A disabled end that still has
an `href`. Pagination that hides the last page number. A row that scrolls
horizontally.

### Sidebar navigation (`sidebar-nav`)

A vertical list of section links with optional nested groups; the current section carries a 2px inline-start indicator and bright text, and the rail collapses to icons. Family: navigation. Priority: R1. Maturity: stable. States: default, hover, focus-visible, current, selected, selected+container-inactive, expanded, collapsed.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.default` | `#100c0c` | use |
| root.border | `color.border.divider` | `#2b0e0d` | use-and-report (D-008) |
| link.text | `color.text.default` | `#e99499` | use |
| link.bg-hover | `color.interaction.hover.bg` | `#1c0a09` | use |
| link.bg-pressed | `color.interaction.pressed.bg` | `#420f0c` | use |
| link.ring | `color.interaction.focus.ring` | `#e53935` | use |
| link.indicator | `color.border.selected-indicator` | `#e53935` | use |
| link.indicator-inactive | `color.border.selected-indicator-inactive` | `#9e231f` | use |
| current.text | `color.text.bright` | `#ffa2a7` | use |
| selected.bg | `color.interaction.selection.bg` | `#531310` | use |
| selected.text | `color.interaction.selection.text` | `#f4eeee` | use |
| selected.ring | `color.interaction.focus.ring-container` | `#ffa2a7` | use |
| selected.inactive-bg | `color.interaction.selection.inactive-bg` | `#420f0c` | use |
| selected.inactive-text | `color.interaction.selection.inactive-text` | `#e99499` | use |
| icon.stroke | `color.icon.default` | `#e99499` | use |
| group.chevron | `color.icon.decorative` | `#a3676b` | use |

#### Purpose

Moves between the sections of an application. The sidebar is the primary
navigation of a settings panel, an admin area or a documentation site; tabs
switch views inside one section, and a tree browses data rather than
sections.

#### Anatomy

A `nav` on {color.surface.default} with a 1px {color.border.divider}
inline-end edge. Each link is one row: an icon slot, a label in
{color.text.default}, and a transparent 2px inline-start edge. The current
link colours that edge with {color.border.selected-indicator} and its text
with {color.text.bright}. Groups are buttons with a chevron in
{color.icon.decorative}; their children sit in a sublist indented by one icon
width. Collapsed, the rail keeps the icons and hides the labels visually.

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | text {color.text.default}; no fill; transparent inline-start edge | — |
| hover | background → {color.interaction.hover.bg} | cursor: pointer |
| focus-visible | ring 1px dashed {color.interaction.focus.ring} at −2px | the ring |
| current | 2px {color.border.selected-indicator} inline-start bar; text {color.text.bright} | `aria-current="page"`; the bar |
| selected | fill {color.interaction.selection.bg} with {color.interaction.selection.text}; the ring in {color.interaction.focus.ring-container} when focused | `aria-selected` on a listbox-style rail; the fill's on-fill text |
| selected+container-inactive | fill {color.interaction.selection.inactive-bg} with {color.interaction.selection.inactive-text}; no ring | lightness drop between the two fills |
| expanded | the group's chevron rotated 90°; its sublist shown | `aria-expanded="true"`; the children are present |
| collapsed | labels visually hidden; icons centred; the current bar and fills unchanged | the labels remain for assistive technology and as tooltips |

`current` is where the user is; `selected` is what the user chose in a rail
that drives a detail pane. A rail is one or the other, never both at once.

#### Keyboard

Every link and every group button is a tab stop in document order; children
of a closed group are hidden and skipped. Enter follows a link; Enter or Space
toggles a group and leaves focus on the button. There is no arrow-key model:
the rail is a list of links, not a tree, so the native tab order is the whole
contract.

#### Accessibility

`aria-label` on the `nav` distinguishes it from other landmarks. The current
link carries `aria-current="page"`; a rail that drives a detail pane uses a
listbox with `aria-selected` instead of links. Group buttons carry
`aria-expanded` and `aria-controls`; the sublist is `hidden` until expanded
so its links leave the tab order. Collapsed labels use a clip-rect hiding, not
`display: none`, so names survive; icon-only links also get a `title`.
Contrast: text 8.43:1, current text 10.10:1, indicator 4.57:1, selection text
12.47:1 on the fill, inactive selection 7.02:1. Rows are at least 24 CSS pixels
tall in `compact` density.

#### Portability

A border-inline-start, a background and an outline; the chevron is an SVG.
Toolkits without logical borders draw the bar on the left in LTR and on the
right in RTL explicitly. The collapsed rail is a width change and a hidden
label; hosts that cannot hide labels while keeping their names show the full
rail.

#### Non-examples

A filled or rounded current item. The indicator on the inline-end edge. A
current item marked by bold text alone. Labels removed from the DOM when
collapsed. A group whose children stay in the tab order while hidden. A rail
that scrolls horizontally. Icon-only links with no accessible name. Hover that
changes the text colour.

### Toolbar (`toolbar`)

A row of related controls under one tab stop with arrow-key movement; ghost buttons, a vertical separator, and a "more" button when the row overflows. Family: navigation. Priority: R1. Maturity: stable. States: default, hover, focus-visible, disabled, toggled.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.chrome` | `#090707` | use |
| root.border | `color.border.divider` | `#2b0e0d` | use-and-report (D-008) |
| button.text | `color.text.default` | `#e99499` | use |
| button.bg-hover | `color.interaction.hover.bg` | `#1c0a09` | use |
| button.bg-pressed | `color.interaction.pressed.bg` | `#420f0c` | use |
| button.ring | `color.interaction.focus.ring` | `#e53935` | use |
| button.text-disabled | `color.text.disabled` | `#8a5559` | use |
| toggled.bg | `color.interaction.pressed.bg` | `#420f0c` | use |
| toggled.border | `color.border.active` | `#e53935` | use |
| toggled.text | `color.text.bright` | `#ffa2a7` | use |
| separator.color | `color.border.default` | `#531310` | use |
| more.chevron | `color.icon.decorative` | `#a3676b` | use |

#### Purpose

Groups the frequent actions of a view into one row that costs one tab stop.
A toolbar is for actions on the content beneath it; navigation between views
belongs in tabs or the sidebar, and a set of mutually exclusive choices in a
segmented control or radio group.

#### Anatomy

A `div` with `role="toolbar"` on {color.surface.chrome} with a 1px
{color.border.divider} bottom edge. Inside, ghost buttons in
{color.text.default} with a transparent 1px border, vertical separators in
{color.border.default} between groups, and in the overflow variant a trailing
"More" button that opens a menu with the controls that do not fit. Toggle
buttons carry `aria-pressed`.

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | text {color.text.default}; no fill; transparent border | — |
| hover | background → {color.interaction.hover.bg} | cursor: pointer |
| focus-visible | ring 1px dashed {color.interaction.focus.ring} at −2px | the ring |
| disabled | text {color.text.disabled}; no fill; no hover | `disabled`; cursor: not-allowed; skipped by the arrow keys |
| toggled | fill {color.interaction.pressed.bg}; 1px {color.border.active} border; text {color.text.bright} | `aria-pressed="true"`; the border |

Pressing a button uses {color.interaction.pressed.bg} for the duration of
the press only; a toggled button keeps it.

#### Keyboard

The toolbar is one tab stop: the last focused control (initially the first
enabled one) has `tabindex="0"`, every other control `tabindex="-1"`. Left
and Right move between controls and wrap; Home and End jump to the ends;
Enter and Space activate. Disabled controls are skipped. Vertical toolbars
declare `aria-orientation="vertical"` and use Up and Down. Without script
every button is a tab stop, which is acceptable, not ideal.

#### Accessibility

`aria-label` names the toolbar; a page with several toolbars gives each a
distinct name. Toggle buttons expose `aria-pressed`; their label does not
change with the state. Separators are `role="separator"` with
`aria-orientation="vertical"` and are never focusable. Icon-only buttons
carry `aria-label` and a tooltip. Contrast: text 8.60:1 on the chrome
surface, toggled text 8.41:1 on the pressed fill, the toggled border 3.81:1,
ring 4.66:1. Every control is at least 24×24 CSS pixels with 4px between
neighbours in `compact` density.

#### Portability

Ghost buttons with a background and a border; the separator is a 1px rule.
Toolkits with a native toolbar keep their own keyboard model and take the
colours. The overflow menu is a menu component; hosts without one wrap the
row instead and record the deviation.

#### Non-examples

Buttons with a visible border at rest. A filled primary button inside the
toolbar. Rounded buttons or a pill-shaped toolbar. Every button in the tab
order when script is available. A toggled state shown by colour alone without
`aria-pressed`. A toolbar that wraps to two lines. A separator that receives
focus. Disabling by opacity.

### Menu (`menu`)

A menu button that opens a list of actions on the raised surface inside a 1px overlay border; the active item is the selection fill and check items show a glyph. Family: navigation. Priority: R1. Maturity: stable. States: default, hover, focus-visible, open, closed, disabled, checked, selected.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| button.text | `color.text.default` | `#e99499` | use |
| button.bg | `color.surface.default` | `#100c0c` | use |
| button.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| button.bg-hover | `color.interaction.hover.bg` | `#1c0a09` | use |
| button.bg-open | `color.interaction.pressed.bg` | `#420f0c` | use |
| button.border-open | `color.border.active` | `#e53935` | use |
| button.ring | `color.interaction.focus.ring` | `#e53935` | use |
| surface.bg | `color.surface.raised` | `#160b0b` | use |
| surface.border | `color.border.overlay` | `#e53935` | use-and-report (D-008) |
| item.text | `color.text.default` | `#e99499` | use |
| item.bg-hover | `color.interaction.hover.bg-strong` | `#630f0d` | use |
| item.text-hover | `color.text.link-hover` | `#f4eeee` | use |
| item.text-disabled | `color.text.disabled` | `#8a5559` | use |
| active.bg | `color.interaction.selection.bg` | `#531310` | use |
| active.text | `color.interaction.selection.text` | `#f4eeee` | use |
| active.ring | `color.interaction.focus.ring-container` | `#ffa2a7` | use |
| check.glyph | `color.text.bright` | `#ffa2a7` | use |
| shortcut.text | `color.text.muted` | `#bd787d` | use |
| separator.color | `color.border.divider` | `#2b0e0d` | use-and-report (D-008) |
| chevron.color | `color.icon.decorative` | `#a3676b` | use |

#### Purpose

Offers a short list of actions behind one button. A menu is for commands;
choosing a value belongs in select or combobox, and moving between views in
tabs or the sidebar. Menus close as soon as an action runs.

#### Anatomy

A menu button, styled as a secondary button, that opens a list on
{color.surface.raised} inside a 1px {color.border.overlay} frame. Items are
buttons in {color.text.default} with a check gutter at the inline start, the
label, and an optional shortcut hint in {color.text.muted}. Separators are
1px {color.border.divider} rules. The active item, whether reached by pointer
or by arrow key, is the selection fill {color.interaction.selection.bg} with
{color.interaction.selection.text}. The reference renders the menu open in
flow beneath the button; a host positions it at `z.popover`.

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | the button at rest; the list on {color.surface.raised} with the {color.border.overlay} frame | — |
| hover | item background → {color.interaction.hover.bg-strong}; text → {color.text.link-hover} | cursor: pointer |
| focus-visible | ring 1px dashed {color.interaction.focus.ring} at −2px on the button; on the active item the ring is {color.interaction.focus.ring-container} | the ring |
| open | button fill {color.interaction.pressed.bg} with 1px {color.border.active}; the list shown | `aria-expanded="true"`; the list is present |
| closed | the button at rest; the list hidden | `aria-expanded="false"`; the list is absent from the tree |
| disabled | item text {color.text.disabled}; no hover; still focusable by arrow keys when `aria-disabled` | `aria-disabled="true"`; cursor: not-allowed |
| checked | the ✓ glyph in {color.text.bright} in the gutter | `aria-checked="true"`; the glyph |
| selected | the active item: fill {color.interaction.selection.bg} with {color.interaction.selection.text} | focus is on the item; `aria-activedescendant` where used |

Precedence: disabled > selected > hover; focus-visible is always drawn.

#### Keyboard

The button opens on Enter, Space or Down (focus on the first item) and on Up
(focus on the last). Inside, Up and Down move and wrap, Home and End jump,
Right opens a submenu and Left closes it, typing a letter jumps to the next
matching item. Enter or Space activates; a check item toggles without closing.
Escape closes and returns focus to the button; Tab closes and continues the
tab order. Focus never rests on a separator.

#### Accessibility

The button carries `aria-haspopup="menu"`, `aria-expanded` and
`aria-controls`; the list `role="menu"` and `aria-labelledby` pointing at the
button. Items are `role="menuitem"` or `role="menuitemcheckbox"` with
`aria-checked`; disabled items use `aria-disabled` so they remain reachable
and announced. Shortcut hints are text, not `aria-keyshortcuts` alone.
Contrast: item text 8.43:1 and shortcut 5.66:1 on the raised surface, active
text 12.47:1 on the fill, the frame 4.57:1, ring-container 7.48:1 on the fill.
Items are the row height, at least 24 CSS pixels in `compact` density.

#### Portability

A framed box with rows; the active row is a fill, the frame a border, the
ring an outline. Toolkits with native menus keep their keyboard model and
take the surface, frame and fill colours. Hosts that cannot draw the frame
in the overlay colour draw it in the strong border colour and record the
deviation.

#### Non-examples

A shadow under the menu. Rounded corners on the menu or the items. An active
item marked by text colour alone. A hover fill that differs from every other
menu in the product. Icons in the check gutter of a plain menuitem. A menu
that stays open after an action. Items that are links styled as menu items
without `role="menuitem"`. Disabling by opacity.

### Skip link (`skip-link`)

The first tab stop of a page, hidden until it receives keyboard focus, that jumps past the chrome to the main content. Family: navigation. Priority: R1. Maturity: stable. States: default, focus-visible.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.raised` | `#160b0b` | use |
| root.text | `color.text.bright` | `#ffa2a7` | use |
| root.border | `color.border.overlay` | `#e53935` | use-and-report (D-008) |
| root.ring | `color.interaction.focus.ring` | `#e53935` | use |

#### Purpose

Lets a keyboard user bypass the header, navigation and toolbars in one
press. It satisfies WCAG 2.4.1 for pages that have repeated blocks before
the content; a page with nothing before `main` does not need one.

#### Anatomy

One anchor, first in the body, with `href` pointing at the `main` landmark.
It is a small raised card: {color.surface.raised}, 1px {color.border.overlay},
text {color.text.bright}, at the top inline-start corner at `z.skip-link`.
Off-screen at rest (a negative top), on-screen while focused.

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | off-screen; nothing is painted | the link is still in the tab order and the accessibility tree |
| focus-visible | appears in the top corner; ring 1px dashed {color.interaction.focus.ring} at −2px | the link appears; the ring |

There is no hover state: a pointer cannot reach it while it is hidden, and
once visible it behaves as a link.

#### Keyboard

Tab from the top of the document reaches it first. Enter moves focus to
`main`, which carries `tabindex="-1"` so the browser's focus actually lands
there and the next Tab continues inside the content. Tab away hides it again.

#### Accessibility

The link text names the destination ("Skip to content"), translated. Hiding
uses position only, so the link remains in the accessibility tree; `display:
none`, `visibility: hidden` and `aria-hidden` all defeat it. The reveal is an
instant position change: nothing slides, so reduced motion needs no special
case. Contrast: text 10.10:1 on the raised surface, frame 4.57:1,
ring 4.57:1. The visible target is at least 24 CSS pixels tall.

#### Portability

Web-only. Frameworks that render their own header keep this exact markup
first in the body; a host stylesheet that removes outlines must restore the
ring here. The reference site uses this component for its own skip link.

#### Non-examples

A skip link that is always visible. One hidden with `display: none` or
`visibility: hidden`. One that is not the first tab stop. One that points at
an element without `tabindex="-1"`, so focus does not move. A skip link
that slides in from the side and causes horizontal scroll while hidden. A
rounded pill. One rendered without the frame on the canvas.

### Command palette (`command-palette`)

A compact square modal command prompt with local results and category labels. Family: navigation. Priority: R2. Maturity: stable. States: default, open, closed, focus-visible, no-results.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.default` | `#100c0c` | use |
| root.text | `color.text.default` | `#e99499` | use |
| root.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| heading.text | `color.text.bright` | `#ffa2a7` | use |
| secondary.text | `color.text.muted` | `#bd787d` | use |
| control.bg | `color.surface.input` | `#000000` | use |
| control.focus | `color.interaction.focus.ring` | `#e53935` | use |
| dependency.role-border-width-default | `border.width.default` | `1px` | use |
| dependency.role-border-width-emphasis | `border.width.emphasis` | `2px` | use |
| dependency.role-color-border-overlay | `color.border.overlay` | `#e53935` | use-and-report (D-008) |
| dependency.role-color-border-selected-indicator | `color.border.selected-indicator` | `#e53935` | use |
| dependency.role-color-interaction-selection-bg | `color.interaction.selection.bg` | `#531310` | use |
| dependency.role-color-interaction-selection-text | `color.interaction.selection.text` | `#f4eeee` | use |
| dependency.role-color-surface-backdrop | `color.surface.backdrop` | `rgb(0 0 0 / 65%)` | use-and-report (D-008) |
| dependency.role-color-surface-overlay | `color.surface.overlay` | `#241010` | use |
| dependency.role-density-comfortable-control-height | `density.comfortable.control-height` | `32px` | use |
| dependency.role-density-compact-control-height | `density.compact.control-height` | `24px` | use |
| dependency.role-radius-none | `radius.none` | `0px` | use |
| dependency.role-space-12 | `space.12` | `12px` | use |
| dependency.role-space-16 | `space.16` | `16px` | use |
| dependency.role-space-2 | `space.2` | `2px` | use |
| dependency.role-space-8 | `space.8` | `8px` | use |

#### Purpose

Find and invoke local commands from a compact square floating utility panel. One prompt filters dense rows with category labels. Results are caller-supplied links or bounded actions; no remote search occurs. The host may opt into Ctrl+K or Cmd+K.

#### Anatomy

- **trigger:** Named native button opens the utility panel.
- **prompt:** Labelled combobox input retains focus while navigating results.
- **results:** Listbox results include category and command text.
- **dialog:** Native modal dialog provides containment and return.

#### States

| State | Presentation | Non-colour channel |
| --- | --- | --- |
| default | One prompt and dense command rows. | Visible names and categories. |
| open | Square overlay surface and border. | Native modal dialog and active-descendant relationship. |
| closed | Panel absent. | Opener remains focusable. |
| focus-visible | Dashed input ring. | Input retains DOM focus while results change. |
| no-results | Muted zero-results status. | Explicit result count and no active descendant. |

#### Keyboard

- **Ctrl+K / Cmd+K:** Open when the host explicitly enables the shortcut.
- **Up / Down:** Move the active result while retaining input focus.
- **Enter:** Invoke the active enabled result and close.
- **Escape:** Close and restore the opener.
- **Tab / Shift+Tab:** Cycle through native modal controls.

#### Accessibility

Combine native modal focus behavior with the editable combobox pattern. Result names include their category and command text; selection uses the approved selected roles. Escape closes once and restores focus. Required site navigation remains available outside the palette without JavaScript. Do not globally intercept shortcuts unless the host opts in.

#### Portability

Wrap labels and actions without changing source order. Use logical spacing in RTL. Preserve native target sizes at both densities; only bounded data regions may scroll horizontally. Use equivalent native semantics and approved roles; record any unsupported behavior as a mapping deviation. The host owns application data, persistence, permissions and services.

#### Non-examples

Unlabelled controls, color-only status, drag-only operations, hidden required instructions, stolen focus or an invented service success.

### Tree (`tree`)

Hierarchical navigation with explicit expansion and one roving tab stop. Family: navigation. Priority: R2. Maturity: stable. States: default, selected, expanded, collapsed, focus-visible, disabled.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.default` | `#100c0c` | use |
| root.text | `color.text.default` | `#e99499` | use |
| root.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| heading.text | `color.text.bright` | `#ffa2a7` | use |
| secondary.text | `color.text.muted` | `#bd787d` | use |
| control.bg | `color.surface.input` | `#000000` | use |
| control.focus | `color.interaction.focus.ring` | `#e53935` | use |
| dependency.role-border-width-default | `border.width.default` | `1px` | use |
| dependency.role-border-width-emphasis | `border.width.emphasis` | `2px` | use |
| dependency.role-color-border-selected-indicator | `color.border.selected-indicator` | `#e53935` | use |
| dependency.role-color-interaction-selection-bg | `color.interaction.selection.bg` | `#531310` | use |
| dependency.role-color-interaction-selection-text | `color.interaction.selection.text` | `#f4eeee` | use |
| dependency.role-color-text-disabled | `color.text.disabled` | `#8a5559` | use |
| dependency.role-density-comfortable-control-height | `density.comfortable.control-height` | `32px` | use |
| dependency.role-density-compact-control-height | `density.compact.control-height` | `24px` | use |
| dependency.role-radius-none | `radius.none` | `0px` | use |
| dependency.role-space-12 | `space.12` | `12px` | use |
| dependency.role-space-16 | `space.16` | `16px` | use |
| dependency.role-space-2 | `space.2` | `2px` | use |
| dependency.role-space-8 | `space.8` | `8px` | use |

#### Purpose

Navigate hierarchical items with explicit expansion and a single roving tab stop. Selection differs from expansion. Caller-supplied nodes represent identities; activation emits an event and never invents file access.

#### Anatomy

- **tree:** Labelled tree with nested groups.
- **item:** Named treeitems expose selection; parents also expose expansion.
- **children:** A nested group hidden when the parent collapses.

#### States

| State | Presentation | Non-colour channel |
| --- | --- | --- |
| default | Visible named tree items. | Tree and group hierarchy. |
| selected | Selected fill, text and start indicator. | aria-selected. |
| expanded | Downward marker and visible children. | aria-expanded=true. |
| collapsed | Side marker and hidden children. | aria-expanded=false; children leave focus order. |
| focus-visible | Dashed ring on the item's own label. | Roving native focus. |
| disabled | Disabled text at full opacity. | aria-disabled blocks selection. |

#### Keyboard

- **Up / Down / Home / End:** Move through visible items in source order.
- **Right / Left:** Expand or enter a parent, collapse it or return to its parent. Mirror these keys in RTL.
- **Enter / Space:** Select the focused enabled item.
- **Typing:** Move to the next visible label starting with the character.

#### Accessibility

Implement the linked tree pattern. Only one visible item has tabindex=0. Labels exclude descendant text. Collapsing a parent of the focused item moves focus to that parent. Selection has a programmatic state and border indicator; disabled items do not activate.

#### Portability

Wrap labels and actions without changing source order. Use logical spacing in RTL. Preserve native target sizes at both densities; only bounded data regions may scroll horizontally. Use equivalent native semantics and approved roles; record any unsupported behavior as a mapping deviation. The host owns application data, persistence, permissions and services.

#### Non-examples

Unlabelled controls, color-only status, drag-only operations, hidden required instructions, stolen focus or an invented service success.

### Disclosure (`disclosure`)

Native details and summary for supporting content. Family: navigation. Priority: R2. Maturity: stable. States: default, expanded, collapsed, focus-visible.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.default` | `#100c0c` | use |
| root.text | `color.text.default` | `#e99499` | use |
| root.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| heading.text | `color.text.bright` | `#ffa2a7` | use |
| secondary.text | `color.text.muted` | `#bd787d` | use |
| control.bg | `color.surface.input` | `#000000` | use |
| control.focus | `color.interaction.focus.ring` | `#e53935` | use |
| dependency.role-border-width-default | `border.width.default` | `1px` | use |
| dependency.role-border-width-emphasis | `border.width.emphasis` | `2px` | use |
| dependency.role-color-border-active | `color.border.active` | `#e53935` | use |
| dependency.role-color-border-disabled | `color.border.disabled` | `#7d1310` | use |
| dependency.role-color-interaction-disabled-bg | `color.interaction.disabled.bg` | `#160b0b` | use |
| dependency.role-color-text-disabled | `color.text.disabled` | `#8a5559` | use |
| dependency.role-density-comfortable-control-height | `density.comfortable.control-height` | `32px` | use |
| dependency.role-density-compact-control-height | `density.compact.control-height` | `24px` | use |
| dependency.role-radius-none | `radius.none` | `0px` | use |
| dependency.role-space-12 | `space.12` | `12px` | use |
| dependency.role-space-2 | `space.2` | `2px` | use |
| dependency.role-space-8 | `space.8` | `8px` | use |

#### Purpose

Reveal supporting content with native details and summary. Essential instructions stay outside a collapsed disclosure. The summary supplies a complete accessible name; content remains in normal reading order.

#### Anatomy

- **details:** Native expanded state and content container.
- **summary:** Visible name and native disclosure marker.

#### States

| State | Presentation | Non-colour channel |
| --- | --- | --- |
| default | Named content on the default surface. | Native text and structure. |
| expanded | Expanded content and native marker. | Native open state exposes the content. |
| collapsed | Only the summary remains visible. | Native closed content leaves the focus order. |
| focus-visible | Dashed focus ring. | Native keyboard focus position. |

#### Keyboard

- **Tab / Shift+Tab:** Reach the summary and expanded interactive content.
- **Enter / Space:** Toggle native details.

#### Accessibility

Keep the native marker and summary semantics. Do not add a competing aria-expanded value. Required instructions and the portal's normative content are never placed only inside a disclosure.

#### Portability

Wrap labels and actions without changing source order. Use logical spacing in RTL. Preserve native target sizes at both densities; only bounded data regions may scroll horizontally. Use equivalent native semantics and approved roles; record any unsupported behavior as a mapping deviation. The host owns application data, persistence, permissions and services.

#### Non-examples

Unlabelled controls, color-only status, drag-only operations, hidden required instructions, stolen focus or an invented service success.

## Information display

Cards, lists, badges, chips, avatars, descriptions, tables, timelines, simple charts.

### Card (`card`)

A bordered panel that groups a title, body and metadata; a link card makes the whole panel one link, a selectable card is an option with the selection fill. Family: display. Priority: R1. Maturity: stable. States: default, hover, focus-visible, selected, selected+focus-visible.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.default` | `#100c0c` | use |
| root.border | `color.border.default` | `#531310` | use |
| root.bg-hover | `color.interaction.hover.bg` | `#1c0a09` | use |
| root.border-hover | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| root.ring | `color.interaction.focus.ring-container` | `#ffa2a7` | use |
| title.text | `color.text.bright` | `#ffa2a7` | use |
| body.text | `color.text.default` | `#e99499` | use |
| meta.text | `color.text.muted` | `#bd787d` | use |
| selected.bg | `color.interaction.selection.bg` | `#531310` | use |
| selected.text | `color.interaction.selection.text` | `#f4eeee` | use |
| selected.ring | `color.interaction.focus.ring-container` | `#ffa2a7` | use |

#### Purpose

Groups a small unit of content — a title, a line or two, a metadata line —
so it reads as one thing. A card is static by default; it becomes a link
when the whole unit leads somewhere, and an option when the user picks among
several units. A card is not a dialog, not a form and not a table row.

#### Anatomy

A 1px {color.border.default} edge on {color.surface.default}, no shadow, no
radius. Inside, a title in {color.text.bright}, body text in
{color.text.default}, metadata in {color.text.muted}. Padding is 12px in
`comfortable` and 8px in `compact` density. A link card is one anchor around
the same content; a selectable card is an option inside a listbox group.

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | 1px {color.border.default} on {color.surface.default} | — |
| hover | link and selectable cards only: background → {color.interaction.hover.bg}; border → {color.border.control} | cursor: pointer; the border lightens from decorative to control |
| focus-visible | container ring 2px solid {color.interaction.focus.ring-container} at −3px | the ring |
| selected | fill {color.interaction.selection.bg} with {color.interaction.selection.text} on every line | `aria-selected="true"`; the fill's on-fill text |
| selected+focus-visible | the fill and the 2px ring in {color.interaction.focus.ring-container} | both visible at once |

A static card has no hover and no focus: nothing happens on a pointer and it
is not in the tab order.

#### Keyboard

A link card is one tab stop and Enter follows it; controls inside a link
card are not allowed (a link cannot contain a button). A selectable group is
a listbox with roving tabindex: Tab lands on the selected card, the arrow
keys move and select, Space toggles in a multi-select group. A static card
that contains controls contributes those controls to the tab order and
nothing else.

#### Accessibility

A static card is an `article` with a heading when it stands alone, or a
`section` inside a list. A link card names itself from its content and the
title comes first so it is read first. Selectable cards are `role="option"`
inside `role="listbox"` with `aria-label`; `aria-selected` on each option.
Contrast: title 10.10:1, body 8.43:1, metadata 5.66:1 on the card; on the
selection fill every line switches to the on-fill text (12.47:1) because muted
text would fall to 4.19:1. The ring is 10.10:1 on the card and 7.48:1 on the
fill.

#### Portability

A bordered box with a background and an outline. Toolkits that add a shadow
to their frames remove it. Hosts without container outlines draw the ring
as a 2px inner border and record it.

#### Non-examples

A drop shadow or elevation. Rounded corners. A hover lift or scale. A static
card with a hover fill. A card whose only clickable part is an invisible
overlay. A link card containing buttons. A selected card shown by a border
colour alone without the fill and `aria-selected`. Muted metadata left on the
selection fill.

### List (`list`)

A selectable list of rows (an APG listbox) in single or multiple selection; the selected row is the selection fill with a check glyph and an optional metadata column. Family: display. Priority: R1. Maturity: stable. States: default, hover, focus-visible, selected, selected+focus-visible, selected+container-inactive, disabled.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.default` | `#100c0c` | use |
| root.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| row.text | `color.text.default` | `#e99499` | use |
| row.bg-hover | `color.interaction.hover.bg` | `#1c0a09` | use |
| row.divider | `color.border.divider` | `#2b0e0d` | use-and-report (D-008) |
| row.ring | `color.interaction.focus.ring` | `#e53935` | use |
| row.text-disabled | `color.text.disabled` | `#8a5559` | use |
| selected.bg | `color.interaction.selection.bg` | `#531310` | use |
| selected.text | `color.interaction.selection.text` | `#f4eeee` | use |
| selected.ring | `color.interaction.focus.ring-container` | `#ffa2a7` | use |
| selected.inactive-bg | `color.interaction.selection.inactive-bg` | `#420f0c` | use |
| selected.inactive-text | `color.interaction.selection.inactive-text` | `#e99499` | use |
| check.glyph | `color.interaction.selection.text` | `#f4eeee` | use |
| meta.text | `color.text.muted` | `#bd787d` | use |

#### Purpose

Lets the user choose one or several rows from a short, visible set. A list
is a listbox: it holds a selection, it does not navigate. Rows that lead
somewhere are sidebar-nav; rows with columns are a table; a long set that
needs filtering is a combobox.

#### Anatomy

A `ul` with `role="listbox"` framed by 1px {color.border.control} on
{color.surface.default}. Each option is one row: a check gutter, the label in
{color.text.default}, and in the metadata variant a trailing column in
{color.text.muted}. Rows are separated by 1px {color.border.divider}. A
selected row is filled with {color.interaction.selection.bg}; every part of
it, including the metadata and the ✓ glyph, takes
{color.interaction.selection.text}.

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | text {color.text.default}; no fill; the check gutter empty | — |
| hover | background → {color.interaction.hover.bg} | cursor: pointer |
| focus-visible | ring 1px dashed {color.interaction.focus.ring} at −2px on the option | the ring |
| selected | fill {color.interaction.selection.bg}; text and glyph {color.interaction.selection.text} | `aria-selected="true"`; the ✓ glyph |
| selected+focus-visible | the fill and the ring in {color.interaction.focus.ring-container} | both visible at once |
| selected+container-inactive | fill {color.interaction.selection.inactive-bg} with {color.interaction.selection.inactive-text}; no ring | lightness drop between the two fills; the glyph stays |
| disabled | text {color.text.disabled}; no fill; no hover; still reachable by arrow keys | `aria-disabled="true"`; cursor: not-allowed |

Precedence: disabled > selected > hover; focus-visible is always drawn.

#### Keyboard

Roving tabindex: the selected option (or the first) has `tabindex="0"`, the
rest `-1`, so the list costs one tab stop. Up and Down move; in single
selection the selection follows focus, in multiple selection Space toggles
and Shift with the arrows extends. Home and End jump; Ctrl+A selects all in a
multi-select list; typing jumps to the next matching label. Disabled options
are reachable and announced, never skipped silently.

#### Accessibility

`aria-label` or `aria-labelledby` on the listbox; `aria-multiselectable`
when several may be chosen; `aria-selected` on every option, including
`"false"`. The ✓ glyph is `aria-hidden`; `aria-selected` is the state.
Metadata is inside the option so it is read with the label. Contrast: text
8.43:1, metadata 5.66:1, selected text 12.47:1 on the fill, inactive selection
7.02:1, ring 4.57:1 on the surface and 7.48:1 on the fill, frame 4.33:1. Rows
are at least 24 CSS pixels tall with the check gutter as the leading target
area.

#### Portability

Rows with a background, a glyph gutter and an outline; nothing else. Hosts
with a native list view take the fill, the inactive fill and the divider
colour and keep their keyboard model. The inactive fill applies when the
list's window or pane loses focus, which a web page detects through
`:focus-within` on the pane.

#### Non-examples

A selected row marked by a border or a check alone without the fill. Rounded
row highlights. Muted metadata left on the selection fill. A list where every
row is a tab stop. A list that navigates on selection. Disabled rows removed
from the arrow-key order. Hover that changes the text colour. A list without
a frame on the canvas.

### Badge (`badge`)

A small square label for a status or a count; every status carries its glyph and its fill, the outline form uses the status border, and an empty badge is a dot with a name. Family: display. Priority: R1. Maturity: stable. States: default, empty.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| danger.bg | `color.status.danger.fill` | `#dc282e` | use |
| danger.text | `color.status.danger.on-fill` | `#f9faf9` | use |
| danger.border | `color.status.danger.border` | `#e53935` | use |
| danger.outline-text | `color.status.danger.text` | `#f73f35` | use |
| warning.bg | `color.status.warning.fill` | `#c9973f` | use |
| warning.text | `color.status.warning.on-fill` | `#0c0909` | use |
| warning.border | `color.status.warning.border` | `#c9973f` | use |
| warning.outline-text | `color.status.warning.text` | `#c9973f` | use |
| success.bg | `color.status.success.fill` | `#86a46f` | use |
| success.text | `color.status.success.on-fill` | `#0c0909` | use |
| success.border | `color.status.success.border` | `#86a46f` | use |
| success.outline-text | `color.status.success.text` | `#86a46f` | use |
| info.bg | `color.status.info.fill` | `#7e9ebb` | use |
| info.text | `color.status.info.on-fill` | `#0c0909` | use |
| info.border | `color.status.info.border` | `#7e9ebb` | use |
| info.outline-text | `color.status.info.text` | `#7e9ebb` | use |
| neutral.bg | `color.status.neutral.fill` | `#a3676b` | use |
| neutral.text | `color.status.neutral.on-fill` | `#000000` | use |
| neutral.border | `color.status.neutral.border` | `#a3676b` | use |
| neutral.outline-text | `color.status.neutral.text` | `#bd787d` | use |
| count.bg | `color.action.primary.bg` | `#7d1310` | use |
| count.text | `color.action.primary.text` | `#f4eeee` | use |

#### Purpose

Names a status or shows a count next to the thing it describes. A badge is
read-only: it never opens, toggles or removes anything. An interactive
label is a chip; a message with a status is an alert.

#### Anatomy

An inline span in caption type with 2px by 8px padding and square corners.
Status badges fill with `color.status.<status>.fill` and use the matching
`on-fill` text, preceded by the status glyph: ✕ danger, ! warning, ✓ success,
i info, · neutral. Outline badges have no fill, the `text` colour and a 1px
`border` in the same status. The count badge is a number on
{color.action.primary.bg} in {color.action.primary.text} with a visually
hidden unit. An empty badge is an 8px square dot in the status fill with a
name.

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | fill, glyph and text for the status; or the outline form | the glyph; the text |
| empty | an 8px square dot in the status fill; the glyph and text visually hidden | `role="img"` with `aria-label`, or the hidden text; the dot's position beside its subject |

A badge has no hover, focus, pressed or disabled state because it is not a
control.

#### Keyboard

None. A badge is never focusable. When a badge summarises something the
user can act on, the action lives in the neighbouring control and the badge
is part of that control's name or description.

#### Accessibility

The glyph is `aria-hidden` and the text carries the meaning, so a screen
reader hears "Failed", not "cross Failed". Count badges include their unit
as visually hidden text ("12 unread"). An empty badge, a dot, carries
`role="img"` and `aria-label` or wraps hidden text; a dot with no name is
decoration and must be `aria-hidden`. Contrast: on-fill text 4.58:1 (danger),
7.54:1 (warning), 7.13:1 (success), 7.08:1 (info), 4.71:1 (neutral); outline
text 5.26:1 to 7.34:1; borders and dots at least 4.04:1 on the panel surface.
Badges are not targets, so no minimum size applies beyond legibility at 11px
caption type.

#### Portability

A filled or outlined span with a glyph. Toolkits without background colours
on labels use the outline form. Hosts without the glyph characters use the
nearest available (x, !, check mark, i, bullet) and record it.

#### Non-examples

Rounded or pill badges. A badge that is clickable. A status shown by colour
without its glyph. A count badge that hides its unit from assistive
technology. Purple, cyan or magenta statuses. A badge in body type. A badge
whose text wraps. A dot without a name.

### Chip (`chip`)

A square bordered token for a filter, a removable value or a static tag; a pressed filter chip takes the selection fill and a check glyph. Family: display. Priority: R1. Maturity: stable. States: default, hover, focus-visible, selected, selected+focus-visible, disabled.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.default` | `#100c0c` | use |
| root.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| root.text | `color.text.default` | `#e99499` | use |
| root.bg-hover | `color.interaction.hover.bg` | `#1c0a09` | use |
| root.bg-pressed | `color.interaction.pressed.bg` | `#420f0c` | use |
| root.ring | `color.interaction.focus.ring` | `#e53935` | use |
| root.text-disabled | `color.text.disabled` | `#8a5559` | use |
| root.border-disabled | `color.border.disabled` | `#7d1310` | use |
| root.bg-disabled | `color.interaction.disabled.bg` | `#160b0b` | use |
| selected.bg | `color.interaction.selection.bg` | `#531310` | use |
| selected.text | `color.interaction.selection.text` | `#f4eeee` | use |
| selected.ring | `color.interaction.focus.ring-container` | `#ffa2a7` | use |
| remove.text | `color.text.muted` | `#bd787d` | use |
| remove.text-hover | `color.text.bright` | `#ffa2a7` | use |
| remove.bg-hover | `color.interaction.hover.bg-strong` | `#630f0d` | use |

#### Purpose

Represents one value the user can toggle or remove: a filter in a
filterable table, a chosen item in a multiselect, a tag on a record. A chip
that only informs is a badge; a chip that runs an action is a button.

#### Anatomy

A square control-height box with 1px {color.border.control} on
{color.surface.default} and text in {color.text.default}. A filter chip is a
button with a check gutter that shows ✓ while pressed. A removable chip is a
span holding the label and a × button in {color.text.muted}. A static chip is
a span with nothing else. When selected, the chip fills with
{color.interaction.selection.bg} and its text becomes
{color.interaction.selection.text}.

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | 1px {color.border.control} on {color.surface.default}; text {color.text.default} | — |
| hover | background → {color.interaction.hover.bg}; the remove button → {color.interaction.hover.bg-strong} with {color.text.bright} | cursor: pointer |
| focus-visible | ring 1px dashed {color.interaction.focus.ring} at −2px on the chip or on the remove button | the ring |
| selected | fill {color.interaction.selection.bg}; text and glyph {color.interaction.selection.text}; the border takes the fill colour | `aria-pressed="true"`; the ✓ glyph |
| selected+focus-visible | the fill and the ring in {color.interaction.focus.ring-container} | both visible at once |
| disabled | text {color.text.disabled}; border {color.border.disabled}; background {color.interaction.disabled.bg}; no hover | `disabled`; cursor: not-allowed |

Pressing a filter chip uses {color.interaction.pressed.bg} for the duration
of the press only. Precedence: disabled > selected > hover; focus-visible is
always drawn.

#### Keyboard

A filter chip is one tab stop that toggles on Space or Enter. A removable
chip contributes only its remove button to the tab order; the label is not
focusable. A static chip is never focusable. Inside a token field the field
owns Backspace and the arrow keys.

#### Accessibility

Filter chips expose `aria-pressed` and keep the same label in both states;
the ✓ glyph is `aria-hidden`. The remove button is named "Remove <value>" so
it is unambiguous out of context, and removing a chip moves focus to the
next chip or the field. Contrast: text 8.43:1, border 4.33:1, selected text
12.47:1 on the fill, ring 4.57:1 on the surface and 7.48:1 on the fill,
remove glyph 5.66:1. Chips and remove buttons are at least 24×24 CSS pixels
in `compact` density with 4px between neighbours.

#### Portability

A bordered box with a background and an outline; the remove control is an
ordinary small button. Toolkits with a native toggle button take the fill
for the checked state. Hosts without a glyph draw the check as a 2px inset
rule at the inline start and record it.

#### Non-examples

Pill or rounded chips. A selected chip shown by a border colour alone
without the fill and `aria-pressed`. A removable chip whose × has no name.
A chip that is a link. A static tag that reacts to hover. Disabling by
opacity. A chip whose label clips inside the border. A chip in caption
type (that is a badge).

### Table (`table`)

A native data table with a header rule, row dividers, hover and selection fills, sortable headers with a glyph, and static empty and loading rows. Family: display. Priority: R1. Maturity: stable. States: default, hover, selected, selected+focus-visible, selected+container-inactive, sorted, empty, loading.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.default` | `#100c0c` | use |
| header.text | `color.text.bright` | `#ffa2a7` | use |
| header.bg | `color.surface.default` | `#100c0c` | use |
| header.rule | `color.border.strong` | `#9e231f` | use |
| header.bg-sticky | `color.surface.raised` | `#160b0b` | use |
| sort.glyph | `color.text.bright` | `#ffa2a7` | use |
| sort.ring | `color.interaction.focus.ring` | `#e53935` | use |
| row.text | `color.text.default` | `#e99499` | use |
| row.divider | `color.border.divider` | `#2b0e0d` | use-and-report (D-008) |
| row.bg-hover | `color.interaction.hover.bg` | `#1c0a09` | use |
| selected.bg | `color.interaction.selection.bg` | `#531310` | use |
| selected.text | `color.interaction.selection.text` | `#f4eeee` | use |
| selected.ring | `color.interaction.focus.ring-container` | `#ffa2a7` | use |
| selected.inactive-bg | `color.interaction.selection.inactive-bg` | `#420f0c` | use |
| selected.inactive-text | `color.interaction.selection.inactive-text` | `#e99499` | use |
| caption.text | `color.text.muted` | `#bd787d` | use |
| empty.text | `color.text.muted` | `#bd787d` | use |
| loading.glyph | `color.text.muted` | `#bd787d` | use |

#### Purpose

Shows records as rows and attributes as columns so they can be compared
down a column. The table is for reading; a table with selection
checkboxes, resizable columns and a toolbar is the data-table. A single
column of rows is a list.

#### Anatomy

A collapsed native table on {color.surface.default} with no outer border.
The header row is {color.text.bright} above a 1px {color.border.strong} rule
(2px when the header is sticky, on {color.surface.raised}); sortable headers
wrap their label in a button and show ▲ or ▼ when sorted. Body rows are the
row height, separated by 1px {color.border.divider}, text in
{color.text.default}; numeric cells are end-aligned with tabular numbers.
An optional caption in {color.text.muted} sits above. Two special rows,
hidden unless needed, span every column: the empty message and the loading
glyph.

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | rows on {color.surface.default}; header rule 1px {color.border.strong} | — |
| hover | row background → {color.interaction.hover.bg} | cursor: default; only on hover-capable pointers |
| selected | row fill {color.interaction.selection.bg}; every cell {color.interaction.selection.text} | `aria-selected="true"` on the row |
| selected+focus-visible | the fill and a ring in {color.interaction.focus.ring-container} around the row | both visible at once |
| selected+container-inactive | row fill {color.interaction.selection.inactive-bg} with {color.interaction.selection.inactive-text}; no ring | lightness drop between the two fills |
| sorted | the sorted header shows ▲ (ascending) or ▼ (descending) in {color.text.bright} | `aria-sort`; the glyph |
| empty | the body is one full-width cell in {color.text.muted}: "No rows match." | the message text |
| loading | the body is one full-width cell with the static `⋯` glyph in {color.text.muted}; the header stays | `aria-busy="true"`; the glyph; no animation |

Precedence: loading > empty; selected > hover; focus-visible is always
drawn.

#### Keyboard

A plain table has no row focus: Tab reaches the sort buttons in the header
and any control inside a cell. Enter or Space on a sort button cycles
ascending, descending. Tables whose rows are targets (row links, row
selection) are the data-table and add row focus there.

#### Accessibility

`caption` or `aria-labelledby` names the table; `th scope="col"` on every
header and `scope="row"` on a row header column. `aria-sort` on the sorted
`th`, `none` or absent elsewhere; the glyph is `aria-hidden`. The empty and
loading rows are real rows so the table stays a table; `aria-busy="true"`
on the table while loading and a live region for the outcome. Contrast:
body text 8.43:1, header 10.10:1, caption and empty message 5.66:1, selected
text 12.47:1 on the fill, inactive 7.02:1, ring 7.48:1 on the fill, sort ring
4.57:1. Rows are at least 24 CSS pixels tall; sort buttons fill the header
cell.

#### Portability

Row backgrounds, one border under the header, one between rows, an outline
on a selected row. Toolkits with a native table view take the rule, the
divider and the two fills. Hosts that cannot draw an outline on a row draw
the ring as an inset 1px border and record it.

#### Non-examples

Zebra striping. A rounded or bordered table frame. A selected row marked by
a left bar (that is navigation, not selection). Header text in caption size
or uppercase. Sorting shown by colour alone without `aria-sort` and a glyph.
An animated spinner while loading. An empty state that removes the header.
Text truncated with no way to read the full value. A hover fill on a
touch device.

### Data table (`data-table`)

A native table with controls: a selection column with a mixed select-all, sortable headers, row focus with the ring on the fill, and static resize handles. Family: display. Priority: R1. Maturity: stable. States: default, hover, focus-visible, selected, selected+focus-visible, sorted, mixed, empty, loading.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.default` | `#100c0c` | use |
| header.text | `color.text.bright` | `#ffa2a7` | use |
| header.rule | `color.border.strong` | `#9e231f` | use |
| sort.glyph | `color.text.bright` | `#ffa2a7` | use |
| row.text | `color.text.default` | `#e99499` | use |
| row.divider | `color.border.divider` | `#2b0e0d` | use-and-report (D-008) |
| row.bg-hover | `color.interaction.hover.bg` | `#1c0a09` | use |
| row.ring | `color.interaction.focus.ring` | `#e53935` | use |
| selected.bg | `color.interaction.selection.bg` | `#531310` | use |
| selected.text | `color.interaction.selection.text` | `#f4eeee` | use |
| selected.ring | `color.interaction.focus.ring-container` | `#ffa2a7` | use |
| checkbox.bg | `color.surface.input` | `#000000` | use |
| checkbox.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| checkbox.checked-bg | `color.action.primary.bg` | `#7d1310` | use |
| checkbox.glyph | `color.action.primary.text` | `#f4eeee` | use |
| resize.handle | `color.border.default` | `#531310` | use |
| resize.handle-hover | `color.border.active` | `#e53935` | use |
| empty.text | `color.text.muted` | `#bd787d` | use |
| loading.glyph | `color.text.muted` | `#bd787d` | use |

#### Purpose

A table the user works in: selects rows, sorts, resizes columns. The
plain table is for reading; this component adds the controls and the
keyboard model. It stays a native table so its cells keep their header
associations; hosts that need cell-level navigation promote it to a grid
and record the change.

#### Anatomy

The table component's structure with the same rule, dividers and fills,
plus: a selection column whose header holds a select-all with
`aria-checked` true, false or mixed, drawn on {color.action.primary.bg} with
the ✓ or – glyph in {color.action.primary.text}; native checkboxes in each
row on {color.surface.input} with a 1px {color.border.control} boundary;
rows that can take focus; and in the resizable variant a 1px handle in
{color.border.default} at the inline end of each header that turns
{color.border.active} under the pointer.

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | rows on {color.surface.default}; header rule 1px {color.border.strong} | — |
| hover | row background → {color.interaction.hover.bg}; a resize handle → {color.border.active} | cursor: default on rows, col-resize on handles |
| focus-visible | ring 1px dashed {color.interaction.focus.ring} at −2px around the focused row or control | the ring |
| selected | row fill {color.interaction.selection.bg}; every cell {color.interaction.selection.text}; the row checkbox checked | `aria-selected="true"`; the checked box |
| selected+focus-visible | the fill and the ring in {color.interaction.focus.ring-container} | both visible at once |
| sorted | ▲ or ▼ in {color.text.bright} on the sorted header | `aria-sort`; the glyph |
| mixed | the select-all on {color.action.primary.bg} with the – glyph | `aria-checked="mixed"`; the glyph |
| empty | one full-width cell in {color.text.muted}: "No rows match."; the header and controls stay | the message text |
| loading | one full-width cell with the static `⋯` glyph; the header stays | `aria-busy="true"`; the glyph; no animation |

Precedence: loading > empty; selected > hover; focus-visible is always
drawn. Selection is a fill; focus is a ring; they never borrow each other's
form.

#### Keyboard

Header controls first: the select-all, then each sort button. Rows use a
roving tabindex so the body costs one tab stop; Up and Down move, Space
toggles, Shift extends, Ctrl+A selects all. Controls inside the focused row
are reached with Tab and left with Shift+Tab. Column widths are set from a
header menu or by dragging the handle; the handle itself is never a tab
stop.

#### Accessibility

Every row checkbox is labelled by the row's first data cell
(`aria-labelledby`); the select-all is named "Select all rows" and exposes
`aria-checked="mixed"` when only some are selected. `aria-selected` on the
row mirrors its checkbox. Sorted headers carry `aria-sort`; the table
carries `aria-busy` while loading; the count of selected rows is announced
through a live region owned by the surrounding toolbar. Contrast: text
8.43:1, header 10.10:1, selected text 12.47:1 on the fill, ring 4.57:1 on the
surface and 7.48:1 on the fill, checkbox boundary 4.33:1, check glyph 9.28:1
on the primary fill. Checkboxes and handles are at least 24 CSS pixels tall
in `compact` density.

#### Portability

The table component's drawing plus a checkbox column and a header
checkbox; the handle is a 1px rule. Toolkits with native table views map
row selection to their selection model and the mixed state to their
tristate checkbox. Hosts without row focus fall back to the checkbox as the
only row control and record it.

#### Non-examples

A grid role on a table that is not navigated cell by cell. Zebra stripes.
A select-all that is a native checkbox with a script-only indeterminate
state and no `aria-checked`. Rows selected by clicking anywhere with no
checkbox alternative. A selected row shown by a border alone. A resize
handle that is a tab stop. An animated loading skeleton. Rounded checkboxes.
Text truncated with no full-value affordance.

### Description list (`description-list`)

Semantic terms and values that wrap into a readable single column. Family: display. Priority: R2. Maturity: stable. States: default, stacked.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.default` | `#100c0c` | use |
| root.text | `color.text.default` | `#e99499` | use |
| root.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| heading.text | `color.text.bright` | `#ffa2a7` | use |
| secondary.text | `color.text.muted` | `#bd787d` | use |
| control.bg | `color.surface.input` | `#000000` | use |
| control.focus | `color.interaction.focus.ring` | `#e53935` | use |
| dependency.role-border-width-default | `border.width.default` | `1px` | use |
| dependency.role-color-border-divider | `color.border.divider` | `#2b0e0d` | use-and-report (D-008) |
| dependency.role-radius-none | `radius.none` | `0px` | use |
| dependency.role-space-12 | `space.12` | `12px` | use |
| dependency.role-space-2 | `space.2` | `2px` | use |
| dependency.role-space-4 | `space.4` | `4px` | use |
| dependency.role-space-8 | `space.8` | `8px` | use |

#### Purpose

Present named facts as semantic terms and descriptions. Terms remain adjacent to their values in source order. A narrow view stacks the term above its value.

#### Anatomy

- **list:** Native description list.
- **term:** Concise name for a fact.
- **value:** Text, links or semantic inline content.

#### States

| State | Presentation | Non-colour channel |
| --- | --- | --- |
| default | Two aligned columns. | Native term and description relationships. |
| stacked | One column at narrow widths. | Each term immediately precedes its value. |

#### Keyboard

- **Tab / Shift+Tab:** Visit links in values. Plain facts are not tab stops.

#### Accessibility

Use dt and dd rather than a grid of unrelated paragraphs. Long values wrap without clipping. Preserve meaningful link text and do not make every value keyboard focusable.

#### Portability

Wrap labels and actions without changing source order. Use logical spacing in RTL. Preserve native target sizes at both densities; only bounded data regions may scroll horizontally. Use equivalent native semantics and approved roles; record any unsupported behavior as a mapping deviation. The host owns application data, persistence, permissions and services.

#### Non-examples

Unlabelled controls, color-only status, drag-only operations, hidden required instructions, stolen focus or an invented service success.

### Avatar (`avatar`)

A square entity image with a named text fallback. Family: display. Priority: R2. Maturity: stable. States: default, loading, error.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.default` | `#100c0c` | use |
| root.text | `color.text.default` | `#e99499` | use |
| root.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| heading.text | `color.text.bright` | `#ffa2a7` | use |
| secondary.text | `color.text.muted` | `#bd787d` | use |
| control.bg | `color.surface.input` | `#000000` | use |
| control.focus | `color.interaction.focus.ring` | `#e53935` | use |
| dependency.role-border-width-default | `border.width.default` | `1px` | use |
| dependency.role-font-weight-bold | `font.weight.bold` | `700` | use |
| dependency.role-radius-none | `radius.none` | `0px` | use |
| dependency.role-space-12 | `space.12` | `12px` | use |
| dependency.role-space-2 | `space.2` | `2px` | use |
| dependency.role-space-32 | `space.32` | `32px` | use |

#### Purpose

Identify a person or entity with an optional caller-owned image and text fallback. The component ships no image service or assets. Failed images restore the fallback and retain the full accessible name.

#### Anatomy

- **frame:** Square bounded image area with a border.
- **fallback:** Caller-supplied initials, backed by the full accessible name.
- **image:** Optional image; failed loading restores the text fallback.

#### States

| State | Presentation | Non-colour channel |
| --- | --- | --- |
| default | Initials or loaded image. | Full name on the image container. |
| loading | Fallback remains visible. | No loss of the full accessible name while loading. |
| error | Fallback remains visible. | Image failure does not remove the entity name. |

#### Keyboard

- **Tab:** No additional tab stop. An enclosing link remains native.

#### Accessibility

Use one full accessible name, avoiding duplicate announcements from image alt and fallback text. Initials alone do not adequately identify a person. A decorative avatar beside an equivalent visible name may be aria-hidden.

#### Portability

Wrap labels and actions without changing source order. Use logical spacing in RTL. Preserve native target sizes at both densities; only bounded data regions may scroll horizontally. Use equivalent native semantics and approved roles; record any unsupported behavior as a mapping deviation. The host owns application data, persistence, permissions and services.

#### Non-examples

Unlabelled controls, color-only status, drag-only operations, hidden required instructions, stolen focus or an invented service success.

### Timeline (`timeline`)

Chronological events with explicit dates, descriptions and current state. Family: display. Priority: R2. Maturity: stable. States: default, current.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.default` | `#100c0c` | use |
| root.text | `color.text.default` | `#e99499` | use |
| root.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| heading.text | `color.text.bright` | `#ffa2a7` | use |
| secondary.text | `color.text.muted` | `#bd787d` | use |
| control.bg | `color.surface.input` | `#000000` | use |
| control.focus | `color.interaction.focus.ring` | `#e53935` | use |
| dependency.role-border-width-default | `border.width.default` | `1px` | use |
| dependency.role-border-width-emphasis | `border.width.emphasis` | `2px` | use |
| dependency.role-color-border-active | `color.border.active` | `#e53935` | use |
| dependency.role-color-border-divider | `color.border.divider` | `#2b0e0d` | use-and-report (D-008) |
| dependency.role-radius-none | `radius.none` | `0px` | use |
| dependency.role-space-12 | `space.12` | `12px` | use |
| dependency.role-space-2 | `space.2` | `2px` | use |
| dependency.role-space-4 | `space.4` | `4px` | use |

#### Purpose

Read events as an ordered list with explicit time and status text. The caller supplies order; the component does not reorder unknown dates. Lines support the sequence without becoming the only status channel.

#### Anatomy

- **list:** Ordered events in reading order.
- **time:** Native time element with machine-readable datetime.
- **event:** Heading and status-bearing description.

#### States

| State | Presentation | Non-colour channel |
| --- | --- | --- |
| default | Divider along the event sequence. | Ordered list and visible time. |
| current | Emphasized start border. | aria-current and explicit Current text. |

#### Keyboard

- **Tab / Shift+Tab:** Reach event links and actions. The sequence is not focusable.

#### Accessibility

Use real time values and visible dates. Identify the current event with aria-current and text. Avoid a color-only dot or an order that changes between visual presentation and reading order.

#### Portability

Wrap labels and actions without changing source order. Use logical spacing in RTL. Preserve native target sizes at both densities; only bounded data regions may scroll horizontally. Use equivalent native semantics and approved roles; record any unsupported behavior as a mapping deviation. The host owns application data, persistence, permissions and services.

#### Non-examples

Unlabelled controls, color-only status, drag-only operations, hidden required instructions, stolen focus or an invented service success.

### Chart (`chart`)

A one-series numeric chart with an always-visible complete data table. Family: display. Priority: R2. Maturity: stable. States: default, empty, loading.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.default` | `#100c0c` | use |
| root.text | `color.text.default` | `#e99499` | use |
| root.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| heading.text | `color.text.bright` | `#ffa2a7` | use |
| secondary.text | `color.text.muted` | `#bd787d` | use |
| control.bg | `color.surface.input` | `#000000` | use |
| control.focus | `color.interaction.focus.ring` | `#e53935` | use |
| dependency.role-border-width-default | `border.width.default` | `1px` | use |
| dependency.role-color-chart-axis | `color.chart.axis` | `#9e231f` | use |
| dependency.role-color-chart-grid | `color.chart.grid` | `#2b0e0d` | use |
| dependency.role-color-chart-label | `color.chart.label` | `#bd787d` | use |
| series.primary | `color.chart.series-2` | `#f73f35` | use |
| header.text | `color.text.bright` | `#ffa2a7` | use |
| row.text | `color.text.default` | `#e99499` | use |
| dependency.role-radius-none | `radius.none` | `0px` | use |
| dependency.role-space-12 | `space.12` | `12px` | use |
| dependency.role-space-2 | `space.2` | `2px` | use |
| dependency.role-space-8 | `space.8` | `8px` | use |

#### Purpose

Show one bounded numeric series with its complete textual data table. Labels and exact values carry the meaning without color. The API accepts finite non-negative values; it rejects invalid data rather than silently inventing missing numbers.

#### Anatomy

- **figure:** Caption identifies the measure and units.
- **plot:** Responsive SVG with an accessible name.
- **table:** Always-visible labels and exact values.
- **empty:** Explicit message when the supplied series is empty.

#### States

| State | Presentation | Non-colour channel |
| --- | --- | --- |
| default | Red primary series (`color.chart.series-2`), with canonical axis and label roles. | Complete labelled data table. |
| empty | Plot and rows are absent. | Explicit No data available text. |
| loading | Existing data remains readable under a busy indicator. | aria-busy with named loading text from the host. |

#### Keyboard

- **Tab:** Visit any scrollable data region. Points do not add unnecessary tab stops.

#### Accessibility

Keep the table visible without JavaScript. Do not replace it with tooltips or color-only legends. The single series uses the approved red chart role `color.chart.series-2`, as requested by the owner for graphs; foreground typography retains its rose roles. Each SVG bar has a text label. A loading status must distinguish old data from a completed refresh.

Column headers use `color.text.bright`; body row labels and numeric values use
`color.text.default`. Row-header semantics remain native `th scope="row"` even
though their typography matches body values. Do not apply the column-header color
to every `th` indiscriminately.

#### Portability

Wrap labels and actions without changing source order. Use logical spacing in RTL. Preserve native target sizes at both densities; only bounded data regions may scroll horizontally. Use equivalent native semantics and approved roles; record any unsupported behavior as a mapping deviation. The host owns application data, persistence, permissions and services.

#### Non-examples

Unlabelled controls, color-only status, drag-only operations, hidden required instructions, stolen focus or an invented service success.

### Keyboard key (`kbd`)

A literal key or short keyboard sequence rendered as native kbd text. Family: display. Priority: R2. Maturity: stable. States: default.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.default` | `#100c0c` | use |
| root.text | `color.text.default` | `#e99499` | use |
| root.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| heading.text | `color.text.bright` | `#ffa2a7` | use |
| secondary.text | `color.text.muted` | `#bd787d` | use |
| control.bg | `color.surface.input` | `#000000` | use |
| control.focus | `color.interaction.focus.ring` | `#e53935` | use |
| dependency.role-border-width-default | `border.width.default` | `1px` | use |
| dependency.role-border-width-emphasis | `border.width.emphasis` | `2px` | use |
| dependency.role-radius-none | `radius.none` | `0px` | use |
| dependency.role-space-12 | `space.12` | `12px` | use |
| dependency.role-space-2 | `space.2` | `2px` | use |
| dependency.role-space-4 | `space.4` | `4px` | use |

#### Purpose

Show readable key names using native kbd text. This documents a shortcut; it does not bind one. State the platform when shortcuts differ.

#### Anatomy

- **key:** Native kbd containing a readable key name.
- **sequence:** Separate keys joined by text in activation order.

#### States

| State | Presentation | Non-colour channel |
| --- | --- | --- |
| default | Named content on the default surface. | Native text and structure. |

#### Keyboard

- **None:** The keycap is documentation, not a button.

#### Accessibility

Write readable names such as Ctrl, Shift and Escape. Keep sequence punctuation as text. A shortcut hint does not replace an action's accessible name.

#### Portability

Wrap labels and actions without changing source order. Use logical spacing in RTL. Preserve native target sizes at both densities; only bounded data regions may scroll horizontally. Use equivalent native semantics and approved roles; record any unsupported behavior as a mapping deviation. The host owns application data, persistence, permissions and services.

#### Non-examples

Unlabelled controls, color-only status, drag-only operations, hidden required instructions, stolen focus or an invented service success.

## Feedback and overlays

Alerts, toasts, progress, loading, skeletons, tooltips, popovers, dialogs, drawers, empty and error states.

### Alert (`alert`)

An inline status message with a mandatory glyph on the status tint behind a 1px status border; role alert for danger, status otherwise. Family: feedback. Priority: R1. Maturity: stable. States: default, hover, focus-visible.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| danger.bg | `color.status.danger.tint` | `#2b0e0d` | use |
| danger.border | `color.status.danger.border` | `#e53935` | use |
| danger.text | `color.status.danger.text` | `#f73f35` | use |
| warning.bg | `color.status.warning.tint` | `#1f1a0c` | use |
| warning.border | `color.status.warning.border` | `#c9973f` | use |
| warning.text | `color.status.warning.text` | `#c9973f` | use |
| success.bg | `color.status.success.tint` | `#0f1a0e` | use |
| success.border | `color.status.success.border` | `#86a46f` | use |
| success.text | `color.status.success.text` | `#86a46f` | use |
| info.bg | `color.status.info.tint` | `#0f141c` | use |
| info.border | `color.status.info.border` | `#7e9ebb` | use |
| info.text | `color.status.info.text` | `#7e9ebb` | use |
| neutral.bg | `color.status.neutral.tint` | `#241010` | use |
| neutral.border | `color.status.neutral.border` | `#a3676b` | use |
| neutral.text | `color.status.neutral.text` | `#bd787d` | use |
| body.text | `color.text.default` | `#e99499` | use |
| close.text | `color.action.tertiary.text` | `#e99499` | use |
| close.bg-hover | `color.action.tertiary.hover-bg` | `#1c0a09` | use |
| close.ring | `color.interaction.focus.ring` | `#e53935` | use |

#### Purpose

A status message that belongs to the page or form around it: a failed
submit, a saved draft, a deprecation notice. It stays in flow, is never
timed, and is announced once when it appears. Transient confirmations are
toasts; blocking questions are dialogs.

#### Anatomy

The block on the status tint behind a 1px status border; the glyph in the
status text colour; the title in the same colour and the message in
{color.text.default}; an optional close button at the end. The five statuses
use their own `tint`, `border` and `text` roles: {color.status.danger.tint},
{color.status.warning.tint}, {color.status.success.tint},
{color.status.info.tint} and {color.status.neutral.tint} behind
{color.status.danger.border}, {color.status.warning.border},
{color.status.success.border}, {color.status.info.border} and
{color.status.neutral.border}.

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | status tint, 1px status border, glyph and title in the status text colour | the glyph ✕ ! ✓ i · |
| hover | close button background → {color.action.tertiary.hover-bg}; the alert itself does not react | cursor: pointer on the button only |
| focus-visible | ring 1px dashed {color.interaction.focus.ring} at −2px on the close button | the ring |

The alert has no disabled, selected or loading state; a message that is no
longer true is removed, not greyed.

#### Keyboard

Only the close button is in the tab order. Enter or Space dismisses and
returns focus to where it was, or to the next focusable element after the
alert when that element is gone. Escape does nothing, so a page-level Escape
handler is never swallowed.

#### Accessibility

Danger uses `role="alert"` (assertive) because it needs attention now; the
other four use `role="status"` (polite). The container exists before its
text is set, so the live region fires; text is never injected together with
the container. The glyph is `aria-hidden` and the title states the status in
words ("Build failed", "Saved"), so nothing depends on colour or on the glyph
alone. The close button is `aria-label="Dismiss"` and at least 24×24 CSS
pixels. Contrast: status text on its tint from 4.88:1 (danger) to 6.59:1
(warning, info); body text at least 7.56:1 on every tint; borders from 4.07:1
(neutral) to 6.59:1.

#### Portability

Everything is a tint, a 1px border and text; the glyph is a text character,
not an icon font. Toolkits with a native info bar map its type to the five
statuses and keep the glyph in the text; toolkits without a tint draw the
border only and record the deviation.

#### Non-examples

A coloured left stripe with no border. A rounded, shadowed card. A banner
that slides in or auto-dismisses. Colour-only status without the glyph and a
worded title. Using `role="alert"` for success messages. A close button that
is an icon with no accessible name. Fixed-position alerts covering content.

### Toast (`toast`)

A brief notification in a fixed corner stack; canvas surface behind a 1px overlay border, status tints for the four polite statuses and the danger fill for critical ones. Family: feedback. Priority: R1. Maturity: stable. States: default, hover, focus-visible, stacked, reduced-motion.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.canvas` | `#000000` | use |
| root.border | `color.border.overlay` | `#e53935` | use-and-report (D-008) |
| root.text | `color.text.default` | `#e99499` | use |
| glyph.text | `color.status.info.text` | `#7e9ebb` | use |
| warning.bg | `color.status.warning.tint` | `#1f1a0c` | use |
| warning.border | `color.status.warning.border` | `#c9973f` | use |
| warning.text | `color.status.warning.text` | `#c9973f` | use |
| success.bg | `color.status.success.tint` | `#0f1a0e` | use |
| success.border | `color.status.success.border` | `#86a46f` | use |
| success.text | `color.status.success.text` | `#86a46f` | use |
| info.bg | `color.status.info.tint` | `#0f141c` | use |
| info.border | `color.status.info.border` | `#7e9ebb` | use |
| info.text | `color.status.info.text` | `#7e9ebb` | use |
| neutral.bg | `color.status.neutral.tint` | `#241010` | use |
| neutral.border | `color.status.neutral.border` | `#a3676b` | use |
| neutral.text | `color.status.neutral.text` | `#bd787d` | use |
| danger.bg | `color.status.danger.fill` | `#dc282e` | use |
| danger.text | `color.status.danger.on-fill` | `#f9faf9` | use |
| danger.button-bg-hover | `color.action.destructive.pressed-bg` | `#ae1914` | use |
| action.text | `color.text.link` | `#f73f35` | use |
| action.bg-hover | `color.interaction.hover.bg-strong` | `#630f0d` | use |
| action.text-hover | `color.text.link-hover` | `#f4eeee` | use |
| close.text | `color.action.tertiary.text` | `#e99499` | use |
| close.bg-hover | `color.action.tertiary.hover-bg` | `#1c0a09` | use |
| close.ring | `color.interaction.focus.ring` | `#e53935` | use |
| count.text | `color.text.muted` | `#bd787d` | use |
| stack.z | `z.toast` | `40` | use |

#### Purpose

A short confirmation or notice that does not belong to any one element on
the page: "Saved", "Copied", "Connection lost". It appears in a fixed corner
stack, is announced politely, and leaves on its own after a pause or when
dismissed. Anything that needs a decision is a dialog; anything tied to a
form is an alert.

#### Anatomy

The stack region; each toast on {color.surface.canvas} behind a 1px
{color.border.overlay}, or on a status tint with its status border; the glyph
and the text; an optional action; the Dismiss button; the "+n" count. The
danger toast is the historical critical notification: {color.status.danger.fill}
with {color.status.danger.on-fill} for the text, the glyph and both buttons.

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | canvas surface, 1px overlay border, glyph in the status text colour | the glyph; the worded text |
| hover | action → {color.interaction.hover.bg-strong} with {color.text.link-hover}; Dismiss → {color.action.tertiary.hover-bg}; the timeout pauses | cursor: pointer; underline thickens on the action |
| focus-visible | ring 1px dashed {color.interaction.focus.ring} at −2px on the focused button; on the danger fill the ring takes {color.status.danger.on-fill}; the timeout pauses | the ring |
| stacked | toasts sit in one column with a 4px gap, newest at the end; the last visible toast shows "+n" in {color.text.muted} for queued ones | the count text; at most three visible |
| reduced-motion | no transition; the toast is simply present or absent | `prefers-reduced-motion`; nothing moves |

The toast has no disabled, selected or busy state.

#### Keyboard

Toasts never take focus when they appear. A host shortcut (F6 on the web
reference) moves focus into the region; Tab then walks each toast's action
and Dismiss; Escape dismisses the focused toast. While any toast has focus or
the pointer, its timeout is paused.

#### Accessibility

The region is named "Notifications"; each toast is `role="status"` so it is
announced politely, and danger toasts are `role="alert"`. The text states the
outcome in words and the glyph is `aria-hidden`. Every toast has Dismiss and
the timeout is at least 8 seconds, extended for toasts with an action; an
action is never the only way to reach what it does. Contrast: text 8.65:1 on
the canvas, the overlay border 4.69:1, status text on its tint from 5.31:1
(neutral) to 6.59:1, on-fill text 4.58:1 on the danger fill, which is itself
4.14:1 against the canvas.

#### Portability

A fixed region, tints, 1px borders and text glyphs. Hosts with a notification
daemon send the text and urgency instead of drawing a toast; the danger fill
maps to the critical urgency. Toolkits with a native toast overlay use it and
record that the danger fill is not available.

#### Non-examples

A toast that slides in from an edge. A rounded, shadowed pill. A toast with
no Dismiss that vanishes after two seconds. A toast that takes focus on
appearance. Stacking more than three with no count. A form error shown as a
toast. Colour-only status without the glyph and worded text.

### Progress (`progress`)

A native progress element with a label and value; determinate fills with the primary colour, indeterminate shows static 45° stripes, never an animation. Family: feedback. Priority: R1. Maturity: stable. States: default, busy, error, reduced-motion.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| track.bg | `color.interaction.pressed.bg` | `#420f0c` | use |
| track.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| value.bg | `color.action.primary.bg` | `#7d1310` | use |
| value.bg-error | `color.status.danger.fill` | `#dc282e` | use |
| stripe.a | `color.action.primary.bg` | `#7d1310` | use |
| stripe.b | `color.interaction.pressed.bg` | `#420f0c` | use |
| label.text | `color.text.bright` | `#ffa2a7` | use |
| value.text | `color.text.default` | `#e99499` | use |
| detail.text | `color.text.muted` | `#bd787d` | use |
| error.text | `color.status.danger.text` | `#f73f35` | use |

#### Purpose

Shows how far a known task has gone, or that an unbounded one is running.
It reports; it never blocks. A task that can be cancelled has a button beside
the bar. A short wait with no bar is the loading indicator.

#### Anatomy

The header row with the label in {color.text.bright} and the value in
{color.text.default}; the bar, a native progress element whose track is
{color.interaction.pressed.bg} behind a 1px {color.border.control} and whose
fill is {color.action.primary.bg}; an optional detail line in
{color.text.muted}.

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | fill {color.action.primary.bg} on the track; the value text | the native value; the percentage text |
| busy | the whole track shows static 45° stripes of {color.action.primary.bg} on {color.interaction.pressed.bg}; no value text | `aria-busy`; `aria-valuetext`; the stripe pattern; no animation |
| error | fill → {color.status.danger.fill}; the label gains the ✕ glyph in {color.status.danger.text}; the detail line says what failed | glyph; worded detail |
| reduced-motion | identical to default and busy: nothing animates in any state | `prefers-reduced-motion` |

Indeterminate work is the busy state; the determinate variant enters it while
the total is unknown and leaves it once known.

#### Keyboard

None. The progress element is not focusable and receives no key. A cancel
button next to the bar is an ordinary button with its own tab stop.

#### Accessibility

The label is a real `<label for>` on the progress element, so its name is
programmatic; the value is in the native `value` and repeated in text.
Indeterminate bars set `aria-valuetext` ("Preparing"), and the container sets
`aria-busy` for the duration. Completion and failure are announced through a
polite live region, not by the bar. Contrast: label 10.37:1, value 8.65:1,
detail 5.81:1, track boundary 4.45:1, error fill 3.36:1 against the track.
The primary fill against the track measures 1.51:1 and is waived because the
value is stated in text; a host that must meet 3:1 graphically raises the
track to {color.surface.canvas}.

#### Portability

A track, a fill and a hatched pattern. Toolkits with a native bar map the
fill to the primary colour and disable pulse animation; toolkits without
gradients draw the stripes with a hatched brush or alternating characters.

#### Non-examples

A spinner. Animated or "barber-pole" stripes. A shimmering skeleton in place
of a bar. A circular ring. A rounded pill-shaped bar. Colour-only failure
without the glyph and text. A bar with no label. A bar that blocks input.

### Loading indicator (`loading-indicator`)

A static ⋯ glyph with a worded label inside an aria-busy container; inline next to text or as a centred block. There is no spinner. Family: feedback. Priority: R1. Maturity: stable. States: default, reduced-motion.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| glyph.text | `color.text.muted` | `#bd787d` | use |
| label.text | `color.text.default` | `#e99499` | use |
| block.bg | `color.surface.default` | `#100c0c` | use |
| block.border | `color.border.divider` | `#2b0e0d` | use-and-report (D-008) |

#### Purpose

Marks the moment between a request and its result when there is nothing to
measure: a list that is being fetched, a field that is being validated. The
theme has no spinner; the same static ⋯ glyph that a loading control shows
appears here beside a worded label, and the container says it is busy.

#### Anatomy

The busy container; the glyph in {color.text.muted}; the label in
{color.text.default}. Inline, the two follow the text they qualify; as a
block, they sit centred on {color.surface.default} inside a
{color.border.divider} edge where the content will appear.

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | the static ⋯ and the label | `aria-busy="true"`; the glyph; the worded label |
| reduced-motion | identical: there is nothing to reduce | `prefers-reduced-motion` |

The indicator has no hover, focus or disabled state; it is not a control.

#### Keyboard

None. The indicator is never focusable. When loading replaces content that
had focus, focus moves to the container's heading or to the indicator's
parent region, never to the indicator itself, and returns to the result when
it arrives.

#### Accessibility

The container is `role="status"` with `aria-busy="true"` while loading, so
assistive technology can wait for the subtree to settle and announce the
label once. The glyph is `aria-hidden`; the label is a real sentence with the
object named ("Loading fonts") and is visible in both variants; a host that
must hide it inline keeps it visually hidden, never absent. Because nothing moves, there is no
`prefers-reduced-motion` branch to get wrong. Contrast: label 8.65:1 on the
canvas and 8.43:1 on the panel surface; glyph 5.81:1 and 5.66:1.

#### Portability

Text only. Toolkits with a spinner widget do not use it; the glyph and label
are a plain label. Hosts that insist on an activity indicator record the
deviation and keep it under the 150 ms motion budget with a reduced-motion
branch.

#### Non-examples

A spinner or rotating ring. Animated dots. A shimmering skeleton. A full-page
overlay that blocks input for a fetch. A glyph with no label. An indicator
that takes focus. A dimmed container at reduced opacity.

### Tooltip (`tooltip`)

A short description of a control shown on hover and focus; role tooltip linked by aria-describedby, a 1px overlay border on the raised surface, no arrow. Family: feedback. Priority: R1. Maturity: stable. States: default, hover, focus-visible, open, closed.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| popup.bg | `color.surface.raised` | `#160b0b` | use |
| popup.border | `color.border.overlay` | `#e53935` | use-and-report (D-008) |
| popup.text | `color.text.default` | `#e99499` | use |
| shortcut.text | `color.text.bright` | `#ffa2a7` | use |
| shortcut.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| trigger.text | `color.action.tertiary.text` | `#e99499` | use |
| trigger.bg-hover | `color.action.tertiary.hover-bg` | `#1c0a09` | use |
| trigger.ring | `color.interaction.focus.ring` | `#e53935` | use |
| popup.z | `z.popover` | `10` | use |

#### Purpose

A few words that explain a control without being its name: what an icon
button does, which shortcut opens it, why it is disabled. It is a description
only. Anything a person must read to use the control belongs in the control
or its label, and anything interactive is a popover.

#### Anatomy

The trigger and the popup. The popup is {color.surface.raised} behind a 1px
{color.border.overlay}, text in {color.text.default}, optional shortcut in a
`kbd` with {color.text.bright} inside a 1px {color.border.control}; no arrow,
no shadow, no radius. The demonstration renders it in flow below the trigger.

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | trigger at rest; the popup shown for the demonstration | `aria-describedby` on the trigger |
| hover | trigger background → {color.action.tertiary.hover-bg}; the popup appears after 300 ms | cursor: pointer; the popup itself |
| focus-visible | ring 1px dashed {color.interaction.focus.ring} at −2px on the trigger; the popup appears at once | the ring; the popup |
| open | the popup visible, 1px {color.border.overlay} on {color.surface.raised} | the popup is present in the accessibility tree |
| closed | the popup absent; the trigger unchanged | `hidden`; the description is still read from the DOM |

The popup itself has no hover, pressed or focus state: it is not
interactive.

#### Keyboard

Focus on the trigger shows the tooltip without delay; moving focus away
hides it. Escape hides it and does nothing else, so a dialog behind the
trigger does not close. The popup never receives focus and contains no
focusable element.

#### Accessibility

The trigger points at the popup with `aria-describedby`, so the description
is available whether or not the popup is shown; the popup is `role="tooltip"`.
It opens on hover and on focus, stays open while the pointer is over the
trigger or the popup (WCAG 1.4.13: hoverable, dismissible, persistent), and
closes on Escape. The trigger has its own accessible name; the tooltip never
supplies it. Touch has no hover: the description is available through a
long-press or is shown inline. Contrast: text 8.43:1 on the raised surface,
shortcut 10.10:1, border 4.57:1.

#### Portability

A box with a border and text; placement is the host's positioning primitive.
Toolkits with a native tooltip use it with the tokens for border and
background and record any radius they cannot remove.

#### Non-examples

A rounded bubble with an arrow. A tooltip with a shadow or a fade longer
than 150 ms. A tooltip as the only accessible name of an icon button. A
tooltip with a link or a button inside. One that opens on focus but not on
hover, or on hover with no delay. A title attribute. A tooltip that follows
the pointer.

### Dialog (`dialog`)

A native dialog on the overlay surface behind a 1px overlay border and the backdrop; modal when shown, with trapped focus and Escape to close. Family: feedback. Priority: R1. Maturity: stable. States: default, open, closed, focus-trapped, hover, focus-visible, reduced-motion.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| backdrop.bg | `color.surface.backdrop` | `rgb(0 0 0 / 65%)` | use-and-report (D-008) |
| root.bg | `color.surface.overlay` | `#241010` | use |
| root.border | `color.border.overlay` | `#e53935` | use-and-report (D-008) |
| root.ring | `color.interaction.focus.ring-container` | `#ffa2a7` | use |
| root.z | `z.dialog` | `30` | use |
| title.text | `color.text.bright` | `#ffa2a7` | use |
| text.text | `color.text.default` | `#e99499` | use |
| close.text | `color.action.tertiary.text` | `#e99499` | use |
| close.bg-hover | `color.action.tertiary.hover-bg` | `#1c0a09` | use |
| close.ring | `color.interaction.focus.ring` | `#e53935` | use |
| button.text | `color.action.secondary.text` | `#ffa2a7` | use |
| button.border | `color.action.secondary.border` | `#a3676b` | use |
| button.bg-hover | `color.action.secondary.hover-bg` | `#630f0d` | use |
| primary.bg | `color.action.primary.bg` | `#7d1310` | use |
| primary.text | `color.action.primary.text` | `#f4eeee` | use |
| primary.bg-hover | `color.action.primary.hover-bg` | `#911410` | use |
| destructive.bg | `color.action.destructive.filled-bg` | `#dc282e` | use |
| destructive.text | `color.action.destructive.filled-text` | `#f9faf9` | use |
| destructive.bg-hover | `color.action.destructive.hover-bg` | `#dc282e` | use |
| field.bg | `color.surface.input` | `#000000` | use |
| field.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| field.label | `color.text.bright` | `#ffa2a7` | use |
| note.text | `color.text.muted` | `#bd787d` | use |

#### Purpose

A question or a small task that must be answered before the page continues:
confirm a destructive action, name a new profile, sign in again. It takes
the whole screen's attention, so it is short, has a clear title, and always
offers a way out. The demonstration renders the dialog open in flow, with a
wrapper standing in for the backdrop; a host always uses `showModal()`.

#### Anatomy

The backdrop, {color.surface.backdrop} over the page; the dialog on
{color.surface.overlay} behind a 1px {color.border.overlay}; the header with
the title in {color.text.bright} and Close; the body in {color.text.default};
the actions row. Confirmation semantics do not prescribe action tone (D-015):
ordinary confirmations use primary. A destructive confirmation uses the filled destructive button,
{color.action.destructive.filled-bg} with {color.action.destructive.filled-text};
the form dialog holds a labelled text field on {color.surface.input}.

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | the dialog open on its backdrop | `open`; the page behind is inert |
| open | as default; opacity 0 → 1 within 150 ms on capable hosts | `open`; focus inside the dialog |
| closed | nothing: the dialog and its backdrop are absent | no `open`; focus back on the opener |
| focus-trapped | the dialog itself carries the container ring, 2px solid {color.interaction.focus.ring-container} at −3px, when it holds focus; the note says Tab cycles | Tab never leaves; the note text |
| hover | Close background → {color.action.tertiary.hover-bg}; other buttons take their tone's hover | cursor: pointer |
| focus-visible | ring 1px dashed {color.interaction.focus.ring} at −2px on the focused control; primary uses {color.interaction.focus.ring-container}, destructive uses {color.action.destructive.filled-text} (D-015) | the ring |
| reduced-motion | no opacity transition; the dialog is simply present or absent | `prefers-reduced-motion` |

A dialog is never disabled; a busy dialog disables its confirm button and
shows the ⋯ glyph on it.

#### Keyboard

Tab and Shift+Tab cycle inside the dialog; nothing behind it is reachable.
Escape closes and cancels, and focus returns to the opener. Enter submits a
form dialog; elsewhere it only activates the focused button, so a
destructive confirm is never triggered by Enter on the dialog itself. On
open, focus goes to the first focusable element, or to the dialog when the
first element is destructive; on close, back to the opener.

#### Accessibility

The dialog is named by its title through `aria-labelledby` and described by
its first paragraph through `aria-describedby`. Confirmations use
`role="alertdialog"`. `showModal()` makes the rest of the page inert and
supplies the top layer; the theme never re-implements the trap on a
non-modal dialog. The backdrop is decorative and not a click target for
cancel unless Close is also present. Contrast: text 7.92:1 and title 9.49:1
on the overlay surface, the overlay border 4.30:1, secondary buttons 6.84:1
on hover, destructive confirm text 4.58:1 on its fill, the container ring
9.49:1.

#### Portability

A box, a border, a dimmed backdrop and the button tones. Toolkits with a
native modal window use it with the overlay border on the frame and record
that the backdrop is the host's. Toolkits without a top layer draw the
backdrop as a full-window overlay under the dialog.

#### Non-examples

A glowing or shadowed card. Rounded corners. A dialog that slides or scales
in. `<dialog>` shown with `show()` and a hand-written focus trap. A dialog
with no title, or with the title only in the accessible name. A destructive
confirm as the default Enter action. A backdrop that closes the dialog with
no Close button. A dialog stacked on a dialog.

### Drawer (`drawer`)

A full-height modal panel anchored to the start or end edge; a native dialog on the overlay surface with a 1px overlay border on its inner edge. Family: feedback. Priority: R1. Maturity: stable. States: default, open, closed, focus-trapped, reduced-motion.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| backdrop.bg | `color.surface.backdrop` | `rgb(0 0 0 / 65%)` | use-and-report (D-008) |
| root.bg | `color.surface.overlay` | `#241010` | use |
| root.border | `color.border.overlay` | `#e53935` | use-and-report (D-008) |
| root.ring | `color.interaction.focus.ring-container` | `#ffa2a7` | use |
| root.z | `z.drawer` | `20` | use |
| title.text | `color.text.bright` | `#ffa2a7` | use |
| text.text | `color.text.default` | `#e99499` | use |
| body.divider | `color.border.divider` | `#2b0e0d` | use-and-report (D-008) |
| item.text | `color.text.default` | `#e99499` | use |
| item.bg-hover | `color.interaction.hover.bg-strong` | `#630f0d` | use |
| item.text-hover | `color.text.link-hover` | `#f4eeee` | use |
| close.text | `color.action.tertiary.text` | `#e99499` | use |
| close.bg-hover | `color.action.tertiary.hover-bg` | `#1c0a09` | use |
| close.ring | `color.interaction.focus.ring` | `#e53935` | use |
| note.text | `color.text.muted` | `#bd787d` | use |

#### Purpose

A panel that opens from the side to hold navigation or a task on narrow
screens, or a secondary form on wide ones. It is a modal dialog with a
different shape: full height, anchored to the start or end edge, with a
single visible border on the edge that faces the page. Below 900px the
sidebar collapses into a drawer; above it, a drawer holds tasks that need
more room than a dialog.

#### Anatomy

The backdrop, {color.surface.backdrop} over the page; the panel on
{color.surface.overlay} with a 1px {color.border.overlay} on its inner edge;
the header with the title in {color.text.bright} and Close; the body, rows
divided by {color.border.divider}. The demonstration renders the panel inside
a stage of fixed height standing in for the viewport.

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | the panel open at its edge on the backdrop | `open`; the page behind is inert |
| open | as default; opacity 0 → 1 within 150 ms on capable hosts; never a slide | `open`; focus inside the panel |
| closed | nothing: the panel and the backdrop are absent | no `open`; focus back on the opener |
| focus-trapped | the panel carries the container ring, 2px solid {color.interaction.focus.ring-container} at −3px, when it holds focus; the note says Tab cycles | Tab never leaves; the note text |
| reduced-motion | no opacity transition; the panel is simply present or absent | `prefers-reduced-motion` |

Items inside the drawer take the states of the list or navigation they
belong to; Close takes the tertiary button's hover and ring.

#### Keyboard

Tab and Shift+Tab cycle inside the panel; the page behind is inert. Escape
closes and returns focus to the opener. Enter or Space on a navigation item
navigates and closes. On open, focus goes to the first focusable element,
which is the first item or Close.

#### Accessibility

The panel is named by its title through `aria-labelledby`. `showModal()`
supplies the top layer and inertness; the theme never re-implements the trap
on a non-modal element or on a plain aside. The backdrop is decorative; a
click on it may close the drawer only because Close is also present. Both
edges use logical properties, so the panel and its border mirror in RTL with
no extra rules. Contrast: text 7.92:1 and title 9.49:1 on the overlay
surface, the border 4.30:1, the ring 4.30:1, hovered items 11.41:1 on the
strong hover fill.

#### Portability

A full-height box with one border and a dimmed backdrop. Toolkits without a
top layer draw the backdrop as a full-window overlay under the panel;
toolkits with a native sidebar keep it non-modal and record that it is a
sidebar, not a drawer.

#### Non-examples

A panel that slides in from the edge. A drawer with a shadow or a rounded
inner corner. A drawer that is a plain aside with a hand-written focus trap.
A bottom sheet with a drag handle. A drawer with no Close button. A drawer
that stays open under a dialog. A translucent panel over page content.

### Empty state (`empty-state`)

What a container shows when it has nothing to list; a glyph, a heading in the bright text colour, one sentence in the muted colour and an optional secondary action. Family: feedback. Priority: R1. Maturity: stable. States: default.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.default` | `#100c0c` | use |
| root.border | `color.border.divider` | `#2b0e0d` | use-and-report (D-008) |
| glyph.text | `color.icon.decorative` | `#a3676b` | use |
| heading.text | `color.text.bright` | `#ffa2a7` | use |
| text.text | `color.text.muted` | `#bd787d` | use |
| action.text | `color.action.secondary.text` | `#ffa2a7` | use |
| action.border | `color.action.secondary.border` | `#a3676b` | use |
| action.bg-hover | `color.action.secondary.hover-bg` | `#630f0d` | use |
| action.ring | `color.interaction.focus.ring` | `#e53935` | use |

#### Purpose

A region with nothing in it says so, and says what would fill it. It
replaces a blank pane, an empty table body or a list with no rows after a
filter, and it offers the one action that changes that when there is one.
A failure is the error state; a wait is the loading indicator.

#### Anatomy

The block on {color.surface.default} inside a 1px {color.border.divider},
content centred; the glyph in {color.icon.decorative}; the heading in
{color.text.bright}; the sentence in {color.text.muted}; the optional
secondary action in {color.action.secondary.text} with a 1px
{color.action.secondary.border}.

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | glyph, heading and sentence centred; the action, when present, below them | the worded heading; the action is a real button |

The block has no states of its own; the action takes the secondary
button's hover and ring.

#### Keyboard

Only the action is in the tab order. When a filter empties a list that had
focus inside it, focus moves to the heading of the region, not into the
empty state, so the person hears where they are.

#### Accessibility

The heading is a real heading at the level the region uses, so the empty
state appears in the outline; the sentence follows it in the reading order.
The glyph is `aria-hidden`. When content becomes empty after an action
(filter, delete), a polite live region announces the heading text. The
action is never the only route: filters can also be cleared where they were
set. Contrast: heading 10.10:1, sentence 5.66:1, action text 10.10:1 and its
border 4.33:1 on the panel surface.

#### Portability

Text and one optional button. Toolkits with a status page use it with the
glyph as text; toolkits with only a placeholder string add the action as a
separate button below the view.

#### Non-examples

An illustration or mascot. A primary (filled) call to action. A blank pane.
A greyed-out table header with no message. A message that only says "No
data". A heading in the accent red. An empty state that appears while
loading. Centred text in a rounded card with a shadow.

### Skeleton (`skeleton`)

Static layout placeholders with a named loading status. Family: feedback. Priority: R2. Maturity: stable. States: default, loading, reduced-motion.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.default` | `#100c0c` | use |
| root.text | `color.text.default` | `#e99499` | use |
| root.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| heading.text | `color.text.bright` | `#ffa2a7` | use |
| secondary.text | `color.text.muted` | `#bd787d` | use |
| control.bg | `color.surface.input` | `#000000` | use |
| control.focus | `color.interaction.focus.ring` | `#e53935` | use |
| dependency.role-border-width-default | `border.width.default` | `1px` | use |
| dependency.role-color-border-divider | `color.border.divider` | `#2b0e0d` | use-and-report (D-008) |
| dependency.role-color-surface-raised | `color.surface.raised` | `#160b0b` | use |
| dependency.role-radius-none | `radius.none` | `0px` | use |
| dependency.role-space-12 | `space.12` | `12px` | use |
| dependency.role-space-16 | `space.16` | `16px` | use |
| dependency.role-space-2 | `space.2` | `2px` | use |

#### Purpose

Reserve approximate layout space while a named region loads. Decorative blocks are hidden from accessibility APIs. A separate status describes loading. The skeleton is static and never shimmers.

#### Anatomy

- **status:** Text names what is loading.
- **blocks:** Decorative square placeholders retain approximate dimensions.

#### States

| State | Presentation | Non-colour channel |
| --- | --- | --- |
| default | Static decorative blocks. | Named loading status. |
| loading | Raised surface blocks; no opacity fade. | aria-busy on the region. |
| reduced-motion | Same static presentation. | No information depends on movement. |

#### Keyboard

- **Tab:** No new tab stops or focus capture.

#### Accessibility

Apply aria-busy to the region being updated and provide a concise status. Decorative blocks are aria-hidden. Do not remove the focused control merely to show a skeleton. Replacement content must keep a sensible reading order.

#### Portability

Wrap labels and actions without changing source order. Use logical spacing in RTL. Preserve native target sizes at both densities; only bounded data regions may scroll horizontally. Use equivalent native semantics and approved roles; record any unsupported behavior as a mapping deviation. The host owns application data, persistence, permissions and services.

#### Non-examples

Unlabelled controls, color-only status, drag-only operations, hidden required instructions, stolen focus or an invented service success.

### Popover (`popover`)

A non-modal supporting panel with light dismissal and ordinary tab order. Family: feedback. Priority: R2. Maturity: stable. States: default, open, closed, focus-visible.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.default` | `#100c0c` | use |
| root.text | `color.text.default` | `#e99499` | use |
| root.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| heading.text | `color.text.bright` | `#ffa2a7` | use |
| secondary.text | `color.text.muted` | `#bd787d` | use |
| control.bg | `color.surface.input` | `#000000` | use |
| control.focus | `color.interaction.focus.ring` | `#e53935` | use |
| dependency.role-border-width-default | `border.width.default` | `1px` | use |
| dependency.role-color-border-overlay | `color.border.overlay` | `#e53935` | use-and-report (D-008) |
| dependency.role-color-surface-overlay | `color.surface.overlay` | `#241010` | use |
| dependency.role-density-comfortable-control-height | `density.comfortable.control-height` | `32px` | use |
| dependency.role-density-compact-control-height | `density.compact.control-height` | `24px` | use |
| dependency.role-radius-none | `radius.none` | `0px` | use |
| dependency.role-space-12 | `space.12` | `12px` | use |
| dependency.role-space-16 | `space.16` | `16px` | use |
| dependency.role-space-2 | `space.2` | `2px` | use |
| dependency.role-space-8 | `space.8` | `8px` | use |

#### Purpose

Disclose a non-modal supporting panel near its trigger. Interactive content follows ordinary tab order. Escape and outside activation dismiss; focus is not trapped. Use the native Popover API when available with an equivalent documented fallback.

#### Anatomy

- **trigger:** Native button with expanded state and panel relationship.
- **panel:** Named supporting content and optional visible close action.

#### States

| State | Presentation | Non-colour channel |
| --- | --- | --- |
| default | Named trigger. | Button semantics and accessible name. |
| open | Square overlay surface and border. | aria-expanded=true; panel is reachable. |
| closed | Panel hidden. | aria-expanded=false; hidden controls leave focus order. |
| focus-visible | Dashed focus ring. | Native keyboard focus. |

#### Keyboard

- **Enter / Space:** Toggle from the trigger.
- **Tab / Shift+Tab:** Move through ordinary controls without trapping focus.
- **Escape:** Close and restore the trigger.

#### Accessibility

Keep essential instructions outside collapsed panels. Use a visible label or aria-label for the panel. Do not add aria-modal or a focus trap. Native automatic dismissal and the fallback both synchronize expanded state.

#### Portability

Wrap labels and actions without changing source order. Use logical spacing in RTL. Preserve native target sizes at both densities; only bounded data regions may scroll horizontally. Use equivalent native semantics and approved roles; record any unsupported behavior as a mapping deviation. The host owns application data, persistence, permissions and services.

#### Non-examples

Unlabelled controls, color-only status, drag-only operations, hidden required instructions, stolen focus or an invented service success.

### Error state (`error-state`)

A specific failure explanation with an explicit recovery request. Family: feedback. Priority: R2. Maturity: stable. States: default, error, loading, disabled, focus-visible.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.default` | `#100c0c` | use |
| root.text | `color.text.default` | `#e99499` | use |
| root.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| heading.text | `color.text.bright` | `#ffa2a7` | use |
| secondary.text | `color.text.muted` | `#bd787d` | use |
| control.bg | `color.surface.input` | `#000000` | use |
| control.focus | `color.interaction.focus.ring` | `#e53935` | use |
| dependency.role-border-width-default | `border.width.default` | `1px` | use |
| dependency.role-border-width-emphasis | `border.width.emphasis` | `2px` | use |
| dependency.role-color-border-disabled | `color.border.disabled` | `#7d1310` | use |
| dependency.role-color-interaction-disabled-bg | `color.interaction.disabled.bg` | `#160b0b` | use |
| dependency.role-color-status-danger-border | `color.status.danger.border` | `#e53935` | use |
| dependency.role-color-status-danger-text | `color.status.danger.text` | `#f73f35` | use |
| dependency.role-color-text-disabled | `color.text.disabled` | `#8a5559` | use |
| dependency.role-density-comfortable-control-height | `density.comfortable.control-height` | `32px` | use |
| dependency.role-density-compact-control-height | `density.compact.control-height` | `24px` | use |
| dependency.role-radius-none | `radius.none` | `0px` | use |
| dependency.role-space-12 | `space.12` | `12px` | use |
| dependency.role-space-2 | `space.2` | `2px` | use |
| dependency.role-space-8 | `space.8` | `8px` | use |

#### Purpose

Explain a failed operation and offer a bounded recovery action. Retrying emits a request; the component does not claim that a service recovered. The host updates content when the actual operation completes.

#### Anatomy

- **message:** Heading, explanation and text error glyph.
- **recovery:** Native retry action, disabled while the host request is pending.

#### States

| State | Presentation | Non-colour channel |
| --- | --- | --- |
| default | Danger border and text heading. | Specific written explanation and error glyph. |
| error | Same explicit failure treatment. | Failure text remains available. |
| loading | Busy status accompanies the existing explanation. | aria-busy and host-provided pending text. |
| disabled | Disabled roles at full opacity. | Native retry button is disabled. |
| focus-visible | Dashed ring. | Native keyboard focus. |

#### Keyboard

- **Tab / Shift+Tab:** Reach the recovery action.
- **Enter / Space:** Request a retry without moving focus.

#### Accessibility

Do not rely only on color or a glyph. Give the retry action a specific name. Announce a newly occurring error in an appropriate host status region, without repeatedly interrupting reading. Keep focus unless recovery requires an explicit next step.

#### Portability

Wrap labels and actions without changing source order. Use logical spacing in RTL. Preserve native target sizes at both densities; only bounded data regions may scroll horizontally. Use equivalent native semantics and approved roles; record any unsupported behavior as a mapping deviation. The host owns application data, persistence, permissions and services.

#### Non-examples

Unlabelled controls, color-only status, drag-only operations, hidden required instructions, stolen focus or an invented service success.

## Developer interfaces

Editor syntax, gutters, caret, selections, search, diagnostics, diffs, terminal text and the historical ANSI slots.

### Code editor (`code-editor`)

The editor surface, gutter, caret, current line, selection, bracket match and monochrome syntax roles that every code-editing or code-viewing host maps onto. Family: developer. Priority: R1. Maturity: stable. States: default, focus-visible, selected, selected+container-inactive, current, read-only.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.code.bg` | `#0c0909` | use |
| root.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| root.ring | `color.interaction.focus.ring-container` | `#ffa2a7` | use |
| gutter.bg | `color.code.gutter-bg` | `#0c0909` | use |
| gutter.rule | `color.code.gutter-rule` | `#2b0e0d` | use |
| gutter.bg-read-only | `color.surface.sunken` | `#000000` | use |
| line-number.text | `color.code.line-number` | `#ad7175` | use |
| line-number.text-active | `color.code.line-number-active` | `#e99499` | use |
| line.bg-current | `color.code.current-line` | `#1c0a09` | use |
| caret.bg | `color.code.caret` | `#e99499` | use |
| selection.bg | `color.code.selection-bg` | `#420f0c` | use |
| selection.bg-inactive | `color.interaction.selection.inactive-bg` | `#420f0c` | use |
| selection.text-inactive | `color.interaction.selection.inactive-text` | `#e99499` | use |
| bracket.outline | `color.code.bracket-match` | `#e53935` | use |
| indent.guide | `color.code.indent-guide` | `#531310` | use |
| whitespace.text | `color.code.whitespace` | `#7d1310` | use |
| fold.text | `color.text.muted` | `#bd787d` | use |
| fold.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| syntax.keyword | `color.code.syntax.keyword` | `#f73f35` | use |
| syntax.string | `color.code.syntax.string` | `#bd787d` | use |
| syntax.comment | `color.code.syntax.comment` | `#ad7175` | use |
| syntax.number | `color.code.syntax.number` | `#d4868b` | use |
| syntax.constant | `color.code.syntax.constant` | `#d4868b` | use |
| syntax.function | `color.code.syntax.function` | `#ffa2a7` | use |
| syntax.variable | `color.code.syntax.variable` | `#e99499` | use |
| syntax.type | `color.code.syntax.type` | `#b37175` | use |
| syntax.operator | `color.code.syntax.operator` | `#e99499` | use |
| syntax.punctuation | `color.code.syntax.punctuation` | `#e99499` | use |
| syntax.property | `color.code.syntax.property` | `#e53935` | use |
| syntax.heading | `color.code.syntax.heading` | `#e53935` | use |
| syntax.tag | `color.code.syntax.tag` | `#f73f35` | use |
| syntax.attribute | `color.code.syntax.attribute` | `#d4868b` | use |
| syntax.escape | `color.code.syntax.escape` | `#d4868b` | use |
| syntax.invalid | `color.code.syntax.invalid` | `#e53935` | use |
| syntax.deprecated | `color.code.syntax.deprecated` | `#ad7175` | use |

#### Purpose

The reference rendering of source code: the surface, gutter, caret, current
line, selection, bracket match, indent guides, rendered whitespace, fold
markers and the seventeen monochrome syntax roles. It is the contract a
real editor's theme API is mapped onto; this component never edits.

#### Anatomy

Root `.code-editor`; parts `.code-editor-<part>`: `-pre` (the `<pre>`),
`-line`, `-ln` (line number), `-text`, `-indent`, `-ws` (rendered
whitespace), `-caret`, `-sel` (selection range), `-bracket`, `-fold`, and
the gutter fold glyph `-fold-gutter`. Syntax spans are `.code-editor-<role>`:
`-kw` keyword, `-str` string, `-cm` comment, `-num` number, `-const`
constant, `-fn` function, `-var` variable, `-type` type, `-op` operator,
`-punct` punctuation, `-prop` property, `-heading` heading, `-tag` tag,
`-attr` attribute, `-esc` escape, `-invalid` invalid, `-deprecated`
deprecated. Line numbers are {color.code.line-number} on
{color.code.gutter-bg}, separated from the text by a 1px
{color.code.gutter-rule}; indent guides are 1px {color.code.indent-guide};
rendered whitespace is {color.code.whitespace}. Every colour is a role
variable, so the profile switch on the specimen recolours the sample without
touching the markup.

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | code on {color.code.bg}; comments italic; invalid text wavy-underlined in {color.code.syntax.invalid}; deprecated text struck through | italic, wavy underline, line-through |
| focus-visible | container ring 2px {color.interaction.focus.ring-container} at −3px; a 1px {color.code.caret} bar; the caret line filled {color.code.current-line} across gutter and text; its number in {color.code.line-number-active} | the ring; the caret bar |
| selected | the range filled {color.code.selection-bg}; syntax colours kept | the range is announced through the host's selection; the fill spans line ends |
| selected+container-inactive | the same range in {color.interaction.selection.inactive-bg} with {color.interaction.selection.inactive-text}; no caret, no ring | lightness drop between the two fills |
| current | both characters of the matched pair outlined 1px solid {color.code.bracket-match} | the outline is a box, not a colour change |
| read-only | gutter drops to {color.surface.sunken} behind the same line numbers; the gutter rule becomes dotted; no caret is drawn | `aria-readonly`; dotted rule; missing caret |

Precedence: selection is drawn under the text, the current line under the
selection, the bracket outline and the caret above everything. The caret
never blinks in the reference; a host that blinks it keeps the user's
cursor-blink setting and stops under reduced motion.

#### Keyboard

The host editor owns every binding. The theme states only what it draws in
response: the caret and current line while focused, the selection fill for
the host's selection, the outline for the host's bracket match. The reference
markup is one Tab stop and, being read-only, consumes no key. A host that
captures Tab for indentation must offer an escape route (Escape, then Tab) and
announce it; the theme does not provide one.

#### Accessibility

Syntax colour is decoration: no meaning is carried by colour alone. Invalid
text has the wavy underline, deprecated text the line-through, comments the
italic. Selection is a fill and focus is a ring, drawn together and never
substituted for one another. Every syntax role reaches 4.5:1 on
{color.code.bg} (the lowest is {color.code.syntax.property} at 4.69:1) and
{color.text.default} reaches 7.02:1 inside the selection fill. Comment,
type, keyword and property text drop below 4.5:1 inside
{color.code.selection-bg} (3.81–4.38:1); this is a recorded limitation of the
selection value and is not to be fixed by recolouring the tokens locally. Line
numbers are metadata people read and use {color.text.subtle}, not a graphic
colour; indent guides, rendered whitespace and the gutter rule are decorative.
In forced-colours mode the caret, selection and bracket outline take the
system `Highlight`, and the syntax spans keep their text styles.

#### Portability

A real editor is a host surface: CodeMirror, Monaco, GtkSourceView,
KSyntaxHighlighting, an `.icls` scheme or a terminal editor each expose a
theme API, and the port maps the roles here onto that API without touching
caret behaviour, keyboard handling or selection semantics. Where a host has no
bracket-match outline it may use the bold weight and records the deviation;
where a host cannot draw a 1px caret it uses its own caret in
{color.code.caret}. The `heritage-ansi` and `extended` profiles change only
role values; the mapping does not change.

#### Non-examples

Rainbow syntax in the `default` profile (the three extension hues are
proposed only in `extended`). A blinking or animated caret in the reference.
A selection that recolours the text to white and loses the syntax roles. A
current-line highlight brighter than the selection. A bracket match drawn as
a background fill, which reads as a selection. Line numbers in the decorative
`#7d1310`. A glow, gradient or rounded corner on the editor box. Removing the
host's focus indicator without drawing the container ring.

### Diagnostics (`diagnostics`)

Inline problem markers in code (underline patterns, an error line and an overview stripe) and the problems panel that lists them as a listbox, each severity carrying its glyph. Family: developer. Priority: R1. Maturity: stable. States: default, hover, focus-visible, selected, selected+focus-visible, error, empty.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.default` | `#100c0c` | use |
| root.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| code.bg | `color.code.bg` | `#0c0909` | use |
| code.text | `color.text.default` | `#e99499` | use |
| code.line-number | `color.code.line-number` | `#ad7175` | use |
| code.gutter-rule | `color.code.gutter-rule` | `#2b0e0d` | use |
| code.selection-bg | `color.code.selection-bg` | `#420f0c` | use |
| error.underline | `color.diagnostic.error.underline` | `#e53935` | use |
| error.text | `color.diagnostic.error.text` | `#f73f35` | use |
| error.stripe | `color.diagnostic.error.stripe` | `#e53935` | use |
| error.line-bg | `color.diagnostic.error.bg` | `#2b0e0d` | use |
| warning.underline | `color.diagnostic.warning.underline` | `#c9973f` | use |
| warning.text | `color.diagnostic.warning.text` | `#c9973f` | use |
| warning.stripe | `color.diagnostic.warning.stripe` | `#c9973f` | use |
| info.underline | `color.diagnostic.info.underline` | `#7e9ebb` | use |
| info.text | `color.diagnostic.info.text` | `#7e9ebb` | use |
| hint.underline | `color.diagnostic.hint.underline` | `#ad7175` | use |
| hint.text | `color.text.subtle` | `#ad7175` | use |
| unused.text | `color.diagnostic.unused.text` | `#bd787d` | use |
| deprecated.text | `color.code.syntax.deprecated` | `#ad7175` | use |
| row.text | `color.text.default` | `#e99499` | use |
| row.location | `color.text.muted` | `#bd787d` | use |
| row.divider | `color.border.divider` | `#2b0e0d` | use-and-report (D-008) |
| row.bg-hover | `color.interaction.hover.bg` | `#1c0a09` | use |
| row.ring | `color.interaction.focus.ring` | `#e53935` | use |
| row.bg-selected | `color.interaction.selection.bg` | `#531310` | use |
| row.text-selected | `color.interaction.selection.text` | `#f4eeee` | use |
| row.ring-selected | `color.interaction.focus.ring-container` | `#ffa2a7` | use |
| header.text | `color.text.bright` | `#ffa2a7` | use |
| status.text | `color.status.danger.text` | `#f73f35` | use |
| empty.text | `color.text.muted` | `#bd787d` | use |
| empty.glyph | `color.status.success.text` | `#86a46f` | use |

#### Purpose

How problems appear on code and in the list that indexes them. Six kinds are
distinguished by pattern and glyph as well as colour: error, warning, info,
hint, unused and deprecated. The panel is the keyboard route to every marker.

#### Anatomy

Root `.diagnostics` with the variant class `.diagnostics-inline` or
`.diagnostics-panel`. Inline parts: `.diagnostics-code` (the `<pre>`),
`-line`, `-ln`, `-text`, `-mark` (the stripe cell), and the ranges
`.diagnostics-error`, `-warning`, `-info`, `-hint`, `-unused`, `-deprecated`;
`.diagnostics-line-error` marks the row filled {color.diagnostic.error.bg};
`.diagnostics-target` is the range that belongs to the selected row. Panel
parts: `-header`, `-title`, `-count`, `-status`, `-list`, `-row`, `-glyph`
(with `-glyph-error` and so on), `-message`, `-loc`, `-rule`, `-empty`.
Patterns: error and warning wavy, info and hint dotted, unused dotted in
{color.diagnostic.unused.text}, deprecated line-through in
{color.code.syntax.deprecated}. Glyphs: `✕` error in
{color.diagnostic.error.text}, `!` warning in {color.diagnostic.warning.text},
`i` info in {color.diagnostic.info.text}, `·` hint in {color.text.subtle}.
The stripe mark repeats the glyph in the stripe colour
({color.diagnostic.error.stripe}, {color.diagnostic.warning.stripe}).

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | ranges decorated as above on {color.code.bg}; the error row filled {color.diagnostic.error.bg}; panel rows on {color.surface.default} with 1px {color.border.divider} between them | underline pattern; glyph |
| hover | panel row background → {color.interaction.hover.bg}; inline ranges unchanged (the hover tooltip is a host surface) | cursor: default on rows |
| focus-visible | the active row: 1px dashed {color.interaction.focus.ring} at −2px; inline box: the container ring 2px {color.interaction.focus.ring-container} at −3px | the ring |
| selected | row filled {color.interaction.selection.bg}, text and glyph {color.interaction.selection.text}; the matching inline range filled {color.code.selection-bg} under its underline | `aria-selected`; the glyph stays; the fill spans the row |
| selected+focus-visible | the selection fill and a 1px dashed ring in {color.interaction.focus.ring-container} | both at once |
| error | the provider failed: a `role=status` line with `✕` in {color.status.danger.text} above the stale rows; the inline stripe shows the same `✕` at its top | glyph; live announcement |
| empty | no problems: rows and stripe marks hidden; `✓ No problems` in {color.text.muted} with the glyph in {color.status.success.text}; inline decorations removed | glyph; the absence of decoration |

Precedence: selected > hover; focus-visible is drawn on top. A selected row
recolours its glyph to the on-selection text so the shape, not the hue,
carries the severity.

#### Keyboard

The panel follows the APG listbox pattern with `aria-activedescendant`:
the list is one Tab stop, Up and Down move the active row, Home and End jump,
type-ahead matches message text, Enter opens the location. Selection follows
focus. Inline markers add no keys; next-problem and previous-problem
navigation and the hover card are host bindings and host surfaces.

#### Accessibility

Never colour-only: every severity has its underline pattern in code and its
glyph in the panel, and the glyph is part of the option's accessible name
("Error: frmae is not defined, title.js line 6"). The error row background
is an addition to the wavy underline, not a replacement. Contrast:
{color.diagnostic.error.text} reaches 4.88:1 on the error row and 5.26:1 on
the panel; every underline reaches 3:1 on its actual background including
the selected range (the error underline measures 3.81:1 on the code
selection fill); {color.diagnostic.unused.text} reaches 5.81:1
so unused code is still readable. `aria-selected` and `aria-activedescendant`
carry the state; the status line is `role=status`, announced once. In
forced-colours mode the underline patterns persist because they are
`text-decoration`, not backgrounds.

#### Portability

Underline styles are native in Pango, Qt, JetBrains and most terminal
emulators; where wavy is unavailable the port uses a plain underline plus the
glyph and records the deviation. Hosts that draw their own overview ruler map
the stripe colours; hosts without one omit the stripe and keep the glyphs.
The listbox maps to any single-selection list widget.

#### Non-examples

Red-only errors without the `✕` glyph or the wavy pattern. A wavy underline
for every severity, which leaves the pattern meaningless. Purple or cyan for
info and hint. A hover on a panel row that changes only the text colour. A
selected row whose glyph keeps its severity colour on the red fill (2.47:1).
A panel row with a rounded highlight or a left accent bar instead of the fill.
Removing the error line background to "calm" the editor.

### Diff view (`diff-view`)

Unified and split diffs as native tables; added, removed and modified rows keep their textual gutter marker, word-level changes are underlines, conflicts are double borders. Family: developer. Priority: R1. Maturity: stable. States: default, hover, selected, focus-visible, expanded, collapsed.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.code.bg` | `#0c0909` | use |
| root.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| root.text | `color.text.default` | `#e99499` | use |
| line-number.text | `color.text.muted` | `#bd787d` | use |
| line-number.rule | `color.code.gutter-rule` | `#2b0e0d` | use |
| row.divider | `color.border.divider` | `#2b0e0d` | use-and-report (D-008) |
| added.bg | `color.diff.added.bg` | `#0f1a0e` | use |
| added.gutter | `color.diff.added.gutter` | `#86a46f` | use |
| added.emphasis | `color.diff.added.emphasis` | `#86a46f` | use |
| removed.bg | `color.diff.removed.bg` | `#2b0e0d` | use |
| removed.gutter | `color.diff.removed.gutter` | `#e53935` | use |
| removed.emphasis | `color.diff.removed.emphasis` | `#e53935` | use |
| modified.bg | `color.diff.modified.bg` | `#1f1a0c` | use |
| modified.gutter | `color.diff.modified.gutter` | `#c9973f` | use |
| hunk.bg | `color.diff.header-bg` | `#241010` | use |
| hunk.text | `color.text.muted` | `#bd787d` | use |
| hunk.context | `color.text.default` | `#e99499` | use |
| conflict.border | `color.diff.conflict-border` | `#f73f35` | use |
| conflict.marker | `color.text.bright` | `#ffa2a7` | use |
| fold.text | `color.text.link` | `#f73f35` | use |
| fold.bg-hover | `color.interaction.hover.bg-strong` | `#630f0d` | use |
| row.bg-hover | `color.interaction.hover.bg` | `#1c0a09` | use |
| row.ring | `color.interaction.focus.ring` | `#e53935` | use |
| row.bg-selected | `color.interaction.selection.bg` | `#531310` | use |
| row.text-selected | `color.interaction.selection.text` | `#f4eeee` | use |
| row.ring-selected | `color.interaction.focus.ring-container` | `#ffa2a7` | use |

#### Purpose

How changed text is shown: line rows with a fill and a textual marker,
word-level changes as underlines that leave text contrast untouched, hunk
headers, collapsible unchanged regions, and merge conflicts in a double
border. The `+`, `−` and `~` markers are content, present in every state and
every profile.

#### Anatomy

Root `.diff-view` (the table) with the variant class `.diff-view-unified`,
`-split`, `-word-level` or `-conflict`. Row classes: `.diff-view-row`,
`-row-context`, `-row-added`, `-row-removed`, `-row-modified`, `-row-hunk`,
`-row-fold`, `-row-conflict` with `-row-conflict-start`, `-row-conflict-mid`
and `-row-conflict-end` for the marker lines. Cells: `.diff-view-ln`,
`-marker`, `-text`, `-fold-cell`, and `-range` for the `@@` span; the split
variant adds `-side-old` and `-side-new` on cells and, on a `-row-pair`,
`-cell-removed` and `-cell-added` per side (an empty `-cell-blank` faces an
added line); the conflict variant adds `-row-ours` and `-row-theirs`. Word-level spans: `.diff-view-word-added` (2px underline in
{color.diff.added.emphasis}) and `.diff-view-word-removed` (underline plus
line-through in {color.diff.removed.emphasis}). The fold button is
`.diff-view-fold` with an inline SVG chevron `.diff-view-chevron` and two
labels, `-fold-collapsed` and `-fold-expanded`; the folded rows are a
`<tbody class="diff-view-folded" hidden>`. Line numbers
use {color.text.muted} rather than {color.code.line-number} because the
modified fill would drop the subtle step to 4.45:1.

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | added rows {color.diff.added.bg} with `+` in {color.diff.added.gutter}; removed {color.diff.removed.bg} with `−` in {color.diff.removed.gutter}; modified {color.diff.modified.bg} with `~` in {color.diff.modified.gutter}; hunk rows {color.diff.header-bg}; conflicts in a 3px double {color.diff.conflict-border} | the markers; the double border |
| hover | the row's number and marker cells → {color.interaction.hover.bg}; the text cell keeps its fill | cursor: default; only on hover-capable pointers |
| selected | the whole row filled {color.interaction.selection.bg} with {color.interaction.selection.text}; the marker stays | `aria-current` (or `:target` for a permalinked line); the marker glyph |
| focus-visible | 1px dashed {color.interaction.focus.ring} at −2px around the row; on a selected row the ring is {color.interaction.focus.ring-container} | the ring |
| expanded | the folded `<tbody>` is shown; the chevron points down; the button reads "Hide 12 unchanged lines" | `aria-expanded="true"`; the rows appear |
| collapsed | the folded `<tbody>` is hidden; the chevron points to the inline end; "… 12 unchanged lines" | `aria-expanded="false"`; the ellipsis |

Precedence: selected > hover; focus-visible is always drawn. A selected row
loses its diff fill but never its marker, which is why the marker is content.

#### Keyboard

A plain table has no row focus. The official web distribution places it in a
named, focusable scroll region so keyboard users can inspect wide lines without
page overflow. The region uses the canonical container focus ring. Within the
table, the fold buttons are the only stops and
toggle with Enter or Space, the chevron turning within the motion budget.
Hosts that expose the diff as a grid own row navigation and draw the ring on
the focused row; the theme adds no binding of its own.

#### Accessibility

The caption names both files. Every changed row carries its marker in a cell
of its own, so a screen reader announces `+` or `−` before the line and a
monochrome print keeps the distinction. Word-level changes use
`text-decoration` (underline, or underline and line-through), never a fill,
so the row text stays at 7.56:1 or better on every diff fill. The `−` marker
measures 4.24:1 on the removed fill: it is bold, sits on its own fill and is
a glyph, so it is held to the 3:1 graphics floor and recorded here. Folded
hunks use `aria-expanded` and `aria-controls` on a real button; the hidden
rows are `hidden`, not visually clipped, so they are not read while
collapsed. Conflict markers are the literal `<<<<<<<`, `=======` and
`>>>>>>>` lines in {color.text.bright} on {color.diff.header-bg}, inside the
double border.

#### Portability

A table with cell backgrounds, borders and text decoration is expressible in
every toolkit. Hosts without double borders draw two 1px lines and record it;
hosts without line-through keep the underline and the `−` marker. The three
extension hues are confined to the `diff` group here and change with the
profile; the markers do not.

#### Non-examples

Red and green fills with no `+`/`−` marker. Word-level changes drawn as a
bright fill that pushes the text below 4.5:1. A removed line rendered in
`text.disabled` or struck through as a whole. Collapsed hunks that are only
visually clipped and still read out. Conflict blocks in purple. Hover that
recolours the code text. Round-cornered hunk cards floating on the page. A
selected row that drops the marker because "the colour says it".

### Terminal (`terminal`)

Terminal text on the terminal surface with the sixteen heritage ANSI slots carried exactly, the cursor, the selection and the six text attributes; a transcript, the slot grid and the attributes. Family: developer. Priority: R1. Maturity: stable. States: default, focus-visible, selected, busy, current.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.terminal.bg` | `#0c0909` | use |
| root.text | `color.terminal.fg` | `#e99499` | use |
| root.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| root.ring | `color.interaction.focus.ring-container` | `#ffa2a7` | use |
| cursor.bg | `color.terminal.cursor` | `#e99499` | use |
| cursor.text | `color.terminal.bg` | `#0c0909` | use |
| selection.bg | `color.terminal.selection-bg` | `#911410` | use |
| selection.text | `color.terminal.selection-text` | `#f4eeee` | use |
| current.bg | `color.code.current-line` | `#1c0a09` | use |
| prompt.bg | `color.terminal.ansi.4` | `#8c1212` | use |
| prompt.text | `color.terminal.selection-text` | `#f4eeee` | use |
| prompt.branch-bg | `color.terminal.ansi.3` | `#d4868b` | use |
| prompt.branch-text | `color.terminal.bg` | `#0c0909` | use |
| dim.text | `color.text.muted` | `#bd787d` | use |
| inverse.bg | `color.terminal.fg` | `#e99499` | use |
| inverse.text | `color.terminal.bg` | `#0c0909` | use |
| label.text | `color.text.muted` | `#bd787d` | use |
| fail.glyph | `color.terminal.fg` | `#e99499` | use |
| slot.ansi-0 | `color.terminal.ansi.0` | `#0c0909` | use |
| slot.ansi-1 | `color.terminal.ansi.1` | `#c81a1a` | use |
| slot.ansi-2 | `color.terminal.ansi.2` | `#bd787d` | use |
| slot.ansi-3 | `color.terminal.ansi.3` | `#d4868b` | use |
| slot.ansi-4 | `color.terminal.ansi.4` | `#8c1212` | use |
| slot.ansi-5 | `color.terminal.ansi.5` | `#f73f35` | use |
| slot.ansi-6 | `color.terminal.ansi.6` | `#9e474a` | use |
| slot.ansi-7 | `color.terminal.ansi.7` | `#ffa2a7` | use |
| slot.ansi-8 | `color.terminal.ansi.8` | `#7d1310` | use |
| slot.ansi-9 | `color.terminal.ansi.9` | `#ab1612` | use |
| slot.ansi-10 | `color.terminal.ansi.10` | `#ad2721` | use |
| slot.ansi-11 | `color.terminal.ansi.11` | `#b37175` | use |
| slot.ansi-12 | `color.terminal.ansi.12` | `#871f19` | use |
| slot.ansi-13 | `color.terminal.ansi.13` | `#e82132` | use |
| slot.ansi-14 | `color.terminal.ansi.14` | `#e0292f` | use |
| slot.ansi-15 | `color.terminal.ansi.15` | `#a3676b` | use |

#### Purpose

The reference for terminal text: the surface, the default foreground, the
cursor, the selection, the six attributes and the sixteen slots the
workstation froze in `Xresources`. The slots are the origin of the theme and
are carried exactly; what a program does with a slot is that program's
choice, and the theme documents where a choice fails rather than quietly
fixing it.

#### Anatomy

Root `.terminal` (the `<pre>`) with a variant class `.terminal-transcript`,
`.terminal-ansi-grid` or `.terminal-attributes`. Parts `.terminal-<part>`:
`-line`, `-prompt` with `-prompt-path`, `-prompt-branch` and `-prompt-arrow`,
`-cursor`, `-sel`, `-label`, `-fail` (the `✕`), and the attributes `-bold`,
`-dim`, `-italic`, `-underline`, `-inverse`, `-strike`. Slot classes are
`.terminal-fg-0` … `.terminal-fg-15` and `.terminal-bg-0` … `.terminal-bg-15`,
each a single role variable {color.terminal.ansi.0} … {color.terminal.ansi.15},
so the profile switch on the specimen recolours the grid without touching
the markup. The foreground is {color.terminal.fg} on {color.terminal.bg};
the prompt path segment is {color.terminal.selection-text} on
{color.terminal.ansi.4} and the branch segment {color.terminal.bg} on
{color.terminal.ansi.3}, the two agnoster backgrounds the workstation used.

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | text on {color.terminal.bg}; the cursor cell hollow, a 1px {color.terminal.cursor} outline; no ring | the hollow cursor |
| focus-visible | container ring 2px {color.interaction.focus.ring-container} at −3px; the cursor solid {color.terminal.cursor} with {color.terminal.bg} text | the ring; the cursor fills |
| selected | the run filled {color.terminal.selection-bg} with {color.terminal.selection-text}; slot colours inside the run give way to the selection text | the fill spans whole cells; the emulator exposes the selection |
| busy | a command is running: the cursor is a solid static block at the end of the output; the transcript is `aria-busy` | `aria-busy`; the cursor shape; no blink |
| current | the cursor line filled {color.code.current-line} across its width; the cursor as in focus | the row fill; the cursor cell |

The cursor never blinks in the reference. A host keeps the user's own blink
setting and stops blinking under reduced motion; the theme never turns it on.

#### Keyboard

The emulator and the shell own every key, including Tab, Escape and the
selection modifiers. The reference transcript is a single focus stop that
consumes nothing. The theme draws the ring when the host reports focus, the
fill when the host reports a selection, and the block cursor where the host
puts it.

#### Accessibility

The transcript is `role=log` so new output is announced politely; the grid
and the attribute sheet are plain `<pre>` elements with a label. The sixteen
slots are exact in every profile: slots 2, 3, 5, 7 and 11 reach 4.5:1 on the
terminal background; slots 1, 4, 6, 8, 9, 10, 12, 13 and 14 do not
(1.86:1–4.43:1), and the grid marks each of them with a `✕` after its number
so the failure is visible, not implied. Two more are marked on the same
evidence: slot 15 measures 4.45:1 and slot 0 is the background itself. The
`extended` overlay reassigns the slots to a semantic sixteen that all reach
4.5:1; the site's profile control shows the grid under it. The background
row is legible only with a foreground the program chooses: with the default
foreground only slots 0 and 8 pass, which is why the row is shown and not
declared as a contract. Two escape families bypass the slots entirely and
are outside the theme: bold-as-bright, which substitutes slots 8–15 for 0–7
and must be off for the slots to hold, and 256-colour or 24-bit sequences,
which carry their own values. Attributes are attributes: dim uses
{color.text.muted} (5.81:1) rather than a slot, inverse swaps foreground and
background (8.65:1), and bold, italic, underline and strikethrough are text
styles a screen reader can expose.

#### Portability

Every emulator with a sixteen-slot palette can hold these values byte for
byte; the port's evidence records whether bold-as-bright is off and whether
the cursor and selection keys exist. Programs that pick slot 4 or 12 for
directories inherit the documented failure; a port may recommend `LS_COLORS`
or a prompt configuration but never changes the slots to compensate. The
cursor shape (block, bar, underline) is the user's setting; the theme only
supplies its colour.

#### Non-examples

Normalising the heritage slots so every one reaches 4.5:1 and calling it
`heritage-ansi`. A blinking cursor in the reference. A cursor that is a
glowing bar. Bold rendered as bright, which silently swaps the slots. A
purple or cyan slot borrowed from another scheme. Wrapping the transcript at
narrow widths and breaking the prompt segments. A selection drawn as a ring
instead of a fill. Rounded prompt segments or a gradient prompt.

### Editor search (`editor-search`)

Literal search and match navigation over escaped read-only source text. Family: developer. Priority: R2. Maturity: stable. States: default, filled, no-results, focus-visible.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.default` | `#100c0c` | use |
| root.text | `color.text.default` | `#e99499` | use |
| root.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| heading.text | `color.text.bright` | `#ffa2a7` | use |
| secondary.text | `color.text.muted` | `#bd787d` | use |
| control.bg | `color.surface.input` | `#000000` | use |
| control.focus | `color.interaction.focus.ring` | `#e53935` | use |
| dependency.role-border-width-default | `border.width.default` | `1px` | use |
| dependency.role-color-code-search-current-bg | `color.code.search-current-bg` | `#ffa2a7` | use |
| dependency.role-color-code-search-current-text | `color.code.search-current-text` | `#0c0909` | use |
| dependency.role-color-code-search-match-bg | `color.code.search-match-bg` | `#630f0d` | use |
| dependency.role-color-interaction-focus-ring-container | `color.interaction.focus.ring-container` | `#ffa2a7` | use |
| dependency.role-color-surface-code | `color.surface.code` | `#000000` | use |
| dependency.role-density-comfortable-control-height | `density.comfortable.control-height` | `32px` | use |
| dependency.role-density-compact-control-height | `density.compact.control-height` | `24px` | use |
| dependency.role-radius-none | `radius.none` | `0px` | use |
| dependency.role-space-12 | `space.12` | `12px` | use |
| dependency.role-space-2 | `space.2` | `2px` | use |
| dependency.role-space-8 | `space.8` | `8px` | use |

#### Purpose

Find literal text in a read-only code sample. Search, previous and next expose exact match counts and the current match. The component does not execute regexes, mutate source text or provide a code editor backend.

#### Anatomy

- **query:** Labelled search input.
- **navigation:** Previous and next actions, disabled without matches.
- **source:** Escaped text with native mark elements.
- **status:** Current and total match counts.

#### States

| State | Presentation | Non-colour channel |
| --- | --- | --- |
| default | Plain escaped source. | Readable preformatted text. |
| filled | Match fill; current match has its own marker. | Exact current and total counts. |
| no-results | No highlighted matches. | Explicit zero-results status. |
| focus-visible | Dashed control ring. | Native keyboard focus. |

#### Keyboard

- **Tab / Shift+Tab:** Reach query and match navigation.
- **Enter / Shift+Enter:** Move to next or previous match.
- **Escape:** Clear the query and retain focus.

#### Accessibility

Render source as text, never as trusted HTML from the consumer. Highlighted matches must retain the exact source text for copying. Announce counts without moving input focus. Match navigation scrolls the current mark into view, and reduced motion requires no animation.

#### Portability

Wrap labels and actions without changing source order. Use logical spacing in RTL. Preserve native target sizes at both densities; only bounded data regions may scroll horizontally. Use equivalent native semantics and approved roles; record any unsupported behavior as a mapping deviation. The host owns application data, persistence, permissions and services.

#### Non-examples

Unlabelled controls, color-only status, drag-only operations, hidden required instructions, stolen focus or an invented service success.

## Composed specimens

Settings panel, administrative form, filterable table, i3-inspired window frame; file browser, compact dashboard and form builder later.

### Settings panel (`settings-panel`)

A two-column settings screen composed from the sidebar-nav, fieldset, switch, select, radio-group, text-field, checkbox and button components; below 600px the sidebar becomes a tab strip. Family: composed. Priority: R1. Maturity: stable. States: default, focus-visible, invalid, busy.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.default` | `#100c0c` | use |
| root.border | `color.border.default` | `#531310` | use |
| nav.bg | `color.surface.default` | `#100c0c` | use |
| nav.border | `color.border.divider` | `#2b0e0d` | use-and-report (D-008) |
| nav.text | `color.text.default` | `#e99499` | use |
| nav.current-text | `color.text.bright` | `#ffa2a7` | use |
| nav.current-indicator | `color.border.selected-indicator` | `#e53935` | use |
| nav.bg-hover | `color.interaction.hover.bg` | `#1c0a09` | use |
| title.text | `color.text.bright` | `#ffa2a7` | use |
| lead.text | `color.text.muted` | `#bd787d` | use |
| group.border | `color.border.divider` | `#2b0e0d` | use-and-report (D-008) |
| group.legend | `color.text.bright` | `#ffa2a7` | use |
| control.bg | `color.surface.input` | `#000000` | use |
| control.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| control.text | `color.text.default` | `#e99499` | use |
| control.ring | `color.interaction.focus.ring` | `#e53935` | use |
| control.checked-bg | `color.action.primary.bg` | `#7d1310` | use |
| control.checked-glyph | `color.action.primary.text` | `#f4eeee` | use |
| invalid.border | `color.status.danger.border` | `#e53935` | use |
| invalid.message | `color.status.danger.text` | `#f73f35` | use |
| footer.border | `color.border.divider` | `#2b0e0d` | use-and-report (D-008) |
| apply.bg | `color.action.primary.bg` | `#7d1310` | use |
| apply.text | `color.action.primary.text` | `#f4eeee` | use |
| apply.busy-glyph | `color.action.primary.text` | `#f4eeee` | use |
| reset.text | `color.action.secondary.text` | `#ffa2a7` | use |
| reset.border | `color.action.secondary.border` | `#a3676b` | use |

#### Purpose

A settings screen that proves the form components compose without new
tokens: a section list on the left, one fieldset of preferences on the
right, and a footer of actions. It exists to show the pieces together under
every state and at every width, not to prescribe a product.

#### Anatomy

The region landmark; the sidebar-nav with the open section marked by
`aria-current` and the 2px {color.border.selected-indicator}; the form
landmark with its h2 in {color.text.bright} and lead in {color.text.muted};
one fieldset with a {color.border.divider} edge holding two switches (Hide
the bar, Smart gaps), a wallpaper select, the bar-label radio-group (中文 /
English), the inner-gap text-field with a `px` suffix and the bar-module
checkbox group; a footer rule and the Apply (primary) and Reset (secondary)
buttons. Every part is the component's own markup and classes.

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | rest tokens of every component on {color.surface.default}; the open section carries the indicator bar | `aria-current` |
| focus-visible | the dashed {color.interaction.focus.ring} on the control holding focus (shown on the first switch); the other controls stay at rest | the ring |
| invalid | the inner-gap field takes the 2px {color.status.danger.border} and shows its ✕ message; the other controls stay at rest | border width 1 → 2px; glyph; `aria-invalid` |
| busy | Apply shows the static ⋯ glyph in {color.action.primary.text}; the form carries `aria-busy`; nothing shrinks or animates | glyph; `aria-busy`; cursor: progress |

The composed components keep their own hover, checked, disabled and
read-only treatments; this specimen declares only the states the screen adds
to them.

#### Keyboard

Landmarks first: a screen reader's landmark list offers the region, the
navigation and the form by name. Tab walks the section links, then every
control in reading order, then Apply and Reset. The radio-group and the
select move with arrows inside one stop; the numeric field steps with Up and
Down; switches and checkboxes toggle with Space. Nothing traps focus and
nothing changes on hover alone.

#### Accessibility

The region is named by the h2 through `aria-labelledby`; the nav has its own
label so two navigation landmarks on a page stay distinguishable; the open
section is `aria-current="page"`. The form is named by the same h2 and
carries `aria-busy="true"` while Apply is in progress, so the pending state
is announced without a live region. Every control has a programmatic label;
every message is linked by `aria-describedby`. Contrast: title 10.10:1 and
lead 5.66:1 on the panel surface, control boundaries 4.33:1, the indicator
bar 4.57:1, the danger border 4.69:1 on the input surface.

#### Portability

Three landmarks, native controls and a container query; nothing here needs a
script. A host without container queries collapses the sidebar at the 600px
viewport breakpoint. The 56rem reference width, the sidebar width and the
two-column grid belong to this specimen and are not rules; the specimen
proves composition, not a product. Toolkits port each contained component
through its own mapping and place them with their native layout.

#### Non-examples

A rounded, shadowed settings card. A Material-style app bar above the form.
Toggle switches with rounded pills or animated thumbs. Section links drawn
as filled tabs instead of the indicator bar. A red form background to show
that something is invalid. Applying while the controls are dimmed with
opacity. A spinner replacing the Apply label.

### Admin form (`admin-form`)

A create-or-edit record screen composed from breadcrumbs, a wizard step list, alerts, six field components and a button row; the review step is a table with an Edit button per row. Family: composed. Priority: R1. Maturity: stable. States: default, invalid, busy, disabled.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.default` | `#100c0c` | use |
| root.border | `color.border.default` | `#531310` | use |
| crumbs.link | `color.text.link` | `#f73f35` | use |
| crumbs.current | `color.text.bright` | `#ffa2a7` | use |
| title.text | `color.text.bright` | `#ffa2a7` | use |
| step.text | `color.text.default` | `#e99499` | use |
| step.current-text | `color.text.bright` | `#ffa2a7` | use |
| step.current-indicator | `color.border.selected-indicator` | `#e53935` | use |
| step.complete | `color.status.success.text` | `#86a46f` | use |
| step.invalid | `color.status.danger.text` | `#f73f35` | use |
| notice.tint | `color.status.info.tint` | `#0f141c` | use |
| notice.border | `color.status.info.border` | `#7e9ebb` | use |
| notice.text | `color.status.info.text` | `#7e9ebb` | use |
| summary.tint | `color.status.danger.tint` | `#2b0e0d` | use |
| summary.border | `color.status.danger.border` | `#e53935` | use |
| summary.text | `color.status.danger.text` | `#f73f35` | use |
| field.bg | `color.surface.input` | `#000000` | use |
| field.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| field.text | `color.text.default` | `#e99499` | use |
| field.label | `color.text.bright` | `#ffa2a7` | use |
| field.help | `color.text.muted` | `#bd787d` | use |
| field.affix | `color.text.muted` | `#bd787d` | use |
| invalid.border | `color.status.danger.border` | `#e53935` | use |
| invalid.message | `color.status.danger.text` | `#f73f35` | use |
| disabled.bg | `color.interaction.disabled.bg` | `#160b0b` | use |
| disabled.text | `color.text.disabled` | `#8a5559` | use |
| disabled.border | `color.border.disabled` | `#7d1310` | use |
| review.key | `color.text.muted` | `#bd787d` | use |
| review.header-rule | `color.border.strong` | `#9e231f` | use |
| review.row-rule | `color.border.divider` | `#2b0e0d` | use-and-report (D-008) |
| footer.border | `color.border.divider` | `#2b0e0d` | use-and-report (D-008) |
| continue.bg | `color.action.primary.bg` | `#7d1310` | use |
| continue.text | `color.action.primary.text` | `#f4eeee` | use |
| continue.busy-glyph | `color.action.primary.text` | `#f4eeee` | use |
| back.text | `color.action.secondary.text` | `#ffa2a7` | use |
| back.border | `color.action.secondary.border` | `#a3676b` | use |
| draft.text | `color.action.tertiary.text` | `#e99499` | use |
| draft.bg-hover | `color.action.tertiary.hover-bg` | `#1c0a09` | use |

#### Purpose

A record screen that proves the advanced form components compose with the
navigation and feedback components: a trail, a heading, a step list, a
notice, a grid of fields and a row of actions, then the same screen at its
review step. It exists to show validation, saving and inertness across a
whole form, not to prescribe a product.

#### Anatomy

The region landmark; the breadcrumbs (Records / Workstations / New); the h2
in {color.text.bright}; the wizard step list with the current step on the
2px {color.border.selected-indicator}; an info alert on
{color.status.info.tint}; the form landmark holding the name and hostname
text-fields, the role select, the commissioned-on date-picker, the SSH-key
file-input, the notes textarea with its count and the enable checkbox; the
footer rule and the Back, Save draft and Continue buttons. The review
variant replaces the fields with a table whose row headers are the field
names in {color.text.muted} and whose last column is a tertiary Edit button.
Every part is the component's own markup and classes.

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | rest tokens of every component on {color.surface.default}; the current step carries the indicator bar, future steps are disabled | `aria-current="step"` |
| invalid | the name and hostname fields take the 2px {color.status.danger.border} and show their ✕ messages; the current step's marker becomes ✕; a danger alert on {color.status.danger.tint} summarising the two fields replaces the notice; the other fields stay at rest | border width 1 → 2px; glyphs; `aria-invalid`; `role="alert"` |
| busy | Continue shows the static ⋯ glyph in {color.action.primary.text}; the form carries `aria-busy`; nothing shrinks or animates | glyph; `aria-busy`; cursor: progress |
| disabled | every control takes its component's disabled treatment ({color.text.disabled}, {color.border.disabled}, {color.interaction.disabled.bg}); labels and review keys dim; no hover anywhere | `disabled` on the wrapping fieldset; cursor: not-allowed; never opacity |

The composed components keep their own hover, focus-visible, required,
placeholder-shown and read-only treatments; this specimen declares only the
states the whole form adds to them.

#### Keyboard

Landmarks first: the region, the Breadcrumb navigation and the form are
offered by name. Tab walks the trail, the reachable steps, the fields in
reading order and the three buttons; the file-input's hidden native input is
the stop and its visible button label is drawn with the ring. The select and
the date field move with arrows inside one stop. In the review variant the
Edit buttons are ordinary stops inside the table. Nothing traps focus.

#### Accessibility

The region and the form are both named by the h2. Complete steps read ", complete"
through a visually hidden suffix; future steps are disabled, not hidden.
The notice is `role="status"` and the invalid summary is `role="alert"`, so
the summary is announced once on submit while each field's message stays
linked by `aria-describedby`; `aria-invalid` is set only after submit. While
saving the form carries `aria-busy` and, once inert, sits inside a disabled
fieldset so every control reports its state natively. The review table has
a caption, column headers and row headers; each Edit button names its field
through a visually hidden suffix. Contrast: notice title 6.5:1 or better on
its tint, messages 5.26:1 on the panel surface, the invalid boundary 4.69:1
on the input surface, disabled text 3.24:1 (exempt).

#### Portability

Landmarks, native controls, one container query and three root hooks
(`data-state-invalid`, `data-state-busy`, `data-state-disabled`); no script
is needed to render any state. Hosts without container queries stack the
footer at the 600px viewport breakpoint. The 56rem reference width and the
two-column field grid are layout of this specimen and not rules; the
specimen proves composition, not a product. Toolkits port each contained
component through its own mapping.

#### Non-examples

A rounded card with a drop shadow around the form. A Material stepper with
circular numbered badges and a connecting line. A red banner across the
whole form instead of the summary alert plus field messages. Fields whose
borders glow red. A spinner replacing the Continue label. Saving while the
fields fade to half opacity. Placing the actions in a sticky footer bar with
a shadow.

### Filterable table (`filterable-table`)

A list screen composed from a toolbar with search and filter, removable chips, a selectable sortable data-table with status badges, a count and pagination; with empty and loading variants. Family: composed. Priority: R1. Maturity: stable. States: default, hover, focus-visible, selected, no-results.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.default` | `#100c0c` | use |
| root.border | `color.border.default` | `#531310` | use |
| toolbar.bg | `color.surface.chrome` | `#090707` | use |
| toolbar.border | `color.border.divider` | `#2b0e0d` | use-and-report (D-008) |
| toolbar.separator | `color.border.default` | `#531310` | use |
| search.bg | `color.surface.input` | `#000000` | use |
| search.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| search.text | `color.text.default` | `#e99499` | use |
| search.placeholder | `color.text.placeholder` | `#a3676b` | use |
| search.icon | `color.icon.default` | `#e99499` | use |
| filter.text | `color.text.default` | `#e99499` | use |
| filter.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| new.bg | `color.action.primary.bg` | `#7d1310` | use |
| new.text | `color.action.primary.text` | `#f4eeee` | use |
| chip.bg | `color.surface.default` | `#100c0c` | use |
| chip.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| chip.text | `color.text.default` | `#e99499` | use |
| chip.remove | `color.text.muted` | `#bd787d` | use |
| header.text | `color.text.bright` | `#ffa2a7` | use |
| header.rule | `color.border.strong` | `#9e231f` | use |
| row.text | `color.text.default` | `#e99499` | use |
| row.rule | `color.border.divider` | `#2b0e0d` | use-and-report (D-008) |
| row.bg-hover | `color.interaction.hover.bg` | `#1c0a09` | use |
| row.ring | `color.interaction.focus.ring` | `#e53935` | use |
| row.selected-bg | `color.interaction.selection.bg` | `#531310` | use |
| row.selected-text | `color.interaction.selection.text` | `#f4eeee` | use |
| row.ring-on-selection | `color.interaction.focus.ring-container` | `#ffa2a7` | use |
| check.bg | `color.surface.input` | `#000000` | use |
| check.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| check.checked-bg | `color.action.primary.bg` | `#7d1310` | use |
| check.glyph | `color.action.primary.text` | `#f4eeee` | use |
| badge.tested-fill | `color.status.success.fill` | `#86a46f` | use |
| badge.tested-text | `color.status.success.on-fill` | `#0c0909` | use |
| badge.demonstrated-fill | `color.status.info.fill` | `#7e9ebb` | use |
| badge.demonstrated-text | `color.status.info.on-fill` | `#0c0909` | use |
| empty.text | `color.text.muted` | `#bd787d` | use |
| empty.heading | `color.text.bright` | `#ffa2a7` | use |
| empty.glyph | `color.icon.decorative` | `#a3676b` | use |
| count.text | `color.text.muted` | `#bd787d` | use |
| pagination.text | `color.text.default` | `#e99499` | use |
| pagination.current | `color.text.bright` | `#ffa2a7` | use |
| pagination.disabled | `color.text.disabled` | `#8a5559` | use |

#### Purpose

A list screen that proves the navigation, display and feedback components
compose around one data-table: find, filter, select, page. It exists to
show the states of a whole list screen, including nothing matching and
still loading, not to prescribe a product.

#### Anatomy

The region landmark; the toolbar on {color.surface.chrome} with the
search-field, the Filter menu button, the Columns toggle, a separator and the
primary New button; a row of removable chips (priority: R1, maturity:
stable); the scroller around the data-table, whose Name header carries
`aria-sort`, whose rows carry `aria-selected` and a checkbox, and whose last
column is a status badge (✓ Tested on {color.status.success.fill}, i
Demonstrated on {color.status.info.fill}); the count in {color.text.muted}
and the compact pagination. The empty variant adds the empty-state block;
the loading variant marks the table `aria-busy`. Every part is the
component's own markup and classes.

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | rest tokens of every component; Name sorted ascending with its ▲ glyph; Prev disabled | `aria-sort`; the glyph |
| hover | the pointed row takes {color.interaction.hover.bg}; the toolbar controls and chips keep their own hover on their own pointer events only | cursor |
| focus-visible | the dashed {color.interaction.focus.ring} on the row holding focus (shown on the third row); the toolbar and chips stay at rest | the ring |
| selected | two rows take {color.interaction.selection.bg} with {color.interaction.selection.text} and a checked box; the select-all reports mixed with the – glyph | `aria-selected`; ✓ and – glyphs; `aria-checked="mixed"` |
| no-results | the body is the "No components match" row, the count reads 0 of 43, the search field's status line announces it; the chips stay so the cause is visible | text; `role="status"` |

The composed components keep their own pressed, sorted, disabled, loading
and open treatments; this specimen declares only the states the whole
screen adds to them.

#### Keyboard

Landmarks first: the region, the toolbar's search landmark and the
pagination navigation are offered by name. The toolbar is one stop with
arrow movement between the search field, Filter, Columns and New (the
toolbar contract); Escape clears the search. Each chip's Remove button is
its own stop. The table follows the data-table contract: the first row is
the stop, arrows move between rows, Space on a row's checkbox toggles it,
Enter on a header sorts. Next is the last stop; a disabled Prev is not
focusable.

#### Accessibility

The search field's visible label is replaced by a visually hidden one so the
toolbar stays one row; its status line is visually hidden too and announces
"No components match" through `role="status"`. The count is a second status
region and changes once per filter. Rows are named by their Name cell
through `aria-labelledby` on the row checkbox; the select-all is a
checkbox-role button whose mixed state carries the – glyph. Badges carry
their glyphs so status survives without colour. Contrast: toolbar text
8.60:1 on the chrome surface, boundaries 4.42:1, headers 10.10:1, badges
7.08:1 and 7.13:1 on their fills, the ring 7.48:1 on the selection fill.

#### Portability

Native table semantics, a toolbar, a menu button and a status region; the
roving tabindex and the menu need the host's script, every state renders
without one from the root hooks `data-state-selected` and
`data-state-no-results` and the table's own `aria-busy`. Hosts without
container queries wrap the toolbar at the 600px viewport breakpoint. The
56rem width and the 44rem table minimum are layout of this specimen and not
rules; the specimen proves composition, not a product.

#### Non-examples

A rounded search pill with a magnifier inside a shadowed toolbar. Filter
chips drawn as pills with a coloured fill per category. Zebra-striped rows.
A selected row shown by a coloured left bar and no fill. A blue Material
checkbox. A skeleton shimmer while loading. An infinite scroll with no count
and no pages.

### i3 window frame (`i3-window-frame`)

The workstation frame; an i3 bar with workspaces and Chinese i3status labels above the terminal and code-editor components as tiled, tabbed or floating windows behind i3 title bars. Family: composed. Priority: R1. Maturity: stable. States: default, focus-visible, selected, container-inactive, error.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.desktop` | `#000000` | use |
| root.border | `color.border.default` | `#531310` | use |
| bar.bg | `color.surface.chrome` | `#090707` | use |
| bar.text | `color.text.default` | `#e99499` | use |
| bar.rule | `color.border.divider` | `#2b0e0d` | use-and-report (D-008) |
| workspace.text | `color.text.default` | `#e99499` | use |
| workspace.selected-bg | `color.interaction.selection.bg` | `#531310` | use |
| workspace.selected-text | `color.interaction.selection.text` | `#f4eeee` | use |
| workspace.bg-hover | `color.interaction.hover.bg` | `#1c0a09` | use |
| workspace.ring | `color.interaction.focus.ring` | `#e53935` | use |
| workspace.ring-on-selection | `color.interaction.focus.ring-container` | `#ffa2a7` | use |
| workspace.urgent-bg | `color.status.danger.fill` | `#dc282e` | use |
| workspace.urgent-text | `color.status.danger.on-fill` | `#f9faf9` | use |
| status.separator | `color.text.muted` | `#bd787d` | use |
| status.glyph | `color.icon.decorative` | `#a3676b` | use |
| clock.text | `color.text.bright` | `#ffa2a7` | use |
| area.bg | `color.surface.desktop` | `#000000` | use |
| window.bg | `color.surface.canvas` | `#000000` | use |
| window.border | `color.border.default` | `#531310` | use |
| window.border-focused | `color.border.active` | `#e53935` | use |
| window.ring | `color.interaction.focus.ring-container` | `#ffa2a7` | use |
| title.bg | `color.surface.chrome` | `#090707` | use |
| title.text | `color.text.muted` | `#bd787d` | use |
| title.bg-focused | `color.surface.chrome-alt` | `#1c0a09` | use |
| title.text-focused | `color.text.bright` | `#ffa2a7` | use |
| tab.indicator | `color.border.selected-indicator` | `#e53935` | use |
| tab.indicator-inactive | `color.border.selected-indicator-inactive` | `#9e231f` | use |
| floating.border | `color.border.overlay` | `#e53935` | use-and-report (D-008) |
| floating.shadow | `shadow.floating` | `0px 6px 22px 0px rgb(0 0 0 / 55%)` | use |
| terminal.bg | `color.terminal.bg` | `#0c0909` | use |
| terminal.text | `color.terminal.fg` | `#e99499` | use |
| editor.bg | `color.code.bg` | `#0c0909` | use |

#### Purpose

The frame the palette was tuned in: an i3 bar, tiled windows with 1px
pixel borders, 14/−2 gaps, a tabbed container and one floating window. It
proves that the terminal and code-editor components sit inside window
chrome drawn from the same roles, and it records the workstation's geometry
as reference metrics. It is a specimen of composition, not a desktop.

#### Anatomy

The region on {color.surface.desktop}; the 28px bar on
{color.surface.chrome} with the workspace tablist (the visible workspace on
{color.interaction.selection.bg}) and the i3status group in Chinese with
its clock in {color.text.bright}; the workspace grid with the reference
gaps; each window a focusable group behind a 1px {color.border.default}
frame, {color.border.active} when focused, with a 23px title bar in
{color.text.muted} on {color.surface.chrome} that turns {color.text.bright}
on {color.surface.chrome-alt} when focused; the terminal or code-editor as
the client with its own boundary given up to the frame. Tabbed: the titles
form a tablist whose visible tab carries the 2px
{color.border.selected-indicator}. Floating: the editor sits above the
terminal behind {color.border.overlay} with {shadow.floating}, the only
shadow in the theme.

#### States

| State | Visual | Non-colour channel |
| --- | --- | --- |
| default | the terminal window focused: {color.border.active} frame and the bright title; the editor at rest; workspace 1 filled | the title text; `aria-selected` |
| focus-visible | the 2px {color.interaction.focus.ring-container} ring at −3px inside the focused window or container; the client draws no second ring | the ring |
| selected | workspace 2 takes the selection fill with {color.interaction.selection.text}; workspace 1 drops to rest | `aria-selected` |
| container-inactive | no window has focus: every frame {color.border.default}, every title {color.text.muted} on {color.surface.chrome}, the tab indicator {color.border.selected-indicator-inactive}, no caret | lightness drop; no caret |
| error | workspace 3 is urgent: {color.status.danger.fill} with {color.status.danger.on-fill} and a trailing ! | the ! mark; "urgent" read to assistive technology |

The clients keep their own states (the terminal's busy cursor, the editor's
selection and read-only gutter); this specimen declares only the states the
frame adds around them.

#### Keyboard

The workspace tablist is one stop with arrow movement; Enter or Space shows
a workspace. Each window is a focusable group so the frame itself can hold
focus, as i3 focuses a container; Tab then enters the client, whose own
contract applies (the terminal log, the read-only editor textbox). In the
tabbed variant the tab list is one stop and only the visible panel's client
follows. Mod+h j k l and Mod+1..9 belong to the window manager and are
documented, not reimplemented. Nothing traps focus.

#### Accessibility

The region names its workspace and layout. The bar is a group holding a
named tablist and a status group in `lang="zh"`, whose Nerd Font glyphs
are `aria-hidden` so only the labels are read. Every window is a named
group; the focused one says so in its name and the floating one too.
Urgency carries the ! mark and a visually hidden "urgent". Contrast:
workspace names 8.60:1 and the clock 10.31:1 on the bar, focused titles
10.01:1, rest titles 5.77:1, the focused frame 4.97:1 and the ring 10.37:1
on the window surface, the urgent fill 4.12:1 against the bar with 4.58:1
text on it.

#### Portability

Groups, a tablist and the clients' own roles; every state renders from the
root hooks without a script. The i3 geometry — bar 28px, titles 23px, gaps
14px inner and −2px outer — is reproduced with private variables on this
specimen's root only. Those numbers are reference metrics of the
workstation, recorded so a port of the i3 configuration matches it; they
are not tokens and not a rule for any other application, which keeps its
own window manager, bar and gaps. The specimen proves composition, not a
product; only i3 and sway ports draw this frame.

#### Non-examples

A window with rounded corners or a title bar gradient. A focused window
that glows or blurs its neighbours. A shadow under a tiled window; the only
shadow is the floating one. Gaps of 14/−2 forced into an unrelated
application's layout. A workspace indicator drawn as a red underline
instead of the selection fill. Traffic-light close buttons in the title
bar. A translucent bar.

### File browser (`file-browser`)

Location navigation and a filterable native file table with bounded host actions. Family: composed. Priority: R2. Maturity: stable. States: default, selected, empty, no-results, focus-visible.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.default` | `#100c0c` | use |
| root.text | `color.text.default` | `#e99499` | use |
| root.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| heading.text | `color.text.bright` | `#ffa2a7` | use |
| secondary.text | `color.text.muted` | `#bd787d` | use |
| control.bg | `color.surface.input` | `#000000` | use |
| control.focus | `color.interaction.focus.ring` | `#e53935` | use |
| dependency.role-border-width-default | `border.width.default` | `1px` | use |
| dependency.role-border-width-emphasis | `border.width.emphasis` | `2px` | use |
| dependency.role-color-border-divider | `color.border.divider` | `#2b0e0d` | use-and-report (D-008) |
| dependency.role-color-border-selected-indicator | `color.border.selected-indicator` | `#e53935` | use |
| dependency.role-density-comfortable-control-height | `density.comfortable.control-height` | `32px` | use |
| dependency.role-density-compact-control-height | `density.compact.control-height` | `24px` | use |
| dependency.role-radius-none | `radius.none` | `0px` | use |
| dependency.role-space-12 | `space.12` | `12px` | use |
| dependency.role-space-16 | `space.16` | `16px` | use |
| dependency.role-space-2 | `space.2` | `2px` | use |
| dependency.role-space-8 | `space.8` | `8px` | use |

#### Purpose

Compose hierarchical location navigation with a filterable file table. Opening an entry emits its stable identity. The caller owns file access, upload, deletion and permissions. The supplied names and sizes are synthetic local examples.

#### Anatomy

- **locations:** Tree navigation selects a location filter.
- **query:** Labelled native search filters visible rows.
- **table:** Native rows show names, kinds and sizes with explicit Open actions.

#### States

| State | Presentation | Non-colour channel |
| --- | --- | --- |
| default | Locations and native file rows. | Explicit names, kinds and sizes. |
| selected | Selected location follows the tree contract. | aria-selected plus location filter. |
| empty | No file rows. | Explicit zero-files text. |
| no-results | Filtered rows are hidden. | Matching count and retained search query. |
| focus-visible | Dashed control outline. | Native focus and tree roving focus. |

#### Keyboard

- **Tab / Shift+Tab:** Move among tree navigation, search and row actions.
- **Arrow keys:** Use the tree contract inside navigation.
- **Enter / Space:** Request opening the chosen file through an event.

#### Accessibility

Keep the complete nested tree behavior and semantic table headers. Filtering does not move focus. Announce counts, and keep the search field available after zero results. File actions must not imply access beyond the host's actual permissions.

#### Portability

Wrap labels and actions without changing source order. Use logical spacing in RTL. Preserve native target sizes at both densities; only bounded data regions may scroll horizontally. Use equivalent native semantics and approved roles; record any unsupported behavior as a mapping deviation. The host owns application data, persistence, permissions and services.

#### Non-examples

Unlabelled controls, color-only status, drag-only operations, hidden required instructions, stolen focus or an invented service success.

### Compact dashboard (`compact-dashboard`)

Named operational metrics, a complete chart and an actionable native records table. Family: composed. Priority: R2. Maturity: stable. States: default, loading, empty, focus-visible.

| Part | Role | Value | Eligibility |
| --- | --- | --- | --- |
| root.bg | `color.surface.default` | `#100c0c` | use |
| root.text | `color.text.default` | `#e99499` | use |
| root.border | `color.border.control` | `#a3676b` | use-and-report (D-007) |
| heading.text | `color.text.bright` | `#ffa2a7` | use |
| secondary.text | `color.text.muted` | `#bd787d` | use |
| control.bg | `color.surface.input` | `#000000` | use |
| control.focus | `color.interaction.focus.ring` | `#e53935` | use |
| dependency.role-border-width-default | `border.width.default` | `1px` | use |
| dependency.role-color-border-divider | `color.border.divider` | `#2b0e0d` | use-and-report (D-008) |
| dependency.role-color-surface-chrome | `color.surface.chrome` | `#090707` | use |
| dependency.role-density-comfortable-control-height | `density.comfortable.control-height` | `32px` | use |
| dependency.role-density-compact-control-height | `density.compact.control-height` | `24px` | use |
| dependency.role-font-size-h2 | `font.size.h2` | `16px` | use |
| dependency.role-radius-none | `radius.none` | `0px` | use |
| dependency.role-space-12 | `space.12` | `12px` | use |
| dependency.role-space-2 | `space.2` | `2px` | use |
| dependency.role-space-8 | `space.8` | `8px` | use |

#### Purpose

Compose a small operational overview from named metrics, a complete chart and a records table. All figures are caller data. Synthetic fixtures are labelled; the component does not fetch data or infer business health.

#### Anatomy

- **metrics:** Terms and values pair each metric name with its number.
- **activity:** Chart retains its full textual data table.
- **records:** Native rows include named actions and explicit status words.

#### States

| State | Presentation | Non-colour channel |
| --- | --- | --- |
| default | Square metric panels and readable tables. | Metric names, exact values and status text. |
| loading | Busy boundary with existing content retained. | aria-busy and host-provided loading status. |
| empty | Explicit absence of records. | Empty message; metric zero differs from unknown. |
| focus-visible | Dashed action outline. | Native keyboard focus. |

#### Keyboard

- **Tab / Shift+Tab:** Visit record actions and any scrollable data region.
- **Enter / Space:** Request the chosen record through an event.

#### Accessibility

Keep metrics as term/value pairs, tables labelled and all chart data visible. Do not use a color-only health score. Busy status does not manufacture a completed refresh. The host decides how unknown metrics are described, rather than silently displaying zero.

#### Portability

Wrap labels and actions without changing source order. Use logical spacing in RTL. Preserve native target sizes at both densities; only bounded data regions may scroll horizontally. Use equivalent native semantics and approved roles; record any unsupported behavior as a mapping deviation. The host owns application data, persistence, permissions and services.

#### Non-examples

Unlabelled controls, color-only status, drag-only operations, hidden required instructions, stolen focus or an invented service success.

## Coverage ledger

| Component | Family | Priority | Specified | Demonstrated | Test implemented |
| --- | --- | --- | --- | --- | --- |
| `admin-form` | composed | R1 | yes | yes | — |
| `alert` | feedback | R1 | yes | yes | — |
| `avatar` | display | R2 | yes | yes | — |
| `badge` | display | R1 | yes | yes | — |
| `breadcrumbs` | navigation | R1 | yes | yes | — |
| `button-group` | actions | R2 | yes | yes | — |
| `button` | actions | R1 | yes | yes | yes |
| `card` | display | R1 | yes | yes | — |
| `chart` | display | R2 | yes | yes | — |
| `checkbox` | forms-basic | R1 | yes | yes | yes |
| `chip` | display | R1 | yes | yes | — |
| `code-editor` | developer | R1 | yes | yes | — |
| `combobox` | forms-advanced | R1 | yes | yes | — |
| `command-palette` | navigation | R2 | yes | yes | — |
| `compact-dashboard` | composed | R2 | yes | yes | — |
| `data-table` | display | R1 | yes | yes | — |
| `date-picker` | forms-advanced | R1 | yes | yes | — |
| `description-list` | display | R2 | yes | yes | — |
| `diagnostics` | developer | R1 | yes | yes | — |
| `dialog` | feedback | R1 | yes | yes | yes |
| `diff-view` | developer | R1 | yes | yes | — |
| `disclosure` | navigation | R2 | yes | yes | — |
| `drawer` | feedback | R1 | yes | yes | — |
| `editor-search` | developer | R2 | yes | yes | — |
| `empty-state` | feedback | R1 | yes | yes | — |
| `error-state` | feedback | R2 | yes | yes | — |
| `field` | forms-basic | R1 | yes | yes | — |
| `fieldset` | forms-basic | R1 | yes | yes | — |
| `file-browser` | composed | R2 | yes | yes | — |
| `file-input` | forms-advanced | R1 | yes | yes | — |
| `filterable-table` | composed | R1 | yes | yes | — |
| `form-builder` | forms-advanced | L | yes | yes | yes |
| `i3-window-frame` | composed | R1 | yes | yes | — |
| `icon-button` | actions | R1 | yes | yes | — |
| `kbd` | display | R2 | yes | yes | — |
| `link` | actions | R1 | yes | yes | yes |
| `list` | display | R1 | yes | yes | — |
| `loading-indicator` | feedback | R1 | yes | yes | — |
| `menu` | navigation | R1 | yes | yes | yes |
| `multiselect` | forms-advanced | R2 | yes | yes | — |
| `number-field` | forms-basic | R1 | yes | yes | — |
| `pagination` | navigation | R1 | yes | yes | — |
| `popover` | feedback | R2 | yes | yes | — |
| `progress` | feedback | R1 | yes | yes | — |
| `radio-group` | forms-basic | R1 | yes | yes | yes |
| `range` | forms-basic | R2 | yes | yes | — |
| `repeater` | forms-advanced | R2 | yes | yes | — |
| `search-field` | forms-basic | R1 | yes | yes | — |
| `segmented-control` | actions | R2 | yes | yes | — |
| `select` | forms-basic | R1 | yes | yes | yes |
| `settings-panel` | composed | R1 | yes | yes | — |
| `sidebar-nav` | navigation | R1 | yes | yes | — |
| `skeleton` | feedback | R2 | yes | yes | — |
| `skip-link` | navigation | R1 | yes | yes | — |
| `switch` | forms-basic | R1 | yes | yes | — |
| `table` | display | R1 | yes | yes | yes |
| `tabs` | navigation | R1 | yes | yes | yes |
| `terminal` | developer | R1 | yes | yes | — |
| `text-field` | forms-basic | R1 | yes | yes | yes |
| `textarea` | forms-basic | R1 | yes | yes | — |
| `time-picker` | forms-advanced | R1 | yes | yes | — |
| `timeline` | display | R2 | yes | yes | — |
| `toast` | feedback | R1 | yes | yes | — |
| `toolbar` | navigation | R1 | yes | yes | — |
| `tooltip` | feedback | R1 | yes | yes | — |
| `tree` | navigation | R2 | yes | yes | — |
| `wizard` | forms-advanced | R1 | yes | yes | — |

## Application ports

No native ports are published yet. Historical implementations are catalogued in `references/` and are not supported downloads.

## Decisions

Statuses: `proposed` (awaiting the owner), `accepted`, `rejected`, `superseded`.
A token moves from `proposed` to `approved` only by an `accepted` decision referenced
in its `$extensions["io.github.j3w1.theme"].approval.decision`. Agents may open
`proposed` entries; only the owner changes a status.

| # | Title | Status | Date | Decided by |
| --- | --- | --- | --- | --- |
| D-000 | Responsibility split: tokens, spec, ports, evidence | accepted | 2026-09-05 | owner (design package) |
| D-001 | Controlled three-hue extension for status, diagnostics and diffs | accepted | 2026-09-06 | owner |
| D-002 | v0.1.0 ships the full R1 component inventory | accepted | 2026-09-06 | owner |
| D-003 | Text-role corrections with new in-hue values | accepted | 2026-09-06 | owner |
| D-004 | Public repository; MIT code, CC BY 4.0 prose | accepted | 2026-09-06 | owner |
| D-005 | Default profile id is `default` | proposed | 2026-09-06 | — |
| D-006 | Focus-ring rule, including invalid + focus | proposed | 2026-09-06 | — |
| D-007 | `border.control` uses `#A3676B` | proposed | 2026-09-06 | — |
| D-008 | Fold site hairlines into `border.divider`; `surface.sunken`; unified overlay border | proposed | 2026-09-06 | — |
| D-009 | Density modes `compact` and `comfortable` as tokens | proposed | 2026-09-06 | — |
| D-010 | The site deploys from `main` | proposed | 2026-09-06 | — |
| D-011 | Reference screenshots are cropped before publication | proposed | 2026-09-06 | — |
| D-012 | Exports are committed and drift-checked | proposed | 2026-09-06 | — |
| D-015 | Clarify component mappings exposed by consumption acceptance | proposed | 2026-09-07 | — |
| D-016 | Pinned release comparison report and historical rendering | proposed | 2026-09-08 | — |

| D-017 | Private framework parity evidence | proposed | 2026-09-08 | — |
| D-018 | Declared port capabilities and fresh import evidence | proposed | 2026-09-08 | — |
| D-019 | One-way namespaced Figma Variables bridge | proposed | 2026-09-08 | — |
| D-020 | Bounded local validation and recovery workflow | proposed | 2026-09-08 | — |
| D-021 | Local form composition with versioned data | proposed | 2026-09-08 | — |
| D-022 | Local visual selection before agent implementation distribution | proposed | 2026-09-08 | — |
| D-023 | True Black / Rose canonical foundation | accepted | 2026-09-08 | owner (explicit selection of candidate I) |
| D-024 | Distinguish links from body text and headings | accepted | 2026-09-08 | owner (explicit preview correction) |
| D-025 | Theme interactive controls throughout the portal and demo | accepted | 2026-09-09 | owner (explicit native-control replacement request) |
| D-026 | Primary action fill darkens off heritage ANSI 12 | accepted | 2026-09-11 | owner (explicit acceptance after visual review) |
| D-027 | Recede the interactive chrome | accepted | 2026-09-11 | owner (explicit acceptance after visual review) |
| D-028 | Merge gates and the deployment gate are different suites | accepted | 2026-09-11 | owner (explicit verification-split instruction) |

### D-000 Responsibility split

Status: accepted · 2026-09-05 (adopted from the design package `ARCHITECTURE.md`).

**Decision.** Token files own literal values and aliases. The specification owns
meaning, permitted uses and constraints. Port mappings own native keys. Evidence
records own what was actually tested. README, the site, and pictures summarise
these authorities and never replace them. A disagreement between spec and tokens
is a defect to resolve, not permission to choose the convenient answer. Images
never override token values. Historical source is evidence of origin, not an
override of the adopted accessible design.

### D-001 Controlled three-hue extension

Status: accepted · 2026-09-06.

**Context.** The observed identity is monochrome red/rose. Diagnostics, diffs and
status information need differentiation that lightness alone cannot give (a
200-line diff cannot be scanned by glyph). The owner's own PhpStorm scheme already
uses conventional hues for these surfaces.

**Decision.** The `default` profile adds exactly three hues, desaturated and
warm-shifted: amber `#C9973F` (warning, modified), green `#86A46F` (success,
added), blue `#7E9EBB` (info), with dark tints `#1F1A0C`, `#0F1A0E`, `#0F141C`.
They are permitted only in the `status`, `diagnostic`, `diff` and (in the
`extended` overlay) `code`, `terminal` and `chart` groups. They are forbidden in
`surface`, `text`, `border`, `interaction` and `action`. Purple, cyan and magenta
are not added. Every status role also carries a mandatory non-colour channel
(glyph, underline pattern or border style). Syntax highlighting in `default`
stays monochrome; the `extended` overlay profile remains `proposed`.

**Consequences.** Measured contrast on canvas / surface / raised: amber
7.54 / 7.34 / 6.90, green 7.13 / 6.95 / 6.53, blue 7.08 / 6.90 / 6.48.

### D-002 v0.1.0 inventory

Status: accepted · 2026-09-06.

**Decision.** The first public release contains every component marked `R1` in
the coverage inventory (about 47 across all nine families) plus four composed
specimens (settings panel, administrative form, filterable table, i3 window
frame), each *specified* and *demonstrated*; ten core components are also
*tested* (button, link, text-field, select, checkbox, radio-group, tabs, dialog,
menu, table). `R2` items and the form-builder specimen are deferred to v0.2.

### D-003 Text-role corrections

Status: accepted · 2026-09-06.

**Context.** `#A3676B` (the site's `--quiet`) measures 4.45:1 on `#0C0909` and
`#7D1310` (`--inactive`) 1.86:1; the site already restricts them but still uses
`#7D1310` for line numbers. The site's suggested replacement `#A8403A` measures
only 3.27:1.

**Decision.** `text.subtle` = `#AD7175` (5.10 / 4.96 / 4.67) for metadata,
comments and line numbers. `text.disabled` = `#8A5559` (3.33 / 3.24 / 3.05;
exempt under WCAG 1.4.3, at least 3:1 by house policy). `#A3676B` remains in
service as `border.control`, `icon.decorative`, `status.neutral.fill` and
`chart.series.4` (non-text, at least 3:1). `#7D1310` remains only as
`border.disabled`, `code.whitespace` and minor grid lines, never as text. The
`heritage-ansi` profile keeps the original assignments flagged `$deprecated`.

### D-004 Visibility and licensing

Status: accepted · 2026-09-06.

**Decision.** The repository is public. Code, tokens, schemas, scripts, tests,
site source and generated JSON/CSS exports are MIT. Specification prose,
specimens and generated Markdown exports are CC BY 4.0. Screenshots under
`references/` are all rights reserved, reference only. No fonts, no vendor
template material, no private business data are ever committed; CI scans for
them. See `LICENSE.md`.

### D-005 Default profile id

Status: proposed.

**Decision.** The approved everyday composition is the profile `default`. A
later rename would be a MAJOR version change.

### D-006 Focus-ring rule

Status: proposed.

**Decision.** Controls and rows: `outline: 1px dashed {color.interaction.focus.ring}`
(`#E53935`) with `outline-offset: -2px`. Focusable containers (panes, windows,
dialogs, cards): `outline: 2px solid {color.interaction.focus.ring-container}`
(`#FFA2A7`) with `outline-offset: -3px`. On any filled surface (selection,
action fills, status fills) the ring takes that surface's on-fill text colour,
because `#E53935` on `#911410` measures 2.15:1. For invalid + focus, where the
2px danger border is already red, the ring switches to the container colour
`#FFA2A7` at `outline-offset: -4px`. `:focus:not(:focus-visible)` draws nothing.
In forced-colors mode the ring uses `Highlight`. Selection is always a fill and
focus is always a ring; neither borrows the other's form.

**Current clarification (D-015).** The owner-directed component mappings
below clarify the generic on-fill wording above. Primary buttons and checked
controls retain their explicitly declared container-colour ring; destructive
fills retain their declared on-fill text ring. Geometry and contrast floors
are unchanged.

### D-007 Control boundary colour

Status: proposed.

**Decision.** Form-control boundaries must reach 3:1 against their surface
(WCAG 1.4.11). `#531310` (1.39) and `#9E231F` (2.56) both fail, so
`border.control` = `#A3676B` (4.45 / 4.33 / 4.07). The alternative `#C81A1A`
(3.42) is rejected as too loud for every input at rest.

### D-008 Hairlines, sunken surface, overlay border

Status: proposed.

**Decision.** The site's six near-identical hairline hexes (`#290D0C`,
`#24100F`, `#321110`, `#38100F`, `#190B0B`, `#2B0E0D`) fold into one
`border.divider` = `#2B0E0D`. `#0A0707` becomes `surface.sunken`. Every
floating layer (dialog, drawer, menu, popover, toast) uses a 1px
`border.overlay` = `#E53935`; the historical dunst frame `#AE1914` survives only
in `heritage-ansi`.

### D-009 Density modes

Status: proposed.

**Decision.** Two density modes are tokens, applied with `data-density` on any
subtree. `compact` matches the site: control height 24px, row height 28px, UI
type 12/16, icon 16, list gap 2. `comfortable`: control 32px, row 36px, UI
type 13/18, icon 16, list gap 4. Both meet the 24×24 CSS-pixel target minimum;
`compact` requires 4px between adjacent targets.

### D-010 Deployment source

Status: proposed.

**Decision.** The site deploys from `main` through GitHub Actions after the
full pipeline passes. The page header prints the version and build commit and
links to the latest tag; consumers pin tags or commits regardless.

### D-011 Reference screenshots

Status: proposed.

**Context.** The four PhpStorm screenshots show a private project name in the
window title bar.

**Decision.** Before any screenshot is committed under `references/`, the title
bar is cropped (or the name redacted) and the redaction is disclosed in the
provenance record. If the owner prefers, the screenshots stay out of the public
repository and are described in `references/catalogue.json` only.

### D-012 Committed exports

Status: proposed.

**Decision.** `exports/`, `site/src/styles/tokens.generated.css`, the JSON
schemas under `schemas/json/` and the README marker blocks are generated and
committed; `npm run check` fails CI on drift or orphan files. Outputs contain no
timestamps and no commit hashes so two runs are byte-identical. `dist/` is never
committed.

### D-013 Consumption eligibility is separate from approval

Status: proposed (formal log status remains owner-controlled).

**Context.** Issue #4 found conflicting instructions to use and to prohibit
pending roles in the approved default profile. Alternatives were to block
pending roles until individually approved, or to use pinned values with disclosure.

**Owner direction, 2026-09-07.** During Phase 1 planning the owner selected
"Use and report" and "Block new use" for deprecated roles, then authorized
implementation. That explicit direction governs consumption without approving
pending token values or changing D-005 through D-012.

**Policy.** An approved default profile's pending roles may be used at a pinned
revision with disclosure of their pending decision IDs, including dependencies.
Proposed profiles remain preview-only and blocked for delivery. Heritage profiles
are historical-only. Deprecated or heritage roles cannot be newly mapped in an
approved profile; no substitute is implied. Ordinary observed/approved roles
remain usable within their documented scope; primitives remain inspection-only.
Release version, profile status, role status, deprecation and eligibility are
separate facts. The generator emits the policy and eligibility metadata.

### D-014 Automatic inline literal color previews

Status: proposed (formal log status remains owner-controlled).

**Context.** Issue #1 requests circular previews and a transparency checkerboard,
which conflict with the general square geometry and no-gradient rules. Keeping
square previews would remove the requested circle-to-square interaction.

**Owner direction, 2026-09-07.** The owner explicitly approved a narrow preview
exception and authorized implementation. It is presentation of literal data,
not a new palette, semantic role or component geometry rule.

**Exception.** Every visible CSS hex literal on the spec page receives one
automatically generated preview. Preserve the literal exactly. The preview is
16px including its theme-derived 1px boundary, circular at rest, and scales to
17.4px with radius zero when CSS `:hover` matches. The fast duration token and
ease-out govern transform and border-radius; reduced motion removes animation.
Alpha is composited over a checkerboard made from existing theme tokens.
Only this preview permits rounded geometry, patterned gradient backgrounds and
border-radius animation. Text, copying, keyboard order and no-JS access remain
intact. The parsed literal is data and never inferred from a token or screenshot.


Syntax specimens (literal data, not additional theme roles): `#f00`, `#f008`,
`#ff0000`, `#ff000080`, `#000000`, `#ffffff`. These examples also exercise
copy fidelity and transparent, dark and light previews in the browser suite.

#### Follow the pointer hovering effect

The j3w1 specification's inline color circles provide a pointer-following
exact-value preview for actual mouse and pen pointer events. The popup follows the pointer's
position inside the circle with an 8px gap, flips when necessary and stays
inside an 8px viewport gutter. It disappears immediately when the pointer
leaves that circle, on an outside press, Escape, or loss of window focus.
After page or nested-container scrolling, recheck the element under the last
pointer position: keep the popup only if the same circle remains there.
The popup cannot capture pointer input or keep itself open when hovered.
It is a supplementary, non-interactive visual preview; token tables and the
keyboard-accessible token inspector retain the persistent information and links.
Long previews are bounded by the viewport. No new keyboard stop is introduced.
Pointer tracking has no animation or easing, including under reduced motion.
This owner-directed behavior extends the D-014 preview exception only; it
does not change the general tooltip or focus contract.

**Compatibility clarification.** CSS `:hover` controls the circle-to-square
morph directly. Mouse and pen pointer events control the popup; touch and
unknown pointer types do not open it. Neither behavior is gated by `hover`
or `any-hover` media features: browsers can report no hover capability while
still delivering mouse input. Reduced motion continues to remove animation,
and circles and their literal text remain visible without JavaScript.

### D-015 Clarify component mappings exposed by consumption acceptance

Status: proposed. The owner directed application of this resolution on
2026-09-07; its implementation preserves existing component appearance.
The formal decision status remains owner-managed.

**Context.** Phase 2's independently reviewed composed acceptance preserved
three contradictions: ordinary confirmation versus destructive alert-dialog
tone, the primary button's pressed-border metadata versus its prose/CSS, and
the global on-fill focus wording versus explicit component ring mappings.
Phase 3 controls must not present an arbitrary interpretation as canonical.

**Owner-directed resolution.** Preserve current component appearance and all token
values. Ordinary confirmation uses the primary action tone; destructive tone
belongs to destructive actions, independent of the confirmation's semantic
role. The primary button retains `color.action.primary.border` when pressed;
secondary and tertiary use `color.border.active`, and destructive retains its
declared destructive border. Correct the generic/default metadata accordingly.
Focusable controls retain their documented component ring mappings: primary
and checked/selected fills use `color.interaction.focus.ring-container` where
declared; destructive fills use their on-fill text role. Clarify the global
wording to refer to these explicit mappings, retaining ring geometry,
forced-colors behavior and every contrast floor. No profile or pending-token
approval is implied.

**Alternatives.** Enforce the generic prose everywhere, changing currently
rendered pressed borders and focus colors; or retain the contradictions and
explicitly mark affected workbench configurations unresolved. Neither is
silently selected. Historical acceptance reports remain historical evidence.

### D-016 Pinned release comparison report and historical rendering

Status: proposed. Issue #2 authorizes derived release diagnostics; formal
decision status and all theme approvals remain owner-managed.

**Context.** Release comparison needs a versioned report without putting
run metadata into deterministic canonical exports or interpreting missing
historical fields using today's rules.

**Implementation scope.** Schema version 1 records both immutable revisions,
profile and input digests, separate semantic change categories, explicit
dependency evidence, and unsupported historical inputs. Reports and historical
specimen renderings are build artifacts under `dist/releases/`; the canonical
exports retain their existing contract. Historical data is read from Git, never
executed as a build or imported as a module. The current shared specimen
renderer presents each side's maintained markup and styles with a documented
renderer version and matched conditions. This is a reconstructed visual
reference, not evidence that an old site's JavaScript or an external port ran.
Actual browser captures remain run evidence with recorded environment and
artifact hashes. Missing evidence stays not run. Renames require an explicit
mapping naming the two revisions; equal values do not establish a rename.

**Alternatives.** A raw file diff loses semantic categories and dependency
evidence; executing historical applications expands the trust boundary and
confounds renderer changes with theme changes. Neither is required for this
bounded comparison. No design values, eligibility rules or verification
statuses are changed by the report format.

### D-017 Private framework parity evidence

Status: proposed. Issue #3 authorizes an independently authored private harness;
formal approval statuses and the existing license boundaries are unchanged.

The harness consumes an operator-supplied licensed target and an explicit
license review. It copies only declared template inputs into a new private
fixture directory, uses synthetic examples and records the exact theme,
template, framework, dependency and fixture identities. The original target
and its application behavior remain outside this experiment's write scope.
Browser captures, copied vendor inputs and detailed results remain private.
Missing prerequisites produce not-run output. A successful diagnostic run is
not a verified port or a claim of complete visual parity.

Alternatives are a public vendor copy (incompatible with the current license
boundary) or a generic framework mock presented as real template evidence
(insufficient provenance). Neither is used.

### D-018 Declared port capabilities and fresh import evidence

Status: proposed. Issue #14 authorizes a derived mapping explorer; it does not
create a port, change host capabilities or approve a profile.

Optional `capabilities.json` metadata refines each existing mapped/unmapped
role into mapped, inherited, unsupported, out-of-scope or not-implemented,
with an explicit surface and reason. Legacy unmapped reasons remain
unclassified; the generator never guesses a more specific state. Import
verification is separate from mapping state and declared manifest status.
Verified output requires a recorded real-import protocol bound to current
target metadata, canonical tokens, mappings, capabilities and artifact bytes.
Relevant changes make evidence stale; failed or missing checks cannot pass.
The same generated catalogue supplies summaries, drill-downs and task kits.
Private parity records are never automatically imported into the catalogue.

Alternatives are a hand-maintained support matrix or treating a successful
parse as an application import. Both lose the evidence boundary.

### D-019 One-way namespaced Figma Variables bridge

Status: proposed. Issue #15 authorizes this bounded adapter; approval and
eligibility statuses remain those of the canonical source.

An explicit pinned revision supplies colors and scalar spacing/radius values.
Aliases retain their dependency structure; dependency-only primitives are
hidden from property pickers. Unsupported types and non-approved profiles are
reported. One collection and one mode avoid assuming paid mode capabilities.
Dry-run is read-only, apply is explicit, and external import receipts identify
owned IDs and their last observed values. Conflicts and manual edits stop an
update or rollback. The bridge does not store ownership in unsupported plugin
data, overwrite unrelated variables, or write values back to the repository.

Alternatives are manual duplicate maintenance, a paid-only REST workflow, or
silently flattening aliases/composites. None meets the one-way evidence scope.

### D-020 Bounded local validation and recovery workflow

Status: proposed. Issue #16 authorizes one executable pattern using existing
admin-form, field, alert and button rules, without adding palette roles.

The workflow has editing, invalid, review, busy and success stages. Validation
runs on explicit submission, preserves values, and presents linked errors.
Typing does not rewrite live announcements. Review can return to a field;
confirm enters busy, and a separate local simulation-completion event enters
success. No request is made. The existing workstation example's reserved
name and hostname are explicitly local fixture conflicts, not business policy.
The pattern contract pins constituent component content digests and reports
automated evidence separately from manual screen-reader acceptance.

Alternatives are a color-only invalid snapshot, validation on every keystroke,
or treating a submit click as a successful save. They do not teach recovery.

### D-021 Local form composition with versioned data

Status: proposed. Issue #19 authorizes the deferred form-builder demonstration.

Five canonical field kinds can be added, edited, removed and reordered with
buttons. Definitions, preview values and validation state are separate. JSON
imports have closed fields, bounded size/counts and unique stable IDs; failed
imports leave the current form unchanged. Content identity pins the supplying
theme and component contracts. Downloads contain definitions, never entered
values. The builder reuses D-020's field rendering and validation behavior.
It remains a draft reference component, not a backend platform or native port.

Alternatives are arbitrary HTML/schema execution, a conditional workflow
engine, and a second visual field implementation. They exceed this task.

### D-022 Local visual selection before agent implementation distribution

Status: proposed. Issue #37 and the owner's implementation instruction authorize
three local candidate systems before canonicalization. Formal design approval
remains owner-managed.

The primary outcome is agent consumption of installable or copyable components
and compositions with complete dependencies and pinned machine contracts. The
future package, documentation portal and Vue application are consumers of the
same canonical design language. A local review harness first compares exactly
three explicit token overlays with Current, using shared specimen rendering and
an original Vue composition. Overlays, generated reports and review builds stay
ignored and are never public profiles or delivery artifacts. The existing alias
resolver and unrounded contrast engine evaluate candidates without changing
canonical values, historical ANSI slots or waivers. No approval is inferred from
numerical or browser evidence. The owner must select a concrete candidate before
foundation freeze, mass productization or deployment. See
`docs/phase6-visual-review.md` for commands, boundaries and continuation.

Alternatives are productizing the old palette before visual selection, publishing
unapproved preview profiles, or offering incomplete appearance-only snippets as
consumable components. None satisfies the authorized sequence and consumption goal.

### D-023 True Black / Rose canonical foundation

Status: accepted. The owner explicitly selected the reviewed candidate I:
"use the True Black / Rose theme and make it permanent for j3w1/theme",
and authorized continuing the remaining Phase 6 implementation.

The approved change is confined to seven surface assignments: canvas, sunken,
input, code and terminal use the existing pure black primitive; chrome uses
`#090707`; default panels use `#100c0c`. Add the two selected values as
`color.primitive.ink.14` and `color.primitive.ink.28`, preserving the luminance
ordering and all historical primitives. Keep every other color assignment,
including rose/white foreground, red actions, focus, borders, selections,
status and chart roles, exactly as reviewed. Raised/overlay surfaces remain
`#241010`; alternate chrome remains `#1c0a09`. Preserve the complete historical
profile by explicitly retaining its previous surface assignments.

The rejected A/B/C, revised D/E/F and final G/H/I reviews are historical local
evidence. Their temporary inputs are not public profiles. Selection approves
these specific surface values and assignments, not unrelated pending decisions,
new colors or accessibility waivers. This is the foundation for the Phase 6
package, copy distributions, static portal and Vue showcase. Existing consumers
retain their immutable pins; the new surface contract ships in version 1.0.0,
following the repository's major-version rule for approved value changes.

Superseded in part, and only in part. Three later accepted decisions each
replace one or more of the role assignments above, and leave everything else
here standing: D-024 replaces `text.link`; D-026 replaces
`action.primary.bg`; D-027 replaces `surface.raised`,
`interaction.selection.bg` and `text.placeholder`. Every other value and rule
recorded in this decision — the seven surface assignments, the two new
primitives, the luminance ordering, the remaining foreground, red action,
focus, border, selection, status and chart roles, overlay `#241010`, alternate
chrome `#1c0a09`, and the retained historical profile — stands as accepted.
This decision keeps its `accepted` status; it is not superseded as a whole.

### D-024 Distinguish links from body text and headings

Status: accepted. During the implementation preview the owner requested links
that are more distinguishable from normal text, using different colors. This
is an explicit correction to the foreground-role retention in D-023, limited
to links. Map `color.text.link` to the existing strong red primitive `#f73f35`,
retain the persistent red underline, and retain near-white hover text on the
strong dark-red hover fill. Visited links keep the link color and underline;
current-page indicators and selected navigation retain their explicit on-fill
roles. Body text and heading colors remain the True Black / Rose hierarchy.

The alternative of bright-rose links still shares the heading color and gives
too little separation in the reviewed dashboard. Blue/purple links would
introduce an unrelated hue. No new primitive, status hue, contrast exception
or focus rule is introduced. Preserve the old link assignment in the historical
profile. This refinement ships with the same unreleased 1.0.0 major change.

### D-025 Theme interactive controls throughout the portal and demo

Status: accepted. After reviewing native blue selections in the work board and
agent-kit picker, the owner explicitly instructed: "replace mostly all native
components like `<select>` we must use all j3w1 themed". The owner also requested a
properly proportioned command-search field. This records that authorization.

The maintained select implementation now renders its single-choice popup and
multiple-choice list in theme-owned DOM using the existing selection, surface,
text, border and focus roles. The native select remains the form-value and
constraint source and the no-JavaScript fallback. This supersedes the select
specification's prohibition on replacing the host popup for web implementations.
Native ports retain their host-specific mappings. The custom interface follows
the select-only combobox and multiple listbox keyboard patterns, preserves labels,
disabled options/fieldsets, native input/change events, defaults and form reset,
and documents its actual tested scope.

Provide the same supported enhancement for existing application controls, use it
throughout the portal and Vue demo, and include it in consumption documentation
and copy closures. Theme ordinary input, button, checkbox, radio, range and file
control chrome as well. Browser-owned system dialogs and explicit no-JavaScript
fallbacks remain native; they are not styled by unsupported platform hacks.

Date inputs use a theme-owned text editor and calendar; time inputs use explicit
text entry and theme-owned step buttons. Their original native inputs retain
constraints and form values. This supersedes the earlier date/time component
specifications' host-popup descriptions for enhanced web implementations.

The alternative of advisory option CSS leaves the demonstrated blue host
selection unchanged. One-off Vue/portal dropdowns duplicate behavior and leave
agents with inconsistent components. New hues, focus rules, accessibility
waivers and license changes are not authorized by this correction. The additive
enhancement and corrected component behavior ship in 1.1.0; existing 1.0.0 pins
remain immutable. The command field fills its dialog width below its label.

### D-026 Primary action fill darkens off heritage ANSI 12

Status: accepted. The owner asked for a slightly darker fill on primary
actions such as the portal's `Explore components` button, and accepted this
entry as implemented after reviewing it.

`color.action.primary.bg` aliased `color.primitive.red.300`, which is also
`color.primitive.ansi.12` and `color.terminal.ansi.12`. `schemas/roles.mjs`
fixes every `color.primitive.ansi.<i>` to its heritage value byte for byte in
every profile, so darkening that primitive is not available: it would fail
validation and silently repaint the terminal's bright red.

The role therefore aliases `color.primitive.red.200` (`#7d1310`), a value
already in the palette, rather than minting a new colour. Label contrast rises
from 8.17:1 to 9.28:1 and the focus ring over the fill from 4.90:1 to 5.57:1;
both were already passing. The state ladder stays ordered — pressed `#630f0d`
below the fill, hover `#911410` above it.

The declared pair `primary fill against the panel` is a recorded decorative
waiver. It measured 2.07:1 before this change and measures 1.83:1 after it,
further from 3:1. It is listed as decorative because the label identifies the
button; this decision does not change that reasoning, and no waiver is added
or widened.

This supersedes part of D-023, and only this part. `action.primary.bg`
(`#871f19`) was one of the assignments that decision kept "exactly as
reviewed" under its red actions clause; this entry replaces that single role
and nothing else in D-023. The primitive is untouched, so
`color.primitive.red.300`, `color.primitive.ansi.12` and
`color.terminal.ansi.12` all still carry the heritage value.

Alternatives: mint a new primitive at the exact requested value, which adds a
colour to the palette for a difference of about one step and no measurable
gain; or leave the fill and darken only the portal button, which would put a
value in a stylesheet that is not in `tokens/`, against the confirmation-only
rule in `theme.json`.

### D-027 Recede the interactive chrome

Status: accepted. The owner found the open list, its selected row, the
placeholder text and the link hover fill all louder than the content they sit
against, and asked for a darker treatment across the set, then accepted this
entry as implemented after reviewing it.

Four roles move, all onto values already in the palette; no colour is minted.

- `surface.raised` takes `ink.30` (`#160b0b`) instead of `ink.52`. An open list
  now reads as depth rather than as a lighter panel, and stays distinct from
  `surface.default` (`#100c0c`).
- `interaction.selection.bg` takes `red.100` (`#531310`) instead of `red.350`.
  Text on the fill improves from 7.92:1 to 12.47:1.
- `text.placeholder` takes `rose.350` (`#a3676b`) instead of `rose.600`,
  measuring 4.71:1 on `surface.input` — darker, and still clear of the 4.5:1
  floor without a waiver.
- Links hover on `interaction.hover.bg` rather than `interaction.hover.bg-strong`.
  Menu items keep the strong fill, so the two remain distinguishable.

The selection fill was never what carried selection: at `#911410` it measured
2.00:1 against the raised surface, below the 3:1 non-text minimum, and the
recessed value measures less again. Selection is carried by the 2px
`border.selected-indicator` and the check glyph, which is what
`spec/foundations.md` requires when a state would otherwise be colour-only.
Both are unchanged. The full report still shows 954 pairs, 17 waived and none
failing; no waiver is added or widened.

This supersedes part of D-023, and only this part. `surface.raised`
(`#241010`) and `interaction.selection.bg` (`#911410`) were among the values
the owner selected in that accepted decision, and `text.placeholder`
(`#bd787d`) fell under the same decision's instruction to keep the foreground
roles exactly as reviewed. This entry replaces those three role assignments
and nothing else in D-023: every other surface, foreground, focus, border,
selection, status and chart role it approved stands as accepted, as does its
historical-profile retention. `tests/foundation-selection.test.js` guards the
set by name and now records the recessed values; every other surface in it is
still the D-023 selection, untouched.

The darker fill also settles the ring on a selected row. D-006 recolours the
ring on filled surfaces because `#e53935` measured 2.15:1 on the old
`#911410`; on `#531310` it measures 3.38:1, past the 3:1 minimum, so a
selected row now takes the ordinary 1px dashed control ring instead of the 2px
solid container ring. That is the only fill this reaches: the ring still
measures 2.52:1 on `action.primary.bg` and 1.13:1 on `status.danger.fill`, and
both keep their recoloured ring. `spec/foundations.md` carried the stale 2.15:1
figure as its stated reason and has been corrected; D-006's own record is left
as written, since it describes what was true when it was opened.

Alternatives: keep the fills and darken only the components the owner pointed
at, which would put presentation in a stylesheet rather than in the roles and
leave selection meaning two different things; or introduce new primitives at
intermediate values, which adds colours to the palette for differences the
existing steps already cover.

### D-028 Merge gates and the deployment gate are different suites

Status: accepted. The owner instructed that the exhaustive verification move
off the pull-request critical path: merges gate on a few minutes of checks,
the full matrix gates deployment, and a local full run stays recommended
rather than mandatory.

**Context.** Accepting D-026 and D-027 changed prose, token metadata and
generated exports. It cost twenty-nine and a half minutes of continuous
integration before it could merge. Measured on that run: sources zero point
six minutes, build zero point seven, packed consumers three point three, and
the browser matrix twenty-four point nine. The browser job was eighty-four per
cent of the wait, and the repository is public, so the runners are free and
the whole cost is latency for whoever is waiting.

**Decision.** A pull request merges on `validate`, `build` and a new `smoke`
job. The packed-consumer and browser suites run on `main` before deployment
and on demand through the workflow dispatch. The full local loop is
`npm run test:all`; it is available and recommended before pushing, and it is
not mandatory. Nothing verifies that it was run.

The full matrix is a deployment gate. It always was: a browser failure has
never prevented a merge from being correct, it prevents the site from being
published, and `deploy` still depends on it. Requiring it before merge treated
a publication gate as a correctness gate and charged every change for the
difference. What the merge gate keeps is what a source test cannot see: that
the built site loads under its base path, renders from the tokens, and takes a
keyboard.

`smoke` is a named subset and reports as one. It runs the desktop project
only, over the page, portal, button and text-field specs, and it reports with
the plain list reporter rather than the evidence reporter. That last point is
the rule, not an optimisation: the evidence reporter records unselected tests
as not run, so a filtered run that reached it would publish an evidence matrix
that looked complete and was not. A passing `smoke` is a signal that the build
is sound. It is never a coverage claim, and the published matrix continues to
come only from the full suite.

The packed-consumer suite moves with the browser suite and cannot be split
from it. The browser job downloads the consumer evidence artifact and
republishes it against the deployed package, and
`scripts/ui-verification-report.mjs` requires the complete protocol on all
three engines because a filtered run must not pass as a full one. Both are on
the deployment path together.

**No mandatory local gate.** A receipt produced by the machine that ran the
tests proves only that something wrote a receipt. It cannot distinguish a real
run from a forgotten one that was filled in afterwards, so it would have
caught carelessness and nothing else, at the price of machinery that looked
like proof. Local verification is a practice here, recorded in `AGENTS.md`,
not a gate.

Alternatives: shard the full suite across runners, which keeps complete
coverage before merge at about six minutes and costs nothing on a public
repository, but is bounded below by the largest single spec file and gives
each shard its own partial evidence file to merge; or keep the packed
consumers on the merge path for three more minutes, which buys cross-engine
coverage of the distributed package rather than of this site. Sharding is the
one worth revisiting if complete pre-merge coverage ever matters more than the
minutes.

## References

| Reference | Kind | Where | What it does not prove |
| --- | --- | --- | --- |
| j3w1.github.io site.css — the flagship web implementation | reference-implementation | `j3w1-web` `assets/css/site.css` | The :root block (lines 78–136) is the observed source of every role value. It will become a pinned consumer of this theme through a separate reviewed change; there is no live import. |
| j3w1.github.io accessibility contract (contrast floor, reduced motion, focus) | reference-implementation | `j3w1-web` `docs/wm-accessibility.md` | Section 9 measured the palette and restricted --quiet and --inactive; this specification extends those rules (D-003). |
| Xresources — the canonical sixteen-slot palette with the author's slot annotations | historical | `legacy-i3` `config/Xresources` | Exact values are preserved as color.primitive.ansi.*; the annotations seed the syntax roles. A 2020–21 X11 configuration proves nothing about current desktop installations. |
| i3-gaps configuration — borders, gaps, bar and client colours | historical | `legacy-i3` `config/i3/config` | Source of the 1px borders, 14/−2 gaps and the focused/urgent colour classes. Window-manager geometry is reference metrics, not a cross-application rule. |
| i3status colours — good, bad and degraded inside the same hue | historical | `legacy-i3` `config/i3/i3status.conf` | A historical status semantic (#FFA2A7 / #A3676B / #C81A1A). Superseded for new work by D-001. |
| dunst notification colours | historical | `legacy-i3` `config/config/dunst/dunstrc` | Origin of the toast roles and the critical pair #DC282E / #F9FAF9 (4.58:1). |
| dmenu colours | historical | `legacy-i3` `config/dmenurc` | Origin of the dark selection #630F0D. |
| gedit GtkSourceView scheme '1w3j' — a Solarized structure filled with reds | historical | `legacy-i3` `config/config/gedit/styles/1w3j.xml` | An early editor port by the owner. Not a supported download; values predate the token set. |
| IntelliJ colour scheme '1w3j theme' (2020–21, Material Darker base, red chrome and console) | historical | `legacy-i3` `config/intellij/config/colors/1w3j-theme.icls` | Only chrome, console, gutter and selection were recoloured; syntax stayed stock. Evidence that a JetBrains port has precedent, not a port. |
| tmux status colours (pywal-cache variant) | historical | `legacy-i3` `config/tmux.conf.local` | Uses the non-canonical #0C0A09 / #D09899 variant; catalogued to explain why those values are not tokens. |
| pywal-generated ramp (scripts/colors.sh) | historical | `legacy-i3` `scripts/colors.sh` | The machine-generated predecessor of the hand-tuned Xresources palette. Not normative. |

