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- 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-pickerAdds 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:
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.
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.
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.
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.