Color picker
A swatch that grows into a picker with format morphing, eyedropper, saved swatches, and contrast readout.
inputsnew
Preview
Color picker preview
Live · keyboard ready
When to use
- 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.
When not to use
- 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.
shadcn CLI
Also installs (2)
- manicat-foundation
- text-morph
Manual install(+)
registry.jsonjson
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "color-picker",
"type": "registry:ui",
"title": "Color picker",
"description": "A swatch that grows into a picker with format morphing, eyedropper, saved swatches, and contrast readout.",
"categories": [
"inputs"
],
"dependencies": [
"lucide-react",
"motion"
],
"registryDependencies": [
"https://manicat.viraza.net/r/manicat-foundation.json",
"https://manicat.viraza.net/r/text-morph.json"
],
"files": [
{
"path": "registry/components/color-picker/color-picker.tsx",
"type": "registry:component",
"target": "@components/manicat/color-picker/color-picker.tsx"
},
{
"path": "registry/components/color-picker/color-picker.module.css",
"type": "registry:component",
"target": "@components/manicat/color-picker/color-picker.module.css"
},
{
"path": "registry/components/text-morph/text-morph.tsx",
"type": "registry:component",
"target": "@components/manicat/text-morph/text-morph.tsx"
},
{
"path": "registry/components/text-morph/text-morph.module.css",
"type": "registry:component",
"target": "@components/manicat/text-morph/text-morph.module.css"
}
],
"docs": "Docs and live preview: https://uiarc.dev/components/color-picker",
"meta": {
"tier": "free",
"kind": "component",
"docs": "https://manicat.viraza.net/components/color-picker",
"markdown": "https://uiarc.dev/components/color-picker/markdown",
"tags": [
"inputs",
"new"
]
}
}Usage
Install the component, then copy this into your app. Change the content and the props to fit your screen.
example.tsxtsx
"use client";
import { useState } from "react";
import { ColorPicker } from "@/components/manicat/color-picker/color-picker";
export function ColorPickerExample() {
const [value, setValue] = useState("#7747ff");
return <ColorPicker label="Accent" value={value} onValueChange={setValue} />;
}
Examples
Real screens rather than isolated props. Switch to Code to copy a whole example into your project.
Brand accent Choose a colour and see it applied to a live swatch.
Brand accent
Choose a colour and see it applied to a live swatch. preview
Live · keyboard ready
brand-accent-choose-a-colour-and-see-it-applied-to-a-live-swatch-.tsxtsx
"use client";
import { useState } from "react";
import { ColorPicker } from "@/components/manicat/color-picker/color-picker";
export function ColorPickerBrandExample() {
const [value, setValue] = useState("#7747ff");
return (
<div className="grid w-full max-w-xs gap-3">
<ColorPicker label="Accent" value={value} onValueChange={setValue} />
<div className="h-10 rounded-[var(--radius-control)]" style={{ background: value }} />
</div>
);
}
Keyboard interactions
| Keys | Action |
|---|---|
| Arrow keys | Move the saturation and brightness thumb in 2D, or the focused hue or opacity slider; Shift takes steps of ten. |
| PageUp / PageDown | Step a slider by ten. |
| Home / End | 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 + ArrowLeft / ArrowRight | Moves the focused swatch. |
| Delete / Backspace | Removes the focused swatch. |
| Escape | Discards a typed draft first, then closes the panel and returns focus to the trigger. |
Accessibility
- 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.
Motion
- 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.
Responsive behavior
- 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.
Performance
- 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
- 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.