Color picker — accessibility
Keyboard
- The grid is a
role="radiogroup"of<button role="radio">. Arrow keys move focus by one swatch (left, right) or one row (up, down); Home and End jump to the first and last. The move does not wrap and does not select — Enter or Space on the focused swatch picks it, which closes the panel. - Every swatch is a real
<button>and a Tab stop. The group does not use a roving tabindex, so Tab walks all 24 swatches (a known limit, below). - On open, focus moves to the checked swatch, or to the first one when nothing is chosen, so a keyboard user starts where the current value is.
- Escape closes the panel and nothing else: when the picker is opened from a dialog, the dialog stays open. Focus returns to the trigger.
- The "Bez barvy" row is a
<button>under the grid, one Tab stop after it; Enter or Space picks "no colour". - The foundation focus ring (2 px
--accent, 1 px offset) applies to every swatch and is not switched off. It reads apart from the selection ring (canvas gap +--ink-900), so a focused swatch that is not chosen cannot be mistaken for the value.
ARIA
- The group carries
role="radiogroup"and anaria-labelthat names what is chosen (Barva štítku); on a phone the same text is the sheet title. The panel isrole="dialog"with the same name. - Each swatch is
role="radio"witharia-checked(true for the chosen colour only) and a name in botharia-labelandtitle— the colour is never conveyed by its fill alone. Names are the palette names (Tomato, Sage, …) andBarva <tone>for tones. - The "Bez barvy" row is a button with
aria-pressed, true while nothing is chosen. It is not a swatch and not a radio of the group: "no colour" is the absence of a value, and the row sits outside the radiogroup. - The swatch fill is decorative to assistive technology; the name is the content.
Selection, not a check mark
- The chosen swatch is shown by a ring, not by a check mark inside it. A white check on the light tones had a contrast of 2.1:1 (amber), below the 3:1 required of a graphic object. The ring is drawn against the panel (
--bg-canvasgap, then--ink-900), not against the swatch, so it holds on all 24 tones and in both themes. - Selection does not rely on colour alone: the ring is a shape difference.
Touch
- On a phone the swatch stays 24 px but the touch target is 44 px (
--tap-min) through an invisible::after, centred on the swatch. The row pitch is 48 px, so neighbouring targets never overlap. - The sheet has a grab handle, a title and a full-width
Hotovo, and tapping the backdrop closes it — Escape and a tap outside are not enough on a phone.
Contrast
Computed from packages/ui/src/tokens/tokens.css and the palette:
- The selection ring is
--ink-900on the panel surface — above 3:1 in both themes. - The swatch itself is not text; the 24 palette colours are data (Google Calendar), and each one is told apart by name, not only by hue.
- The "Bez barvy" label is ink on the panel surface, as every menu row.
Known limits
- All 24 swatches are Tab stops; a roving tabindex (one stop, arrows inside) would shorten the walk and is not built.
- Arrow keys stop at the ends of the grid; they do not wrap.
- Colour names are the English Google names on purpose; a screen reader in Czech reads them with Czech phonetics.
- Several palette colours are close in hue (for example Beetroot and Cherry Blossom); users with colour-vision differences rely on the name or the order, which follows the spectrum.
Source: docs/choice/color-picker.a11y.md · GitLab