ColorPicker
A color selection component with presets, format conversion, and screen picker support.
Installation
npx vayu-ui-cli@latest add color-pickerUsage
Choose a primary color for your brand.
Select a color for your theme.
This color does not meet contrast requirements.
Anatomy
ColorPicker
├── ColorPicker.Label -- Accessible label linked to the input
├── ColorPicker.Description -- Helper text below the label
├── ColorPicker.Error -- Error message (renders only in error state)
├── ColorPicker.Trigger -- Color swatch button that toggles the dropdown
├── ColorPicker.Input -- Text input for hex/rgb/hsl values
├── ColorPicker.CopyButton -- Copies current color to clipboard
└── ColorPicker.Content -- Dropdown panel with positioning
├── ColorPicker.Palette -- Native browser color picker
├── ColorPicker.Eyedropper -- Screen color picker (Chrome/Edge only)
└── ColorPicker.Presets -- Grid of preset color swatches
ColorPicker.Swatches -- Standalone swatch grid (no context required)Accessibility
Keyboard support
| Key | Context | Behavior |
|---|---|---|
Enter | Trigger | Opens the dropdown |
Space | Trigger | Opens the dropdown |
Escape | Content (open) | Closes dropdown, returns focus |
Tab | Any | Moves focus to next element |
ARIA attributes
| Component | Attribute | Value |
|---|---|---|
| Trigger | aria-expanded | true / false |
| Trigger | aria-controls | Dropdown ID |
| Trigger | aria-labelledby | Label ID |
| Trigger | aria-haspopup | "dialog" |
| Input | aria-labelledby | Label ID |
| Input | aria-describedby | Description + error IDs |
| Input | aria-invalid | true when in error state |
| Content | role | "dialog" |
| Content | aria-modal | false |
| Error | role | "alert" |
| Error | aria-live | "polite" |
| Presets / Swatches | role | "listbox" / "option" |
| Presets / Swatches | aria-selected | true on active swatch |
| CopyButton | aria-label | Dynamic: "Copy color" / "Copied!" |
| Eyedropper | aria-busy | true while picking |
Focus behavior
- Trigger receives focus via
Tab - Content auto-focuses on open via
requestAnimationFrame Escapecloses dropdown and returns focus to the trigger- Click outside closes dropdown and maintains focus
- All interactive elements have visible
focus-visiblering
Screen reader behavior
- The trigger announces: "Select color. Current color:
{hex value}" - The label is associated with the input via
htmlForandaria-labelledby - Description text is linked to the input via
aria-describedby - In error state, the error message is announced via
aria-live="polite"androle="alert" - Preset swatches are announced as a listbox with options: "Select color
{hex}" - The selected swatch is indicated by
aria-selected="true" - The copy button announces "Copied!" after a successful clipboard copy
- The eyedropper announces its label and sets
aria-busyduring active picking
Component Folder Structure
ColorPicker/
├── ColorPicker.tsx
├── ColorPickerContent.tsx
├── ColorPickerCopyButton.tsx
├── ColorPickerDescription.tsx
├── ColorPickerError.tsx
├── ColorPickerEyeDropper.tsx
├── ColorPickerInput.tsx
├── ColorPickerLabel.tsx
├── ColorPickerPalette.tsx
├── ColorPickerPresets.tsx
├── ColorPickerSwatches.tsx
├── ColorPickerTrigger.tsx
├── hooks.ts
├── index.ts
├── types.ts
├── utils.ts
└── README.mdProps
ColorPicker
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — | Controlled color value (hex) |
defaultValue | string | "#3b82f6" | Uncontrolled default color |
onChange | (color: string) => void | — | Callback when color changes |
format | "hex" | "rgb" | "hsl" | "hex" | Display format in the text input |
presets | string[] | 22 defaults | Custom preset color array |
disabled | boolean | false | Disable all interactions |
validationState | "default" | "error" | "warning" | "success" | "default" | Input validation state |
defaultOpen | boolean | false | Initial dropdown open state |
open | boolean | — | Controlled dropdown state |
onOpenChange | (open: boolean) => void | — | Callback when dropdown toggles |
ColorPicker.Label
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | Label text |
optional | boolean | false | Shows "(optional)" text |
ColorPicker.Description
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | Helper text |
ColorPicker.Error
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | Error message (only renders in error state) |
ColorPicker.Trigger
| Prop | Type | Default | Description |
|---|---|---|---|
size | "sm" | "md" | "lg" | "md" | Swatch size (32/48/64 px) |
ColorPicker.Input
| Prop | Type | Default | Description |
|---|---|---|---|
placeholder | string | "#000000" | Input placeholder |
ColorPicker.CopyButton
| Prop | Type | Default | Description |
|---|---|---|---|
copiedText | string | "Copied!" | Text shown after copying |
ColorPicker.Content
| Prop | Type | Default | Description |
|---|---|---|---|
side | "top" | "bottom" | "bottom" | Preferred vertical position |
align | "start" | "center" | "end" | "start" | Horizontal alignment to trigger |
sideOffset | number | 8 | Distance from trigger (px) |
ColorPicker.Palette
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | "Pick a color" | Accessible label text |
ColorPicker.Eyedropper
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | "Pick from screen" | Button text |
unsupportedText | string | "Eyedropper not supported in this browser" | Fallback message |
ColorPicker.Presets
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | "Preset Colors" | Section label |
colors | string[] | Context presets | Override preset colors |
columns | number | 8 | Grid column count |
ColorPicker.Swatches
| Prop | Type | Default | Description |
|---|---|---|---|
colors | string[] | — | Array of hex colors |
value | string | — | Currently selected color |
onChange | (color) => void | — | Callback on swatch click |
size | "sm" | "md" | "lg" | "md" | Swatch size (24/32/40 px) |
columns | number | 8 | Grid column count |
label | string | — | Section label |
disabled | boolean | false | Disable all swatches |