Requirements

Accessibility

Every control ships a keyboard path, a visible focus ring drawn from tokens, and a color pairing that clears WCAG AA. The table below measures the live theme — toggle it in the sidebar to validate both palettes.

Color contrast

Ratios are computed in the browser from the resolved token values. Text pairs must reach 4.5:1, and non-text pairs such as borders and the focus ring must reach 3:1.

Measuring…

Focus states

One indicator for the whole system, drawn from --focus-ring, --focus-ring-width and --focus-ring-offset. It falls back to the system highlight color under forced-colors mode. Never set outline: none on a control; re-point the tokens instead.

Tab through these to verify the ring in the current theme.

Keyboard navigation

Button

  • Tab reaches every enabled button; Enter and Space activate it.
  • Disabled and loading buttons are removed from the tab order by the native disabled attribute.
  • Loading state sets aria-busy and announces its label through a polite live region.
  • size="sm" keeps a 44x44 pointer target even though it renders smaller.

Input

  • The label is programmatically associated, so clicking it focuses the field.
  • Hint and error text are linked with aria-describedby; errors also use aria-invalid and aria-errormessage.
  • Errors are announced immediately via role="alert".
  • Focus is shown on the whole field shell, not just the bare input.

Checkbox

  • Tab reaches the box; Space toggles it.
  • The indeterminate state reports aria-checked="mixed" to screen readers.
  • The 20px box carries a 44x44 target; the label is also a click target.

Modal

  • Focus moves to the dialog panel on open and returns to the trigger on close.
  • The native dialog element traps Tab inside the dialog and makes the page behind inert.
  • Escape closes a dismissable dialog; the close button is the first tab stop.
  • Non-dismissable dialogs must always provide a footer action that closes them.

Open the dialog, tab through it, press Escape, and confirm focus lands back on this button.

Rotate deploy key

Focus is on the panel, trapped inside the dialog, and returns to the trigger on close.