How to build an accessible ColorPicker in React

The picking area is a role="group" holding one native range input per axis — saturation on x, brightness on y — rather than a single role="slider", which cannot describe two dimensions and in the previous build carried no aria-valuenow at all. Real inputs also mean arrow stepping, Home/End and PageUp/PageDown are the platform's rather than a hand-rolled key switch, and each axis announces its own percentage. Hue and alpha are native range inputs for the same reason. The preset swatches are a labelled role="group" of toggle buttons with roving focus, so the arrows move focus between swatches and Enter or Space selects — the previous build labelled that group "Saturation and lightness" and made the arrows change the value instead. The hex field commits on blur, Enter or a valid paste and rejects anything unparseable, so a half-typed value never reaches onValueChange. A polite live region reports the current colour, mounted before the first change so that change is announced too. Selection and focus each carry a non-colour channel under forced-colors, and every control reaches the coarse-pointer target minimum.

When to use a ColorPicker

When not to use it

Keyboard interactions

Role group, verified at WCAG 2.2-AA.

Common mistakes

Avoid: <ColorPicker /> with no presets for a brand palette of 4 fixed colors

Prefer: <RadioGroup> of color swatches

A free-form picker invites off-brand values when only a fixed set is allowed

Example

<ColorPicker defaultValue="#3b82f6" onValueChange={setColor} />

See the full ColorPicker reference →