Color picker
canonGrid of colour swatches (radiogroup, arrows, optional 'no colour' row) and its canonical wrapper: popover on desktop, sheet on the phone, InSheet panel inside a dialog.
acrm !4337 (one swatch grid instead of four copies: EventLabelPicker, EventTypeFilter, MailLabelDialog use ColorPicker; SegmentIconPicker keeps its own panel and uses ColorSwatches for SEGMENT_TONES — validator exception), !4347 (phone: swatches span the width, 44 px targets, segment icon tiles capped at --tap-min), !4354 (stories + usage/a11y), !4363 (a11y: 'Bez barvy' is a radio of the same group with a check on the right; roving tabindex — one Tab stop on the chosen option; focus ring and selection ring read apart). Canon 1. 10. 2026 (AD) after before/after app screenshots (1440 + 390, light/dark) and the States story. Custom hex colour in the segment picker unchanged; dropping it is a separate owner decision.
Used in 6 files · 5 areas · 6× — components/calendar, components/mail, components/meetings, components/segment, pages/TymyPage
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