# Color Picker

> A React color picker with a saturation and brightness square, hue and opacity sliders, HEX / RGB / HSL inputs, swatches and an eyedropper.

- Type: React component (Inputs)
- Access: Pro (the source needs a Wingo UI Pro plan, see https://wingo-ui.com/pricing)
- Version: 1.0.0
- Install: `npx wingo-ui@latest add color-picker`
- Docs: https://wingo-ui.com/components/color-picker

## Interaction

Click the field (or the round swatch button) to open a panel under it: drag in the square to set saturation (left to right) and brightness (bottom to top), drag the hue bar, and with alpha the opacity bar; the thumb grows a little while you drag and the value updates live, once per frame. Below them the HEX / RGB / HSL switch changes which fields show (the output keeps its format); typed values apply on Enter or when the field loses focus, text that is not a color turns the field red and goes back to the last good color, ArrowUp / ArrowDown in a number field step by 1 (Shift by 10), and a 3-digit hex expands ("F60" is #FF6600). Tap a preset swatch to use it: a ring slides to it. In Chrome and Edge a pipette button picks any pixel on screen. Keyboard: Enter, Space or ArrowDown on the trigger opens the panel with focus in the square; in the square ArrowLeft / ArrowRight change saturation and ArrowUp / ArrowDown brightness by 1% (Shift 10%), PageUp / PageDown step brightness by 10%, Home / End go to no or full saturation; the hue and opacity bars take the arrow keys, Home and End; the swatches are a radio group moved with the arrow keys; Escape closes the panel and returns focus to the trigger. Clicking the label focuses the trigger. On a phone the panel opens in a bottom sheet, full width, with a taller square, 28px thumbs and larger swatches; the changes are a draft until Done, and dragging the sheet away keeps the old color. Disabled and read-only pickers never open. ColorPickerPanel (the panel alone, with the same value, format, alpha, swatches and labels props) is exported for your own popovers and pages, with the helpers parseColor (hex, rgb(), hsl() and the 16 basic CSS names, no canvas), formatColor, hsvaToRgba, rgbaToHsva and COLOR_PICKER_SWATCHES.

## Installation

```bash
npx wingo-ui@latest add color-picker
```

Pro item: run `npx wingo-ui@latest login` once with a Pro account. The CLI adds the files, their dependencies and a version record for updates.

With shadcn: `npx shadcn@latest add @wingo-ui/color-picker` (needs the @wingo-ui registry with your API key in components.json)

Files:

- `components/ui/color-picker.tsx`

npm dependencies: `npm i @radix-ui/react-popover @radix-ui/react-slider motion lucide-react`

Wingo UI dependencies (installed automatically):

- [Button](https://wingo-ui.com/components/button) ([Markdown](https://wingo-ui.com/components/button.md))
- [Drawer](https://wingo-ui.com/components/drawer) ([Markdown](https://wingo-ui.com/components/drawer.md))
- [Field](https://wingo-ui.com/components/field) ([Markdown](https://wingo-ui.com/components/field.md))
- [Segmented Control](https://wingo-ui.com/components/segmented-control) ([Markdown](https://wingo-ui.com/components/segmented-control.md))
- [useControllableState](https://wingo-ui.com/components/use-controllable-state) ([Markdown](https://wingo-ui.com/components/use-controllable-state.md))
- [useMediaQuery](https://wingo-ui.com/components/use-media-query) ([Markdown](https://wingo-ui.com/components/use-media-query.md))
- [Motion presets](https://wingo-ui.com/components/motion) ([Markdown](https://wingo-ui.com/components/motion.md))
- [UIProvider](https://wingo-ui.com/components/ui-config) ([Markdown](https://wingo-ui.com/components/ui-config.md))

## Usage

```tsx
import { useState } from "react"
import { ColorPicker } from "@/components/ui/color-picker"

export function StoreColor() {
  const [color, setColor] = useState("#FC4C01")
  return (
    <ColorPicker
      label="Primary color"
      description="The store's buttons and links use this color."
      value={color}
      onValueChange={setColor}
      onValueCommit={(saved) => console.log("saved", saved)}
      name="primary_color"
      placeholder="Choose a brand color"
      sheetTitle="Brand color"
      labels={{ swatches: "Brand presets", done: "Apply" }}
    />
  )
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `label?` | `ReactNode` | `""` | The field label. The trigger reads it with the value ("Primary color, #FC4C01"); plain text also titles the phone sheet. |
| `description?` | `ReactNode` | `""` | Helper text under the field, linked with aria-describedby. |
| `error?` | `ReactNode` | `""` | Replaces the description with an animated error and marks the field invalid. |
| `required?` | `boolean` | `false` | The red mark after the label and aria-required on the trigger. |
| `invalid?` | `boolean` | `false` | The invalid look and aria-invalid without a message. |
| `disabled?` | `boolean` | `false` | Dims the field and never opens the panel; the hidden input is not submitted. |
| `readOnly?` | `boolean` | `false` | The color stays visible and the trigger focusable, but the panel never opens (inline: nothing can change). |
| `defaultValue?` | `string` | `""` | The starting color while uncontrolled, any CSS color we can read ("#FC4C01", "rgb(252 76 1)", "hsl(18 99% 50%)", "red"). A form reset returns to it. |
| `value?` | `string` |  | The color, controlled, as a CSS color in format ("" is empty). A value in another format is shown as given and re-emitted in format on the next change. |
| `onValueChange?` | `(value: string) => void` |  | Called with the new color in format: live while dragging (at most once per frame), after a typed value, a swatch or the eyedropper. On phones only when Done is pressed. |
| `onValueCommit?` | `(value: string) => void` |  | Called when a change is final: pointer up after a drag, Enter or blur in a field, a swatch, an eyedropper pick, a key in the square, the sheet's Done. Save here. |
| `format?` | `"hex" \| "rgb" \| "hsl"` | `"hex"` | The output: "#FC4C01", "rgb(252, 76, 1)" or "hsl(18, 99%, 50%)". Also the fields the panel shows first; the panel's switch only changes the fields. The exported parseColor(text) and formatColor(hsva, format, alpha) do the same conversions for your own code. |
| `alpha?` | `boolean` | `false` | An opacity slider and an opacity field. Hex becomes #RRGGBBAA below 100% opacity; rgb and hsl always become rgba() / hsla(). Off, colors are always opaque. |
| `swatches?` | `string[]` | `"[\"#0A0A0A\", \"#525252\", \"#A3A3A3\", \"#FFFFFF\", \"#EF4444\", \"#F97316\", \"#F59E0B\", \"#EAB308\", \"#84CC16\", \"#22C55E\", \"#14B8A6\", \"#0EA5E9\", \"#3B82F6\", \"#6366F1\", \"#8B5CF6\", \"#EC4899\"]"` | Preset colors in a grid of 8 per row. Defaults to the exported COLOR_PICKER_SWATCHES: 16 colors (neutrals, red, orange, amber, yellow, lime, green, teal, sky, blue, indigo, violet, pink), easy to extend. [] hides the row. |
| `showInputs?` | `boolean` | `true` | The format switch, the text fields and the eyedropper under the sliders. |
| `showFormatSwitch?` | `boolean` | `true` | The HEX / RGB / HSL segmented control above the fields. |
| `showEyeDropper?` | `boolean` | `true` | The pipette button. It only renders where the browser has window.EyeDropper (Chrome and Edge on desktop), so it never appears broken. |
| `inline?` | `boolean` | `false` | Renders the panel in place (settings pages): no trigger, no popover, no sheet. Changes apply at once. |
| `trigger?` | `"field" \| "swatch"` | `"field"` | A field box with a color chip and the value, or a round swatch button sized like an icon button (toolbars, compact forms). |
| `placeholder?` | `string` | `"Pick a color"` | The trigger's text while empty (with a checkerboard chip). |
| `labels?` | `Partial<{ area; saturation; brightness; hue; alpha; hex; red; green; blue; hueInput; lightness; format; eyeDropper; swatches; done; trigger: string }>` |  | Every spoken and visible string, merged over English defaults ("Saturation and brightness", "Hue", "Opacity", "Done"...). trigger names the trigger when there is no label ("Color, #FC4C01"). |
| `size?` | `"sm" \| "md" \| "lg"` | `"md"` | Trigger heights of 32, 40 and 48px (md is 44px on touch screens); the swatch button is square at the same sizes. |
| `variant?` | `"soft" \| "outline" \| "underline" \| "ghost"` | `"soft"` | The field box look (trigger field): a tinted fill, a border, a bottom line, or nothing until hover. |
| `radius?` | `"none" \| "sm" \| "md" \| "lg" \| "full"` | `"md"` | Corner radius of the field, scaled per size like the button; full also makes the color chip round. Ignored by underline. |
| `name?` | `string` | `""` | Renders a hidden input with this name and the color text, for native forms. |
| `defaultOpen?` | `boolean` | `false` | Starts with the panel (or the sheet) open. Pass open and onOpenChange to control it. |
| `open?` | `boolean` |  | Whether the panel or sheet is open, controlled. It never opens while disabled or readOnly. |
| `onOpenChange?` | `(open: boolean) => void` |  | Called when the panel or sheet opens or closes. |
| `responsive?` | `boolean` | `true` | Below 768px the panel opens in a bottom sheet with a Done button that commits the draft. Off, phones get the popover too. |
| `sheetTitle?` | `string` | `"Pick a color"` | The phone sheet's title (and the popover's accessible name) when the label is not plain text or missing. |
| `side?` | `"top" \| "right" \| "bottom" \| "left"` | `"bottom"` | Where the panel opens; it flips when there is no room. |
| `align?` | `"start" \| "center" \| "end"` | `"start"` | How the panel lines up with the trigger. |
| `container?` | `HTMLElement \| null` |  | Portal target of the panel and the sheet; defaults to document.body. |
| `fieldProps?` | `Partial<FieldProps>` |  | Anything else for the Field wrapper: optional, labelAction, orientation, labelWidth, hideLabel. |
| `id?` | `string` |  | The trigger's id (the panel's with inline); generated when left out. |
| `classNames?` | `Partial<Record<"field" \| "label" \| "description" \| "error" \| "counter" \| "trigger" \| "swatch" \| "value" \| "chevron" \| "content" \| "panel" \| "area" \| "thumb" \| "hue" \| "alpha" \| "preview" \| "inputs" \| "format" \| "input" \| "eyedropper" \| "swatches" \| "swatchOption" \| "footer", string>>` |  | Extra classes for the inner parts. content is the popover or the sheet, thumb the square's thumb, input every text field in the panel. |
| `className?` | `string` |  | Extra classes for the outermost element: the Field when there is a label or message, else the trigger (or the inline panel). |
| `ref?` | `Ref<HTMLButtonElement> (a useRef object or an (el) => void callback)` |  | The trigger button. Other props (onBlur, autoFocus, aria-*, data-*) land on it too. |

## Parts

### ColorPickerPanel (component)

The picker panel on its own (square, hue and opacity bars, fields, format switch, swatches, eyedropper) for your own popover, sheet or settings page. Takes value / defaultValue, onValueChange, onValueCommit, format, alpha, swatches, showInputs, showFormatSwitch, showEyeDropper, labels, disabled, readOnly, autoFocus and classNames, the same as ColorPicker.

### parseColor (function)

Reads hex (3, 4, 6 or 8 digits), rgb() / rgba(), hsl() / hsla() and the 16 basic CSS color names into an Hsva, without a canvas. Returns null for text that is not a color. Signature: (text: string | null | undefined) => Hsva | null.

### formatColor (function)

Writes an Hsva as "#FC4C01", "rgb(252, 76, 1)" or "hsl(18, 99%, 50%)", with the alpha channel when alpha is true. Signature: (hsva: Hsva, format?: ColorFormat, alpha?: boolean) => string.

### hsvaToRgba (function)

Converts hue / saturation / value / alpha to red / green / blue / alpha (0-255 channels). Signature: (hsva: Hsva) => Rgba.

### rgbaToHsva (function)

Converts red / green / blue / alpha (0-255 channels) to hue / saturation / value / alpha. Signature: (rgba: Rgba) => Hsva.

### COLOR_PICKER_SWATCHES (constant)

The 16 default preset colors as hex strings: neutrals, then red through pink. Spread it to add your brand colors.

### ColorPickerPanelProps (type)

Props of ColorPickerPanel.

### ColorFormat (type)

"hex" | "rgb" | "hsl", the output format and the fields the panel shows first.

### Hsva (type)

{ h, s, v, a }: hue 0-360, saturation and value 0-100, alpha 0-1. The internal color model.

## Examples

- **Inline panel**: inline renders the panel in a settings card; changes apply at once.
- **Opacity**: alpha adds the opacity slider and field; format rgb emits rgba().
- **Swatch triggers**: trigger="swatch" round buttons in an editor toolbar.
- **Formats**: One color emitted as hex, rgb and hsl, converted with parseColor / formatColor.
- **Eyedropper**: The pipette picks any pixel on screen where the browser supports it.
- **Brand palette**: Custom swatches only, without the fields.
- **Sizes and states**: sm / md / lg, empty, error and disabled.

## See also

- [Field](https://wingo-ui.com/components/field) ([Markdown](https://wingo-ui.com/components/field.md))
- [Slider](https://wingo-ui.com/components/slider) ([Markdown](https://wingo-ui.com/components/slider.md))
- [Segmented Control](https://wingo-ui.com/components/segmented-control) ([Markdown](https://wingo-ui.com/components/segmented-control.md))
- [Drawer](https://wingo-ui.com/components/drawer) ([Markdown](https://wingo-ui.com/components/drawer.md))
- [Input](https://wingo-ui.com/components/input) ([Markdown](https://wingo-ui.com/components/input.md))

---

Canonical: https://wingo-ui.com/components/color-picker
More components: https://wingo-ui.com/components · Index for agents: https://wingo-ui.com/llms.txt
