Component States
An interactive control is a lifecycle, not a resting style. Map applicable states to semantics, visible treatment, and recovery.
Decision order and evidence
- Name role, interaction model, owner, and applicable states.
- Give each state ARIA/DOM mapping, visible non-color treatment, trigger, and recovery.
- Test keyboard, focus-visible, contrast, reduced motion, and async completion.
Report one evidence row per state; n/a needs a subject-absence reason. Hand field validation
to form-ux, emptiness to empty-states, and missing tokens back as gaps, never fallbacks.
Async/destructive controls
Name one semantic owner; visual states only present its lifecycle.
| State / event | Guard and observable contract |
|---|---|
ready / invoke | Click, Enter, Space, and script use one route; accepted work creates a request key. |
confirming | Use only for irreversible/broad/cascading work; name consequence, move focus in, restore invoker on cancel. |
submitting | Create key before dispatch; reject duplicates; expose disabled/busy and working label. |
| matching completion | Only matching key settles work; stale results cannot clear busy, alter target, or move focus. |
| success / failure | Record data first; confirm locally and choose focus, or retain context with nearby error/retry/cancel. |
Test real and stale/failed completion. If success empties a region, hand it to empty-states.
State matrix
Account for these states where applicable:
| State | Trigger | Must communicate | CSS |
|---|---|---|---|
| Default | rest | "this is here / interactive" | base styles |
| Hover | pointer over | "you can interact" (pointer devices only) | :hover |
| Focus | keyboard tab / programmatic | "this is where you are" (keyboard) | :focus-visible |
| Active / pressed | during click/tap | "your action registered" | :active |
| Disabled | not available | "not available, and why" | :disabled / [aria-disabled] |
| Loading | async in progress | "working, wait" | a data-loading / aria-busy class |
| Selected / current | chosen / active page | "this is the active one" | [aria-selected]/[aria-current] |
| Error / invalid | failed validation | "something's wrong here" | [aria-invalid] |
The first five baseline any control; add selected and error where applicable.
Non-negotiables
- Never ship default-only: applicable states need semantics and non-color signals.
- Never remove a focus outline without an equally visible replacement; hover is not focus.
- Explain unavailability; a silent disabled submit is a
form-uxfailure. - Async controls prevent duplicate dispatch, expose busy state, and preserve contrast (≥3:1 UI, ≥4.5:1 text); feedback is fast and reduced-motion-safe.
Implementation starter
Read assets/states-starter.css for tokenized CSS/reduced-motion details. A missing token is
a gap, never a fallback hex; add the request guard before styling async work.
State by component
- Button/link: default, hover, focus-visible, active, disabled/loading as applicable;
toggles use
aria-pressed; links retain non-color/visited cues. - Toggle / switch: use
role="switch"(notrole="checkbox") witharia-checked; Space toggles, Tab moves focus. Example:<button role="switch" aria-checked="true">Notifications</button>. Screen readers announce "switch, on/off" instead of "checkbox, checked." - Fields: focus, disabled, filled/readonly, error, selected/open/checked states; style a
correctly labelled focusable input (
form-uxowns validation). - Navigation/list/menu: current/selected uses
aria-current/aria-selected; pointer and keyboard focus are equally legible. - Overlays: model open/closed and entry/exit; prefer native Popover/
<dialog>; toasts userole="status"; hover-only content remains focus-reachable.
Reference files
references/states.md— state semantics, pitfalls, ARIA pairings.references/decision-records.md— novel-case ADR rules.
Self-check (per interactive element)
For each applicable state, confirm visible focus, semantics, non-color contrast, and reduced-motion behavior. For async/destructive work confirm owner, guard, justified confirmation, duplicate prevention, recovery, and final focus; exercise real and stale/ failed completion separately. Do not collapse controls into one checkmark.
How to deliver
Deliver applicable matrix and lifecycle evidence. Pair with form-ux, empty-states,
micro-motion, a11y-pass, and color-system at their boundaries.
Contract (generated)
Canonical detail: contract.json.
- Route: A control has interaction states missing or inconsistent with its semantics. (+1 in contract.json); avoid: The request is only about form copy or validation timing. (+1 in contract.json)
- Exclude: Do not invent page-level empty states or validation policy. (+2 in contract.json)
- Stop / handoff: Stop when the component semantics or interactive states cannot be identified. (+2 in contract.json); receives [design-system-interview, empty-states, improve-existing-website, responsive-layout, spacing-system, tasteroll, theming] -> sends [form-ux, empty-states, a11y-pass, micro-motion, tastecheck-pass]
- Output: component lifecycle contract and implementation guidance
- Evidence:
table_with_evidencewithstatus,reason,remediation,evidence,provenance.


