Color Picker
A swatch-triggered popover that presents a grid of preset colors — pick one, fire onValueChange, mark the selection with a check.
- Status
- Since
0.1.0- Accessibility pattern
- APG dialog (non-modal) picker
Last updated
Install
Configure registry credentials first using the Quickstart. Before copying this item or any transitive registry dependency, verify the signed manifest and save the exact bytes for Color Picker before installation. Retain the digest printed by this command.
pnpm exec vegastack-design verify color-pickerInstall with the pinned shadcn CLI. Bare shadcn does not verify VegaStack signature or integrity.
pnpm dlx shadcn@4.21.0 add @vegastack/color-pickerRun the exact offline --post-write command printed by the preflight, using its saved item path and independently retained --expected-integrity digest. Use the saved preflight for every transitive registry dependency too; stop on any mismatch before using the components.
The same command installs the registry items it composes: @vegastack/button, @vegastack/popover, @vegastack/use-list-nav.
Usage
import { useState } from "react";
import { ColorPicker } from "@/components/ui/color-picker";
function Example() {
const [color, setColor] = useState("blue");
return <ColorPicker value={color} onValueChange={setColor} />;
}ColorPicker is controlled — pass value (matched against each option's name) and onValueChange (called with the picked color's name). The trigger is a rounded-md outline control with a rounded-sm fill chip showing the current selection; opening it reveals the palette. Only the in-grid preset swatches are rounded-full.
Examples
Palette
The colors are data backed by semantic tokens. The default palette exposes stable hue names
(green, blue, rose, …), and each swatch uses VegaStack semantic CSS variables — info,
status, neutral, and chart tokens — rather than raw Tailwind palette variables. Pass your own
colors to override:
import { ColorPicker, type ColorOption } from "@/components/ui/color-picker";
const palette: ColorOption[] = [
{ name: "info", label: "Info", color: "var(--color-info)" },
{ name: "accent", label: "Accent", color: "var(--color-accent)" },
{ name: "danger", label: "Danger", color: "var(--color-destructive)" },
];
<ColorPicker
value="blue"
onValueChange={setColor}
colors={palette}
columns={3}
/>;Each ColorOption is { name, label, color }:
name— the stable value the picker emits and matches selection against.label— the swatch's accessible name (aria-label) and tooltip.color— a CSS color value. Prefer semantic variables likevar(--color-info)orvar(--color-chart-2). Arbitrary CSS colors are reserved for dynamic user-authored palette data; this is the one place the design system renders a dynamic swatch fill via inlinestyle={{ backgroundColor }}. Every other style (sizing, borders, focus, spacing) uses semantic tokens.
DEFAULT_COLORS
When you omit colors, the picker renders the exported DEFAULT_COLORS palette — 12 named options,
each mapped to a VegaStack semantic token (status, neutral, or chart series) so the registry
component never ships raw Tailwind palette variables:
name | label | color token |
|---|---|---|
gray | Gray | var(--color-primary) |
red | Red | var(--color-destructive) |
orange | Orange | var(--color-chart-4) |
amber | Amber | var(--color-warning) |
yellow | Yellow | var(--color-chart-7) |
green | Green | var(--color-success) |
teal | Teal | var(--color-chart-2) |
sky | Sky | var(--color-info) |
blue | Blue | var(--color-chart-8) |
indigo | Indigo | var(--color-chart-3) |
pink | Pink | var(--color-chart-5) |
rose | Rose | var(--color-chart-6) |
States
Default (a selection chosen), empty (no selection yet — a transparent trigger), and disabled.
Custom palette
Pass colors to render your own curated set — here a 3-color tag palette in a 3-wide grid.
Hue palette, inline
HUE_COLORS is the ten tag hues — the exact colours Chip, Avatar and IconGlyph draw — so
the stored value ("blue", "purple"…) is a hue those components take. variant="inline" renders
the swatches in the page with no trigger, and onClear adds a separate clear action, selected while
value matches no hue. Pick columns so the row fits its container (six wraps the eleven swatches
onto two rows at a phone's width).
Columns
columns sets the swatch-grid width (default 6), driving the dynamic --swatch-cols grid
template. Below: the default 12-color palette at columns={4}, 7, and 14.
API Reference
ColorPicker
| Prop | Type | Default | Description |
|---|---|---|---|
aria-label | string | "Pick a color" | Accessible name for the trigger button. |
className | string | — | Extra classes for the trigger swatch button. |
clearLabel | string | "None" | The clear action's accessible name. |
closeOnSelect | boolean | false | Close after picking a swatch. |
colors | readonly ColorOption[] | DEFAULT_COLORS | The palette to render. |
columns | number | 6 | Number of columns in the swatch grid. |
disabled | boolean | false | Disables the trigger and every swatch. |
onClear | (() => void) | — | Adds a separate clear action, selected while value matches no option. |
onOpenChange | ((open: boolean) => void) | — | Popup state callback. |
onValueChange | ((value: string) => void) | — | Fired when a swatch is picked, with the chosen ColorOption.name. |
open | boolean | — | Controlled popup state. |
ref | React.Ref<HTMLButtonElement> | — | Ref forwarded to the trigger button — the component's focusable root (the popover content is portaled, so the trigger is the stable host element to focus/measure). |
trigger | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | — | Custom trigger composed through Base UI. |
value | string | — | The currently selected color, matched against each ColorOption.name. When it matches an option,
that swatch marks its outer cell and the trigger renders its color. |
variant | "inline" | "popover" | "popover" | popover — a swatch trigger that opens the grid. inline — the swatch grid itself, in the
page (a settings row), with no trigger. |
Data attributes and CSS variables on ColorPicker
| Attribute | Values |
|---|---|
data-slot | "color-picker" | "color-picker-content" | "color-picker-swatch" |
data-variant | mirrors a prop or state value |
--swatch-cols | CSS custom property |
ColorOption
The shape of each entry in a colors palette.
| Prop | Type | Default | Description |
|---|---|---|---|
color* | string | — | Any CSS color the swatch renders as its background. Prefer semantic design-token variables
(var(--color-info), var(--color-chart-2), …). Consumer-provided arbitrary colors are
allowed only as dynamic user data, so the value is applied via inline style (the sanctioned
exception — see the file header). |
label* | string | — | Human-readable label, used as the swatch's accessible name (aria-label) and its tooltip
(title) — e.g. "Blue". |
name* | string | — | Stable identifier for the color — this is the value onValueChange emits and the value matched
against value to determine the selected swatch (e.g. "blue"). |
Accessibility
- The trigger is a
Buttonwith an accessible name (aria-label, default"Pick a color"); customize it to describe what the color is for (e.g."Label color"). - Each preset is a
<button>labelled by its color name, grouped in arole="group"labelled"Colors". The selected swatch carriesaria-pressed="true"and uses a persistent outer-cell border, leaving the colour fill unobscured. - The picker opens on trigger click; focus moves into the grid and is restored to the trigger on close.
- Dismiss is built in — clicking outside the panel or pressing Esc closes it.
- Every control keeps the global keyboard-focus background tint from
base.css; no component adds a focus ring or changes its border on focus.
| Key | Action |
|---|---|
| Enter / Space | Open the picker (on the trigger), or select the focused swatch. |
| ← / → | Move one swatch within the grid. |
| ↑ / ↓ | Move one row — by columns swatches. |
| Home / End | Jump to the first or last swatch in the grid. |
| Tab / Shift + Tab | Leave the grid — roving tabindex makes it one tab stop. |
| Esc | Close the picker and return focus to the trigger. |
| Contract | States tested |
|---|---|
| Behaviour | default, active, disabled, error, pressed, range, selected, success |
| Accessibility | disabled, keyboard, labeled, pressed |
| Visual | default, hover, error, success |