Color picker

A swatch that grows into a picker with format morphing, eyedropper, saved swatches, and contrast readout.

inputsnew
Preview
Color picker preview

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

Keyboard interactions

KeysAction
Arrow keysMove the saturation and brightness thumb in 2D, or the focused hue or opacity slider; Shift takes steps of ten.
PageUp / PageDownStep a slider by ten.
Home / EndMove a slider to its minimum or maximum; in the swatch row, jump to the first or last swatch.
EnterApplies the typed value in the text field; an invalid value shakes the field and shows an error.
Alt + ArrowLeft / ArrowRightMoves the focused swatch.
Delete / BackspaceRemoves the focused swatch.
EscapeDiscards 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.