Skip to the content

ComponentsForm

Color picker

Lets people choose a colour on a saturation and brightness area, a hue slider and a hex field.

Import

import { ColorPicker, ColorPickerPopover, ColorSwatch } from "@booleanpress/ui/color-picker"

Usage

ColorPicker draws the picker in the page; ColorPickerPopover draws a swatch button that opens it in a popover. Both take the same props.

import { ColorPicker, ColorPickerPopover } from "@booleanpress/ui/color-picker"

export function ButtonColour() {
  return (
    <>
      <ColorPicker defaultValue="#276def" aria-label="Button colour" />
      <ColorPickerPopover defaultValue="#276def" aria-label="Link colour" />
    </>
  )
}

The value is a hex string (#rgb, #rrggbb, with or without alpha); a value that is not hex is ignored and the picker shows black or the colour it had. It is uncontrolled with defaultValue (#000000 by default), or controlled with value and onValueChange, which fires while the colour moves; onValueCommit fires once, when a drag, a key press or an edit of the hex field ends, which suits saving. alpha adds an alpha slider; the value then reads #rrggbbaa while the colour is not opaque. swatches adds preset colours under the picker: hex strings, or { value, label } to give each a spoken name. orientation="vertical" stands the hue (and alpha) slider beside the area. size and variant set the hex field's size and look and follow the provider; name submits the value in a form, as lower-case hex; a reset of the form brings back defaultValue, and a disabled picker is not submitted. Name the picker with aria-label or aria-labelledby; it is "Colour picker" by default, the provider string colorPicker. ColorSwatch draws a colour square on its own, for a list of saved colours.

Examples

Basic

The area, the hue slider, a preview and the hex field, in the page.

import { ColorPicker } from "@booleanpress/ui/color-picker"

export default function ColorPickerBasic() {
  return <ColorPicker defaultValue="#276def" aria-label="Button colour" />
}

In a popover

ColorPickerPopover opens the picker from a 36 px swatch button named with the label and the colour.

import { ColorPickerPopover } from "@booleanpress/ui/color-picker"

export default function ColorPickerInPopover() {
  return (
    <div className="flex items-center gap-3">
      <ColorPickerPopover defaultValue="#ff0000" aria-label="Accent colour" />
      <span className="text-sm/normal text-foreground">Accent colour</span>
    </div>
  )
}

Vertical hue slider

orientation="vertical" stands the hue and alpha sliders beside the area, the highest value at the top.

import { ColorPicker } from "@booleanpress/ui/color-picker"

export default function ColorPickerVertical() {
  return <ColorPicker orientation="vertical" alpha defaultValue="#ff0000" aria-label="Header colour" className="max-w-sm" />
}

Controlled

value and onValueChange show the hex while it moves; onValueCommit reports it once each change ends.

import { useState } from "react"
import { ColorPicker } from "@booleanpress/ui/color-picker"

export default function ColorPickerControlled() {
  const [colour, setColour] = useState("#0f766e")
  const [saved, setSaved] = useState("#0f766e")

  return (
    <div className="flex w-full max-w-xs flex-col gap-3">
      <p className="text-center font-mono text-sm/normal text-muted-foreground">
        onValueChange: {colour}
        <br />
        onValueCommit: {saved}
      </p>
      <ColorPicker value={colour} onValueChange={setColour} onValueCommit={setSaved} aria-label="Email header colour" />
    </div>
  )
}

Swatches

swatches adds preset brand colours, each named by its label; the one matching the colour is chosen.

import { ColorPicker } from "@booleanpress/ui/color-picker"

const BRAND = [
  { value: "#020617", label: "Ink" },
  { value: "#2563eb", label: "Mailer blue" },
  { value: "#0f766e", label: "Teal" },
  { value: "#16a34a", label: "Delivered green" },
  { value: "#ca8a04", label: "Queued amber" },
  { value: "#dc2626", label: "Bounce red" },
  { value: "#9333ea", label: "Violet" },
]

export default function ColorPickerSwatches() {
  return <ColorPicker defaultValue="#2563eb" swatches={BRAND} aria-label="Brand colour" />
}

With alpha

alpha adds an alpha slider over checks; the value carries an alpha pair.

import { ColorPicker } from "@booleanpress/ui/color-picker"

export default function ColorPickerAlpha() {
  return <ColorPicker alpha defaultValue="#2563eb99" aria-label="Overlay colour" />
}

Disabled

disabled dims the picker and takes its sliders and field out of the tab order.

import { ColorPicker } from "@booleanpress/ui/color-picker"

export default function ColorPickerDisabled() {
  return <ColorPicker disabled defaultValue="#64748b" aria-label="Locked theme colour" />
}

Accessibility

Semantics
The picker is a role="group". The area follows React Aria's ColorArea: its thumb holds two visually hidden range inputs, saturation (across) and brightness (up), so each is a slider with its own name and value text. The hue and alpha sliders are range inputs too, so touch screen readers can swipe them. The hex field is a text input; the swatches are a radio group.
Labels
Name the picker with aria-label or aria-labelledby ("Colour picker" by default). The sliders are named "Saturation", "Brightness", "Hue" and "Alpha", the field "Hex colour" and the swatches "Preset colours" (the provider strings saturation, brightness, hue, alpha, hexColor and colorSwatches); values are read as percentages and degrees in the provider's locale. A swatch reads its label, else its hex.
Focus
Tab moves through the area (one stop: the input used last, saturation at first), the hue slider, the alpha slider, the hex field and the swatches (one stop). A thumb shows the 1px --ring outline 2px outside while it has keyboard focus. In a popover, focus moves into the picker on open and back to the swatch button on close.
Known limits
  • Colour is chosen by sight. The hex field and the swatches' names are the ways to choose an exact colour without it.
  • There is no eyedropper and no other format than hex (RGB, HSL); convert the hex value yourself.
  • In right-to-left pages the area and the horizontal sliders run from right to left, and the arrow keys follow them.

Keyboard

Keyboard
KeyBehaviour
←or→On the area, lowers or raises the saturation by 1 and moves focus to the saturation input; on a slider, lowers or raises its value. Swapped in right-to-left pages, where the area and the sliders run from right to left.
↑or↓On the area, raises or lowers the brightness by 1 and moves focus to the brightness input; on a slider, raises or lowers its value.
Shift→With any arrow key, moves 10 that way.
Page UporPage DownOn the area, raises or lowers the brightness by 10; on a slider, its value by 10.
HomeorEndOn the area, lowers or raises the saturation by 10 (as React Aria's ColorArea); on a slider, sets its minimum or maximum.
EnterIn the hex field, applies the colour typed and shows its hex again.
←or→or↑or↓In the swatches, chooses the previous or next preset colour.

API

ColorPicker

ColorPicker props
PropTypeDefaultDescription
alphabooleanfalseAdds an alpha slider; the value then carries an alpha pair (#rrggbbaa) when the colour is not opaque.
defaultValuestring#000000The colour at the start, when it controls itself. #000000 by default.
disabledbooleanfalseDims the picker and ignores the pointer and the keyboard.
formstringThe id of the form the value belongs to, for a control placed outside it.
namestringThe form field name; the hex value is submitted with the form.
onValueChange((value: string) => void)Called with the new hex colour while it changes.
onValueCommit((value: string) => void)Called with the hex colour once a drag, a key press or an edit of the hex field ends.
orientation"horizontal" | "vertical"horizontalvertical puts the hue (and alpha) slider upright beside the area. horizontal by default.
size"default" | "sm" | "lg"The hex field's and swatches' size. Defaults to the provider's controlSize.
swatchesreadonly (string | { value: string; label?: string; })[]Preset colours shown as swatches under the picker: hex strings, or { value, label } to name them.
valuestringThe colour as hex (#3b82f6, or #3b82f680 with alpha), when you control it.
variant"default" | "filled"The hex field's look. Defaults to the provider's fieldVariant.

ColorPickerPopover

ColorPickerPopover props
PropTypeDefaultDescription
align"center" | "start" | "end"startThe popover's alignment against the swatch. start by default.
alphabooleanfalseAdds an alpha slider; the value then carries an alpha pair (#rrggbbaa) when the colour is not opaque.
defaultOpenbooleanWhether the popover starts open.
defaultValuestring#000000The colour at the start, when it controls itself. #000000 by default.
disabledbooleanfalseDims the picker and ignores the pointer and the keyboard.
formstringThe id of the form the value belongs to, for a control placed outside it.
namestringThe form field name; the hex value is submitted with the form.
onOpenChange((open: boolean) => void)Called when the popover opens or closes.
onValueChange((value: string) => void)Called with the new hex colour while it changes.
onValueCommit((value: string) => void)Called with the hex colour once a drag, a key press or an edit of the hex field ends.
openbooleanWhether the popover is open, when you control it.
orientation"horizontal" | "vertical"vertical puts the hue (and alpha) slider upright beside the area. horizontal by default.
side"top" | "bottom" | "left" | "right"The side of the swatch the popover opens on. Below by default.
size"default" | "sm" | "lg"The hex field's and swatches' size. Defaults to the provider's controlSize.
swatchesreadonly (string | { value: string; label?: string; })[]Preset colours shown as swatches under the picker: hex strings, or { value, label } to name them.
valuestringThe colour as hex (#3b82f6, or #3b82f680 with alpha), when you control it.
variant"default" | "filled"The hex field's look. Defaults to the provider's fieldVariant.

ColorSwatch

Renders a span and passes it every other prop.

ColorSwatch props
PropTypeDefaultDescription
colorrequiredstringAny CSS colour: #3b82f6, #3b82f680, rgb(…).

Every part takes className, merged with its defaults by cn(), and ref, which reaches the element it renders.

Data attributes: data-slot="color-picker-preview" (ColorPicker), data-slot="color-picker-trigger" (ColorPickerPopover), data-slot="color-swatch" (ColorSwatch), and data-orientation, data-size, data-disabled, data-channel.

Provider strings: hue, alpha, saturation, brightness, hexColor, colorPicker, colorSwatches (BooleanUIProvider's strings).

Theming

The area, the sliders and the swatches carry the colour itself; their edge is a 1px --border ring inside and the checks behind transparency are --muted on --background. The thumbs are 16 px circles with a white 3px ring in both themes, so they show on any colour. A chosen swatch has a 2px --ring ring 2px away. The hex field is an input.

The tokens its classes read. Change their values in your theme, and it follows.

Theme tokens
TokenUsed for
--ringoutline, focus ring