Color picker

A swatch that grows into a picker with format morphing, eyedropper, saved swatches, and contrast readout.

pnpm dlx shadcn@latest add @uiarc/color-picker
Live · keyboard ready
  • Theme editors and brand settings where users choose an exact color.
  • Design tools that need hex, RGB, HSL, and OKLCH input plus opacity.
  • Any color choice where contrast against a background should be visible while picking.
  • Use segmented-control or radio-group when the choice is between a few fixed colors.
  • Use input for a plain hex field with no visual picking.
  • Use chip-group for tagging items with a small preset palette.

Installation

Add Color picker with the shadcn CLI, or copy the source by hand.

pnpm dlx shadcn@latest add @uiarc/color-picker

Adds the component and its local dependencies, and installs motion, lucide-react. First time? Add the @uiarc registry to components.json, or use the full URL:

example.tsx
import { useState } from "react";import { ColorPicker } from "@/registry/components/color-picker/color-picker";import type { ColorSwatch } from "@/registry/components/color-picker/color-picker"; export function BrandColor() {  const [color, setColor] = useState("#2F6BFF");  const [swatches, setSwatches] = useState<ColorSwatch[]>([    { id: "ink", color: "#17171A" },    { id: "sky", color: "#3A8DFF" },  ]);   return (    <ColorPicker      label="Accent"      value={color}      onValueChange={setColor}      background="#FFFFFF"      swatches={swatches}      onSwatchesChange={setSwatches}    />  );}

API reference

3 parts. The first is the root.

ColorPicker

A swatch trigger that grows into a picker with a saturation area, hue and opacity sliders, a typed value, contrast readout, and saved swatches.

PropTypeDefaultDescription
valuestring–Controlled color in hex, rgb(), hsl(), or oklch().
defaultValuestring"#2F6BFF"Uncontrolled starting color.
onValueChange(hex: string) => void–Receives the color as uppercase hex, with two alpha digits when it is not opaque.
backgroundstring"#FFFFFF"The background the color will sit on, for the WCAG contrast readout.
labelstring"Color"Name shown on the swatch, such as "Accent".
swatchesColorSwatch[]–Controlled saved swatches: { id, color }.
defaultSwatchesColorSwatch[]–Uncontrolled starting swatches. Defaults to none.
onSwatchesChange(swatches: ColorSwatch[]) => void–Called when swatches are added, reordered, or removed.
maxSwatchesnumber7Most saved swatches. Saving past it drops the oldest.
defaultFormat"hex" | "rgb" | "hsl" | "oklch""hex"Format shown in the text field first.
classNamestring–Extra class on the root.

parseColor

Reads hex, rgb(), hsl(), and oklch() in modern or comma syntax into { h, s, v, a }. Returns null for anything else.

PropTypeDefaultDescription
inputRequiredstring–Color text.
huenumber0Hue to keep for grays, where hue is undefined.

toHex

Formats { h, s, v, a } as uppercase hex, adding alpha digits below full opacity.

PropTypeDefaultDescription
hsvaRequiredHsva–Hue in degrees, saturation, value, and alpha from 0 to 1.
Arrow keys
Move the saturation and brightness thumb in 2D, or the focused hue or opacity slider; Shift takes steps of ten.
PageUporPageDown
Step a slider by ten.
HomeorEnd
Move a slider to its minimum or maximum; in the swatch row, jump to the first or last swatch.
Enter
Applies the typed value in the text field; an invalid value shakes the field and shows an error.
Alt+ArrowLeftorArrowRight
Moves the focused swatch.
DeleteorBackspace
Removes the focused swatch.
Escape
Discards a typed draft first, then closes the panel and returns focus to the trigger.
  • The trigger has aria-haspopup="dialog" and aria-expanded; the panel is a labelled dialog that turns inert while closing.
  • The area thumb is a role="slider" with aria-roledescription="2D slider" and a value text naming saturation and brightness; hue and opacity are sliders with degree and percent value texts.
  • The text field sets aria-invalid and points to a polite error message; the contrast ratio and its WCAG grade are also written out for screen readers.
  • Saved swatches are a horizontal listbox with a roving tabindex, aria-selected on the current color, and labels that explain move and delete.
  • The panel grows out of the swatch with an animated clip-path inset on a spring, and its drop shadow follows the clipped shape.
  • Thumbs chase the pointer on a quick spring with slight bounce, and the contrast ratio counts to its new value.
  • The format button morphs the text between hex, RGB, HSL, and OKLCH with text-morph; swatches pop in and reorder with layout springs.
  • Reduced motion jumps the thumbs, fades the panel instead of clipping it, and skips the format morph and shake.
  • The panel is min(19rem, 100vw - 32px) wide and opens down and to the right of the swatch, so leave room there.
  • Below 360px viewport width the "against background" caption hides to keep the contrast row on one line.
  • The area and sliders use touch-action: none with pointer capture, so dragging on touch never scrolls the page.
  • Colors are converted in plain math (HSV, HSL, OKLCH) with no color library.
  • The panel mounts only while open, and a ResizeObserver measures it for the clip-path animation.
  • Thumbs animate motion values, so dragging does not re-render the slider tracks.

Notes for AI

Give your coding assistant the Markdown reference instead of screenshots.

  • Use for picking a single brand or theme color with an alpha channel. onValueChange always returns hex; parse it elsewhere if you need another format.
  • Pass the real surface color as background so the contrast readout reflects where the color will be used.
  • The eyedropper button appears only where window.EyeDropper exists (Chromium desktop).
  • Imports TextMorph from the Arc text-morph component, so install that alongside it.

The full library index for assistants is at /llms.txt.