Color Picker
Figma-style color picker — saturation/value canvas, hue and alpha sliders, eyedropper, HEX/RGB/HSL inputs, swatch libraries, and a WCAG contrast checker with a live boundary curve. Supports light/dark, RTL, and mobile touch targets.
Playground
Installation
pnpm add @tessinaui/uiUsage
import { ColorPicker, ColorPickerPanel } from "@tessinaui/ui";{/* Inline panel */}
<ColorPickerPanel defaultValue="#711DC8" onValueChange={setColor} />
{/* Button-triggered popover */}
<ColorPicker value={color} onValueChange={setColor} />
{/* Contrast checker against white text, AA target */}
<ColorPickerPanel
defaultValue="#711DC8"
contrastCheck={{ against: "#FFFFFF", target: "AA" }}
/>Examples
Default
The popover trigger with quick swatches.
Libraries
Named swatch groups render in a second tab.
Contrast check
The badge shows the live ratio; the dotted boundary on the canvas marks where the target threshold passes.
Controlled trigger
API Reference
ColorPickerPanel
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — | Controlled hex value (#RRGGBB or #RRGGBBAA) |
defaultValue | string | "#711DC8" | Uncontrolled initial value |
onValueChange | (value: string) => void | — | Fires on every change (drag included); 8-digit hex only when showAlpha and alpha < 1 |
showAlpha | boolean | true | Alpha rail + opacity input |
showEyedropper | boolean | true | Eyedropper button (auto-hidden without browser support) |
showInputs | boolean | true | Model select + channel inputs row |
defaultModel | "hex" | "rgb" | "hsl" | "hex" | Initial input model |
swatches | ColorSwatchItem[] | — | Quick swatches under the inputs |
swatchesLabel | string | "Swatches" | Heading above the quick swatches |
libraries | ColorLibrary[] | — | Named groups — presence renders the Custom | Libraries tabs |
contrastCheck | ContrastCheckOptions | — | Presence renders the ratio badge + boundary curve |
size | "sm" | "md" | "md" | Panel width — 280px / 320px |
rounded | "none" | "sm" | "md" | "lg" | "md" | Corner radius |
disabled | boolean | false | Disables all interaction |
dir | "ltr" | "rtl" | "ltr" | Chrome direction (color geometry stays LTR) |
ColorPicker (popover trigger)
Extends every ColorPickerPanel prop, plus:
| Prop | Type | Default | Description |
|---|---|---|---|
placeholder | string | "Pick a color" | Trigger label when no value renders |
triggerVariant | ButtonProps["variant"] | "outline" | Trigger button variant |
triggerSize | ButtonProps["size"] | "sm" | Trigger button size |
side / align | positioning | "bottom" / "start" | Popover placement |
open / defaultOpen / onOpenChange | — | — | Popover open state |
ColorPickerSwatch
| Prop | Type | Default | Description |
|---|---|---|---|
color | string | — | Hex color of the tile |
name | string | — | Accessible name (announced instead of the hex) |
selected | boolean | false | Selection ring |
onSelect | (value: string) => void | — | Click handler |
size | "sm" | "md" | "md" | Tile size |
ColorPickerSkeleton
| Prop | Type | Default | Description |
|---|---|---|---|
size | "sm" | "md" | "md" | Trigger-shaped placeholder height |
Types
interface ColorSwatchItem {
value: string; // "#RRGGBB" | "#RRGGBBAA"
name?: string; // accessible name
}
interface ColorLibrary {
name: string; // group heading
colors: ColorSwatchItem[];
}
interface ContrastCheckOptions {
against: string; // comparison color (hex)
target?: "AA" | "AAA"; // default "AA"
largeText?: boolean; // 3.0 / 4.5 thresholds instead of 4.5 / 7.0
}The color utilities hexToHsva, hsvaToHex, and contrastRatio are also
exported for app-side use.
Notes
- Value model — the external format is a hex string, but the picker keeps an internal HSVA state: hue survives passing through black, white, and gray (a plain hex round-trip would collapse it).
- RTL & color geometry — the panel chrome (tabs, labels, inputs, swatches)
mirrors with
dir="rtl"; the canvas and rails deliberately stay LTR because color geometry (white left, red-origin rainbow) is a convention shared by every design tool. - Contrast checking — translucent colors are measured as rendered, composited over the comparison color. The boundary curve is recomputed per hue via binary search on the WCAG luminance, which is monotonic in brightness.
- Browser support — the eyedropper uses the native
EyeDropperAPI (Chromium only) and hides itself elsewhere. - Keyboard — the canvas and both rails are sliders: arrows step by 1%,
Shift-arrows by 10%, Home/End jump to the extremes; values are announced via
aria-valuetext. - Mobile — inputs and rail thumbs expand to 44px touch targets on small screens (WCAG 2.5.5) while desktop keeps the compact density.
Collapsible
A single disclosure that animates open and closed — built on Base UI's Collapsible primitive with four variants, five sizes, intent accents, and full RTL support.
Combobox
Filterable select with single and multi-select modes, grouped options, chips, loading and empty states, and all Field variants.