# j3w1 UI Theme Spec — compact (1.1.0, profile: default)
<!-- 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= -->

Read this first; it is the smallest complete statement of the approved contract.
Pin a tag or commit and fetch every file at that revision (see `agents/consume.md`). Values marked PROPOSED are not approved.

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.
## Identity

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.

## Approved core roles (default)

Surfaces, text, borders, interaction, actions and status. The code, diagnostic, diff, terminal, chart and icon groups are in `exports/tokens.resolved.json` and inlined in each component's JSON.

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

## Foundations

| Token | Value | Use |
| --- | --- | --- |
| `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.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. |
| `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. |

## State rules

## 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`.

## Component briefs

### 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. Status: specified, demonstrated, test implemented. Site: https://j3w1.github.io/theme/reference/#c-button. JSON: `exports/components/button.json`.

States: default, hover, focus-visible, active, disabled, loading.

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

### 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. Status: specified, demonstrated, test implemented. Site: https://j3w1.github.io/theme/reference/#c-checkbox. JSON: `exports/components/checkbox.json`.

States: default, hover, focus-visible, checked, mixed, checked+focus-visible, checked+disabled, disabled, invalid, required.

Tokens: box.bg → `color.surface.input` (#000000); box.border → `color.border.control` (#a3676b); box.bg-hover → `color.interaction.hover.bg` (#1c0a09); box.ring → `color.interaction.focus.ring` (#e53935); box.bg-checked → `color.action.primary.bg` (#7d1310); box.bg-checked-hover → `color.action.primary.hover-bg` (#911410); box.glyph → `color.action.primary.text` (#f4eeee); box.ring-checked → `color.interaction.focus.ring-container` (#ffa2a7); box.border-invalid → `color.status.danger.border` (#e53935); box.bg-disabled → `color.interaction.disabled.bg` (#160b0b); box.border-disabled → `color.border.disabled` (#7d1310); box.glyph-disabled → `color.text.disabled` (#8a5559); text.default → `color.text.default` (#e99499); text.disabled → `color.text.disabled` (#8a5559); legend.text → `color.text.bright` (#ffa2a7); legend.required-mark → `color.status.danger.text` (#f73f35); message.text → `color.status.danger.text` (#f73f35).

### 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. Status: specified, demonstrated, test implemented. Site: https://j3w1.github.io/theme/reference/#c-dialog. JSON: `exports/components/dialog.json`.

States: default, open, closed, focus-trapped, hover, focus-visible, reduced-motion.

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

### 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. Status: specified, demonstrated, test implemented. Site: https://j3w1.github.io/theme/reference/#c-link. JSON: `exports/components/link.json`.

States: default, hover, focus-visible, visited, current.

Tokens: root.text → `color.text.link` (#f73f35); root.underline → `color.text.link-underline` (#dc282e); root.text-hover → `color.text.link-hover` (#f4eeee); root.bg-hover → `color.interaction.hover.bg-strong` (#630f0d); root.ring → `color.interaction.focus.ring` (#e53935); root.text-visited → `color.text.link` (#f73f35); root.text-current → `color.text.bright` (#ffa2a7); root.indicator → `color.border.selected-indicator` (#e53935); icon.stroke → `color.icon.default` (#e99499).

### 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. Status: specified, demonstrated, test implemented. Site: https://j3w1.github.io/theme/reference/#c-menu. JSON: `exports/components/menu.json`.

States: default, hover, focus-visible, open, closed, disabled, checked, selected.

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

### 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. Status: specified, demonstrated, test implemented. Site: https://j3w1.github.io/theme/reference/#c-radio-group. JSON: `exports/components/radio-group.json`.

States: default, hover, focus-visible, checked, checked+focus-visible, disabled, invalid, required.

Tokens: box.bg → `color.surface.input` (#000000); box.border → `color.border.control` (#a3676b); box.bg-hover → `color.interaction.hover.bg` (#1c0a09); box.ring → `color.interaction.focus.ring` (#e53935); box.bg-checked → `color.action.primary.bg` (#7d1310); box.bg-checked-hover → `color.action.primary.hover-bg` (#911410); box.dot → `color.action.primary.text` (#f4eeee); box.ring-checked → `color.interaction.focus.ring-container` (#ffa2a7); box.border-invalid → `color.status.danger.border` (#e53935); box.bg-disabled → `color.interaction.disabled.bg` (#160b0b); box.border-disabled → `color.border.disabled` (#7d1310); box.dot-disabled → `color.text.disabled` (#8a5559); text.default → `color.text.default` (#e99499); text.disabled → `color.text.disabled` (#8a5559); legend.text → `color.text.bright` (#ffa2a7); legend.required-mark → `color.status.danger.text` (#f73f35); message.text → `color.status.danger.text` (#f73f35).

### select — Select

A themed single-choice combobox or multiple-choice list backed by a native select for forms and the no-JavaScript fallback. Status: specified, demonstrated, test implemented. Site: https://j3w1.github.io/theme/reference/#c-select. JSON: `exports/components/select.json`.

States: default, hover, focus-visible, required, invalid, invalid+focus-visible, disabled.

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

### 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. Status: specified, demonstrated, test implemented. Site: https://j3w1.github.io/theme/reference/#c-table. JSON: `exports/components/table.json`.

States: default, hover, selected, selected+focus-visible, selected+container-inactive, sorted, empty, loading.

Tokens: root.bg → `color.surface.default` (#100c0c); header.text → `color.text.bright` (#ffa2a7); header.bg → `color.surface.default` (#100c0c); header.rule → `color.border.strong` (#9e231f); header.bg-sticky → `color.surface.raised` (#160b0b); sort.glyph → `color.text.bright` (#ffa2a7); sort.ring → `color.interaction.focus.ring` (#e53935); row.text → `color.text.default` (#e99499); row.divider → `color.border.divider` (#2b0e0d); row.bg-hover → `color.interaction.hover.bg` (#1c0a09); selected.bg → `color.interaction.selection.bg` (#531310); selected.text → `color.interaction.selection.text` (#f4eeee); selected.ring → `color.interaction.focus.ring-container` (#ffa2a7); selected.inactive-bg → `color.interaction.selection.inactive-bg` (#420f0c); selected.inactive-text → `color.interaction.selection.inactive-text` (#e99499); caption.text → `color.text.muted` (#bd787d); empty.text → `color.text.muted` (#bd787d); loading.glyph → `color.text.muted` (#bd787d).

### 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. Status: specified, demonstrated, test implemented. Site: https://j3w1.github.io/theme/reference/#c-tabs. JSON: `exports/components/tabs.json`.

States: default, hover, focus-visible, selected, selected+focus-visible, selected+container-inactive, disabled.

Tokens: list.bg → `color.surface.default` (#100c0c); list.border → `color.border.divider` (#2b0e0d); tab.text → `color.text.default` (#e99499); tab.text-selected → `color.text.bright` (#ffa2a7); tab.text-disabled → `color.text.disabled` (#8a5559); tab.bg-hover → `color.interaction.hover.bg` (#1c0a09); tab.bg-pressed → `color.interaction.pressed.bg` (#420f0c); tab.indicator → `color.border.selected-indicator` (#e53935); tab.indicator-inactive → `color.border.selected-indicator-inactive` (#9e231f); tab.ring → `color.interaction.focus.ring` (#e53935); panel.bg → `color.surface.default` (#100c0c); panel.text → `color.text.default` (#e99499).

### 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. Status: specified, demonstrated, test implemented. Site: https://j3w1.github.io/theme/reference/#c-text-field. JSON: `exports/components/text-field.json`.

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.

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

## Roles pending an owner decision

These roles use-and-report their pending decision IDs at the pinned revision; their proposed status is unchanged.

| 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) |

## Where to read more

- Reading protocol: https://raw.githubusercontent.com/j3w1/theme/v1.1.0/agents/consume.md
- Manifest: https://raw.githubusercontent.com/j3w1/theme/v1.1.0/theme.json
- Resolved tokens: https://raw.githubusercontent.com/j3w1/theme/v1.1.0/exports/tokens.resolved.json
- Per-component JSON: https://raw.githubusercontent.com/j3w1/theme/v1.1.0/exports/components/<id>.json
- Full specification: https://raw.githubusercontent.com/j3w1/theme/v1.1.0/exports/theme.full.md
- Digests: https://raw.githubusercontent.com/j3w1/theme/v1.1.0/exports/digests.json
- Site (confirmation only): https://j3w1.github.io/theme/
