Skip to main content

Colour Picker

Allows users to select or specify a colour via a visual picker or hex input. Use in configuration panels where a colour value is a required user input. Don't use when colours should be constrained to a token-defined palette.

OverviewStyleAccessibility

Description

Anatomy

PartRequired?Notes
Trigger swatch Required A small filled square showing the currently selected colour. Clicking it opens the picker popover. Size is 24x24px with --op-radius-sm border-radius.
Hex input field Required A text input pre-populated with the current hex value (e.g. #3D9970). Allows direct keyboard entry of a hex code. Validates on blur.
Colour canvas Required A 2D saturation/lightness gradient canvas derived from the current hue. Users drag a circular thumb to set saturation and lightness.
Hue slider Required A horizontal rainbow gradient slider for selecting the base hue (0–360°). Rendered as a range input with custom styling.
Opacity slider Optional A horizontal checkerboard-backed slider for setting alpha (0–100%). Only shown when the consuming context requires alpha support.
Preset swatches row Optional A row of up to 8 clickable colour swatches representing recently used or context-recommended colours.
Popover container Required The floating panel that wraps all interactive picker elements. Positioned below the trigger swatch by default, flipping above when viewport space is limited.
Label Recommended A visible text label associated with the trigger via aria-labelledby. Describes what the colour is being applied to (e.g. 'Project brand colour').

Variants

States

State Behaviour
Default Trigger swatch displays the current colour. Hex input shows the corresponding hex value. Popover is closed.
Open Popover is visible. Focus moves to the colour canvas on open. Hex input and sliders are interactive.
Focused (trigger) Trigger swatch shows a 3px focus ring using --op-color-interactive-focus. Press Enter or Space to open the popover.
Focused (hex input) Hex input shows standard focus ring. User can type a valid hex code. Canvas and sliders update live on valid input.
Invalid If the hex input value is not a valid colour on blur, the input border switches to --op-color-status-error and an inline error message appears below the field.
Disabled Trigger swatch and hex input are non-interactive. Swatch has 40% opacity. Popover cannot be opened. aria-disabled='true' on the trigger.
Read-only The selected colour is displayed but cannot be changed. Trigger swatch is not focusable. Hex input has readonly attribute.

Usage guidelines

Do / Don't

Do

Always pair the colour picker with a descriptive label (e.g. 'Marker colour') so users understand what the colour applies to.

Don't

Don't use the trigger swatch alone without a label — icon-only or swatch-only controls are not self-explanatory for all users.

Do

Show a preview of the colour applied in context (e.g. a sample map pin or chart bar) near the picker when possible.

Don't

Don't rely on the swatch alone to communicate colour impact — users may not be able to visualise how the colour will look in the actual UI.

Do

Validate the hex input on blur and surface a clear error message if the value is invalid (e.g. 'Enter a valid hex code, such as #3D9970').

Don't

Don't silently reset to the previous value on invalid input without notifying the user.

Do

Persist the selected value immediately on close so the swatch reflects the user's last confirmed choice.

Don't

Don't require a separate 'Apply' button inside the popover — this adds unnecessary steps for an already-deliberate interaction.

Layout & Spacing

Trigger swatch: 24x24px, --op-radius-sm, border 1px solid --op-color-border-default. Trigger swatch + hex input gap: --op-space-8 (8px). Popover container padding: --op-space-16 (16px). Colour canvas: 240px wide x 160px tall. Margin-bottom --op-space-12 (12px). Hue slider: full width of canvas. Margin-bottom --op-space-8 (8px). Opacity slider (when shown): full width of canvas. Margin-bottom --op-space-12 (12px). Hex input within popover: full width. Margin-bottom --op-space-12 (12px) when presets follow. Preset swatches: 20x20px each, --op-radius-sm, gap --op-space-4 (4px). Max 8 per row. Popover min-width: 272px.

Tokens

PartTokenValue
Trigger swatch border --op-color-border-default 1px solid border. Switches to --op-color-interactive-default on focus.
Focus ring (trigger and inputs) --op-color-interactive-focus 3px solid outline, 2px offset.
Hex input border (default) --op-color-border-default 1px solid.
Hex input border (invalid) --op-color-status-error 1px solid. Paired with error message text in --op-color-status-error.
Popover background --op-color-bg-primary White surface. Box shadow: --orbit-shadow-lg.
Canvas thumb --op-color-bg-primary White circle, 14px diameter, border 2px solid --op-color-bg-primary, drop-shadow for contrast on dark colours.
Slider track (hue) N/A — rendered as a CSS hue gradient No token; the hue gradient is computed from 0–360 hsl values.
Slider thumb --op-color-bg-primary White circle, 16px diameter, border 2px solid --op-color-border-default.
Preset swatch border --op-color-border-default 1px solid. Selected swatch uses 2px --op-color-interactive-default ring.
Error message text --op-color-status-error --op-text-sm, paired with an error icon.

Engineering notes

  • The colour picker is a custom component — there is no wa-* web component equivalent. It is implemented as a Web Component or React component wrapping a native fallback for environments that do not support the custom build.
  • The canonical output value is a lowercase 6-digit hex string (e.g. #3d9970). Strip the leading # before storing if the data layer expects a raw hex value.
  • When alpha/opacity support is enabled, output RGBA as a CSS rgba() string or an 8-digit hex. Document which format the consuming API expects.
  • The hex input must accept values with or without a leading # and normalise on blur. Reject values shorter than 3 or 6 meaningful characters.
  • Popover positioning should use floating-ui or an equivalent library to handle viewport edge cases. Apply a z-index above overlays (--op-z-popover).
  • Colour canvas mouse and touch events should be throttled (requestAnimationFrame) to avoid performance issues during drag on lower-powered devices.
  • Emit a 'change' event with the new hex value on every meaningful update (canvas drag end, slider change, hex input blur on valid value). Emit an 'input' event for live preview updates during drag.
  • When used in a form, bind the hidden input name/value to the hex string so the colour is submitted with the form data.

Keyboard interaction

KeyAction
Tab Moves focus to the trigger swatch, then (when popover is open) through the hex input, hue slider, opacity slider (if shown), and preset swatches in DOM order.
Enter / Space Opens the popover when the trigger swatch is focused. Activates a preset swatch when it is focused.
Escape Closes the popover and returns focus to the trigger swatch.
Arrow keys (on colour canvas) Move the canvas thumb in 1% increments (saturation/lightness). Hold Shift for 10% increments.
Arrow keys (on sliders) Adjust hue or opacity in 1-unit increments. Hold Shift for 10-unit increments.

Why it matters

Government configuration panels are used by a wide range of staff, including those who rely on keyboard navigation or screen readers. A colour picker that is only operable via mouse excludes keyboard-only users entirely. Providing full keyboard control of the canvas and sliders, and supporting direct hex input as a primary interaction path, ensures all users can complete colour configuration tasks.

Focus

When the popover opens, focus moves to the colour canvas element (role="slider" with aria-label describing saturation and lightness). When the popover closes (Escape or click outside), focus returns to the trigger swatch. The popover uses role="dialog" with aria-label="Colour picker" to establish a focus trap while open, preventing focus from escaping to underlying page content.

ARIA

Role or attributeWhen to useExample
role="slider" Applied to the colour canvas interactive region and to each range slider (hue, opacity). <div role="slider" aria-label="Saturation and lightness" aria-valuemin="0" aria-valuemax="100" aria-valuenow="72" tabindex="0">
aria-label Applied to the trigger swatch button. Should reference the field label (e.g. 'Project brand colour — open colour picker'). <button aria-label="Project brand colour — open colour picker" aria-expanded="false">
aria-expanded Applied to the trigger swatch button. Set to 'true' when the popover is open, 'false' when closed. <button aria-expanded="true">
aria-live="polite" Applied to an off-screen announcement region. Announces the current hex value when selection changes so screen reader users hear live feedback. <span aria-live="polite" class="sr-only">Selected colour: #3D9970</span>
aria-invalid Applied to the hex input when the entered value is not a valid colour. <input type="text" aria-invalid="true" aria-describedby="hex-error">
aria-describedby Links the hex input to its error message element when in an invalid state. <input aria-describedby="hex-error"> <span id="hex-error">Enter a valid hex code, such as #3D9970</span>
role="dialog" Applied to the popover container to create a focus trap and communicate modal intent to assistive technology. <div role="dialog" aria-label="Colour picker" aria-modal="true">

Contrast

The colour picker UI chrome (labels, hex input text, slider track outlines) must meet 4.5:1 contrast against --op-color-bg-primary. Text in the hex input uses --op-color-text-primary on --op-color-bg-primary, which satisfies this requirement.

The picker does NOT validate whether the user's chosen colour meets contrast requirements for its intended use. If the chosen colour will be used as text or an interactive element colour, the consuming component or form must perform contrast validation and surface a warning. Do not silently accept colours that would produce inaccessible results downstream.

The canvas thumb and slider thumb must remain visible against both light and dark regions of the gradient. Use a white fill with a contrasting drop-shadow to ensure visibility across the full hue/saturation range.

Touch targets

The trigger swatch at 24x24px is below the 44x44px minimum touch target. Wrap it in a button element with padding to bring the interactive hit area to at least 44x44px without enlarging the visual swatch. The canvas thumb (14px) and slider thumbs (16px) should have a touch target overlay of at least 44x44px centred on the visual element.

Things to avoid

  • Do not rely solely on colour to communicate the selected value — always show the hex code alongside the swatch so users with colour vision deficiencies can confirm their selection.
  • Do not close the popover on any mouse-down outside without also supporting Escape key dismissal.
  • Do not disable the hex input as a 'simplified' UI — it is the primary accessible input path for keyboard and screen reader users.
  • Do not skip aria-live announcements for canvas drag updates — screen reader users need feedback that the colour is changing as they use arrow keys.

Was this page helpful?

Updated 2 October 2026