Skip to contentWolf-Rayet

Decision records

ADR-0094 Focus is drawn by the system, and the fixtures are build output

Accepted2026-09-16Phase 6

#Context

The design pass began by asking what a keyboard user sees. Nine components accept input. Tabbed through their specimens on the docs site:

  • input-field and number-input removed focus. Both stylesheets set

outline: none on :focus-visible, on the reasoning that a focused field is told apart by its ground. It is — when the caller passes state="focused". These components are presentational and never set that state themselves, so real keyboard focus drew nothing.

  • checkbox and radio-button drew focus where nobody could see it. Their

native input is transparent over the drawn box, so the browser's ring was painted on an invisible element.

  • The other five fell back to the browser's own ring, which no theme here

was designed against — in field-night that is a hue the theme exists to avoid.

No check read any of this. Every check in the repository reads declarations, tokens, source and receipts, and the site check reads layout; none moved focus.

#Decision

One ring, drawn by the system, on every component that accepts input. Each interactive component's tier gains focus-ring-color — text-primary, the role furthest from every ground in every theme — and focus-ring-width and focus-ring-offset, both border-heavy. The ring is an outline outside the box, so it moves no layout. Where the native control is transparent, the ring goes on the shape the reader sees. The two suppressions are removed.

A site check moves focus. docs:check-site tabs through each interactive component's specimens as a keyboard user would, and every stop must draw an outline on the focused element or on the shape beside it — and an outline on a transparent element does not count. It was run against the old stylesheets first and reported all five failing stops across the four components above, after a first draft that counted the transparent inputs as drawn was caught missing two of them. 62 focus stops pass on the new stylesheets.

No measured value moves, and that was checked rather than assumed: no scene holds focus, and all 63 unthemed and 197 themed comparisons are identical.

#Found while regenerating: the fixtures had drifted

The fixtures inline the built stylesheets, so they were regenerated — and thirty-odd of them changed before the focus rules did: they carried stylesheet comments their components had rewritten since. The measured values were identical, because comments render nothing, but committed output that its generator no longer produces is the failure build:check exists to refuse, and it never looked at fixtures.

The fixtures are now the fourth committed output. fixtures:all reruns every generator the budget package declares, then the themed set, and build:check asserts the tree still matches HEAD. One generator is excluded by name with its reason printed: fixture:proof captures the proof product from a running server and is not a build step.

#Rejected

  • Draw focus with the ground, as state="focused" does. The component does

not know it is focused unless the product tells it, and a product that forgot would ship an invisible focus.

  • A focus role in the semantic tier. text-primary already clears every

ground by the widest margin in every theme; a new role would be a second name for it.

  • An inset ring. It would draw inside the governed box and change emission.
  • Leave the fixtures out of build:check because their values were right.

A committed file is a claim about what its source produces, and these were not.