---
title: "Color Picker"
description: "Compact swatch trigger that opens a color editor in a popover."
links:
  doc: https://cladd.io/react/components/color-picker/
  api: https://cladd.io/react/components/color-picker/#api-reference
---

# Color Picker

A compact swatch trigger that opens a [`ColorEditor`](/react/components/color-editor/)
in a [`Popover`](/react/components/popover/). Reach for it in dense inspectors
and property panels where a full inline editor would take up too much room.

```tsx
<ColorPicker
  value={value}
  onChange={(c) => setValue(c.hex)}
  className="w-48"
/>
```

## Usage

```tsx
import { ColorPicker } from '@cladd-ui/react';

function Example() {
  const [value, setValue] = useState('#3b82f6');

  return <ColorPicker value={value} onChange={(c) => setValue(c.hex)} />;
}
```

## Values

`ColorPicker` takes the **same value props as the [`ColorEditor`](/react/components/color-editor/)** it wraps. `value` / `defaultValue` accept any `ColorInput` — a CSS string (`'#2f6bff'`, `'rgba(...)'`, `'red'`) or a single channel set (`{ r, g, b }`, `{ h, s, l }`, `{ h, s, b }`) — and `onChange` emits the fully-resolved `ColorValue` (`hex`, `rgb`, `hsl`, `hsb`, `css`). With [`gradient`](#gradient), input also takes a `linear-gradient()` string or gradient object and `onChange` emits the discriminated `solid` / `linear` value. The editor's [Values](/react/components/color-editor/#values) section has the full breakdown of accepted formats, ranges, and emitted fields.

Two behaviors are specific to the picker's **trigger**:

- **Label.** A solid value shows its uppercased hex (`#2F6BFF`); a gradient shows `gradientLabel` (default `'Gradient'`). Pass `children` to render your own value node instead.
- **Empty state.** A missing value (`null`, `undefined`, or `''`) — or a fully transparent one (alpha `0`, including a gradient whose every stop is transparent) — collapses the swatch to an outlined "×" and shows `placeholder` in place of the label.

## Examples

### Empty

When there is no value — `null`, an empty string, or a fully transparent color
— the swatch collapses to an outlined "×" and the trigger shows the
`placeholder` node in place of a hex label.

```tsx
<ColorPicker
  value={value}
  placeholder="No fill"
  onChange={(c) => setValue(c.hex)}
  className="w-48"
/>
```

### Gradient

Set `gradient` to add a Solid / Gradient switch and let the embedded
[`ColorEditor`](/react/components/color-editor/) edit a linear gradient. This
flips the value to the discriminated union — `onChange` now emits a `solid` or
`linear` value, so read `c.css` for the CSS string rather than `c.hex`. Pass
`swatches` to offer preset stop colors.

```tsx
<ColorPicker
  gradient
  value={value}
  swatches={PALETTE}
  onChange={(c) => setValue(c.css)}
  className="w-48"
/>
```

### Custom value display

Pass `children` to replace the auto-formatted hex / gradient label with your
own value row — here the hex with the field name pushed to the trailing edge —
and set a leading glyph through the [`icon`](#api-reference) prop. This is the
inspector-row pattern used in tools like PaneFlow.

```tsx
<ColorPicker
  value={value}
  onChange={(c) => setValue(c.hex)}
  icon={<Fill className="size-4" />}
  className="w-56"
>
  <span className="flex w-full items-center gap-2">
    <span className="font-mono uppercase">{value}</span>
    <span className="ml-auto text-cladd-fg-softer">Fill</span>
  </span>
</ColorPicker>
```

### Sizes

`size` runs the standard `2xs → 2xl` scale shared with
[`Button`](/react/components/button/), [`Select`](/react/components/select/), and
the rest of the size-aware controls — the swatch scales with the trigger. Pass
the same token you'd use on a sibling control and the picker sits at the matching
height. The second row pairs `rounded` and `outline` to read as a pill that
matches a recessed form. The editor inside the popover is unaffected — size it
independently with `controlSize`.

```tsx
<ColorPicker
  size={size}
  value={value}
  onChange={(c) => setValue(c.hex)}
  className="w-40"
/>
<ColorPicker
  size={size}
  rounded
  outline
  value={value}
  onChange={(c) => setValue(c.hex)}
  className="w-40"
/>
```

### In a toolbar

ColorPickers slot into a [`Toolbar`](/react/components/toolbar/) as a property
row — a fill swatch, a [`ToolbarSeparator`](/react/components/toolbar/), and a
stroke swatch — for the dense inspector feel cladd was built for.

```tsx
<Toolbar aria-label="Shape style">
  <span className="px-2 text-xs font-semibold text-cladd-fg-soft">
    Fill
  </span>
  <ColorPicker
    rounded
    value={fill}
    onChange={(c) => setFill(c.hex)}
    className="w-32"
  />
  <ToolbarSeparator />
  <span className="px-2 text-xs font-semibold text-cladd-fg-soft">
    Stroke
  </span>
  <ColorPicker
    rounded
    value={stroke}
    onChange={(c) => setStroke(c.hex)}
    className="w-32"
  />
</Toolbar>
```

### Header & footer

`header` and `footer` render your own nodes above and below the editor, inside
the popover — the chrome a real picker needs around the swatches. Here a
[`SectionTitle`](/react/components/section-title/) and a live hex
[`Chip`](/react/components/chip/) label the popover, and an "Apply fill"
[`Button`](/react/components/button/) in the footer commits and closes it via the
controlled `popoverState`.

```tsx
<ColorPicker
  value={value}
  onChange={(c) => setValue(c.hex)}
  swatches={PALETTE}
  className="w-48"
  popoverState={open}
  onPopoverState={setOpen}
  header={
    <div className="mb-2 flex items-center justify-between gap-2">
      <SectionTitle>Fill</SectionTitle>
      <Chip
        size="md"
        className="font-mono font-medium"
        contentClassName="text-xs"
      >
        {value}
      </Chip>
    </div>
  }
  footer={
    <Button
      color="brand"
      size="md"
      rounded
      className="mt-2 w-full"
      onClick={() => setOpen(false)}
    >
      Apply fill
    </Button>
  }
/>
```

### Playground

`size`, `color`, `rounded`, `outline`, and `gradient` compose freely — toggle
between a solid and a gradient picker and restyle the trigger to match its
surroundings.

```tsx
{gradient ? (
  <ColorPicker
    gradient
    size={size}
    color={color}
    rounded={rounded}
    outline={outline}
    swatches={PALETTE}
    value={grad}
    onChange={(c) => setGrad(c.css)}
    className="w-48"
  />
) : (
  <ColorPicker
    size={size}
    color={color}
    rounded={rounded}
    outline={outline}
    swatches={PALETTE}
    value={solid}
    onChange={(c) => setSolid(c.hex)}
    className="w-48"
  />
)}
```

## API Reference

ColorPicker is a compact swatch trigger that opens a
[`ColorEditor`](/react/components/color-editor/) inside a
[`Popover`](/react/components/popover/). The value props form a discriminated
union on `gradient`: by default they describe a solid color (`ColorValue`), and
with `gradient` set they describe a gradient (`GradientValue`). Editor props
such as `alpha`, `inputs`, `format`, `swatches`, and `controlSize` are forwarded
to the embedded `ColorEditor`.

**Inherits from:** [`Button`](/react/components/button/)

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `alpha?` | `boolean` | `true` | Show the alpha slider and scrubber. Default `true`. |
| `anchorRef?` | `RefObject<HTMLElement \| null>` | — | External anchor ref. When provided the trigger button is **not rendered** - the caller owns the trigger and `popoverState` wiring. |
| `angleControl?` | `'button' \| 'scrubber'` | `'scrubber'` | Gradient angle control: a 45°-step button, or a degree scrubber. Default `'scrubber'`. |
| `areaClassName?` | `string` | — | Extra classes for the editor's saturation/brightness area. |
| `children?` | `ReactNode` | — | Custom node rendered in place of the auto-formatted value (hex / gradient label).<br>Rendered as `data-part="value"` — use to show your own value text, a field label, or richer formatting. |
| `className?` | `string` | — | Extra classes for the trigger button. |
| `color?` | `Color` | — | Accent color for the trigger button. Forwarded to `Button.color`. |
| `contentClassName?` | `string` | — | Extra classes for the trigger button's inner content row. |
| `controlOutline?` | `boolean` | `true` | Render the surface outline on the editor's inner controls. Default `true`. |
| `controlSize?` | `ColorEditorControlSize` | `'md'` | Size of the editor's inner controls. Default `'md'`. |
| `debounce?` | `number` | — | Debounce `onChange` calls in ms. Forwarded to the editor. |
| `defaultValue?` | `ColorInput` | — | Initial value (uncontrolled). |
| `disabled?` | `boolean` | — | Visually dim the trigger and prevent the popover from opening. |
| `dropdownIcon?` | `boolean` | `true` | Show the chevron-down indicator on the right of the trigger. Default `true`. |
| `editorClassName?` | `string` | — | Extra classes for the editor panel root. |
| `footer?` | `ReactNode` | — | Content rendered below the editor panel, inside the popover. |
| `format?` | `ColorEditorFormat` | `'rgb'` | Which channels the scrubber row shows. Default `'rgb'`. |
| `gradient?` | `false` | `false` | Enable the Solid/Gradient switch and gradient editing. Default `false`. |
| `gradientLabel?` | `string` | `'Gradient'` | Label rendered for a gradient value in the trigger. Default `'Gradient'`. |
| `header?` | `ReactNode` | — | Content rendered above the editor panel, inside the popover. |
| `hexInput?` | `boolean` | `true` | Show the hex input. Default `true`. |
| `icon?` | `ReactNode` | — | Icon node rendered at the start of the trigger, before the swatch. |
| `iconClassName?` | `string` | — | Extra classes for the icon wrapper. |
| `inputs?` | `boolean` | `true` | Show the channel-scrubber row. Default `true`. |
| `multiline?` | `boolean` | — | Forwarded to the trigger `Button` — allows wrapping the value across lines. |
| `onChange?` | `(value: ColorValue) => void` | — | Fires on every change with the full color. |
| `onClick?` | `(e: MouseEvent) => void` | — | Fires when the trigger button is clicked (before the popover toggles). |
| `onPopoverState?` | `(state: boolean) => void` | — | Fires whenever the popover open state changes. |
| `outline?` | `boolean` | — | Render the trigger button's surface outline ring. Forwarded to `Button.outline`. |
| `placeholder?` | `ReactNode` | — | Value shown when there is no color (or a fully transparent one) and no `children`. |
| `placeholderClassName?` | `string` | — | Extra classes applied to the value container when showing the placeholder. |
| `popoverClassName?` | `string` | `'w-64 p-3'` | Extra classes for the popover. Default `'w-64 p-3'`. |
| `popoverColor?` | `Color` | — | Accent color for the popover. Forwarded to `Popover.color`. |
| `popoverOffset?` | `PopoverOffset` | `['-50%', 4]` | Default `['-50%', 4]`. |
| `popoverPosition?` | `PopoverPosition` | `'bottom-end'` | Default `'bottom-end'`. |
| `popoverState?` | `boolean` | — | Controlled popover open state. Pair with `onPopoverState`. |
| `popoverSurfaceLevel?` | `number \| string` | — | Surface level for the popover. Default same as `Popover`'s `surfaceLevel`. |
| `readOnly?` | `boolean` | — | Show the trigger with the current value but block opening the popover. |
| `reverse?` | `boolean` | — | Reverse the visual order of the content row (icon/swatch/value ↔ dropdown). |
| `rounded?` | `boolean` | — | Pill-style trigger button. Forwarded to `Button.rounded`. |
| `size?` | `ButtonSize` | — | Trigger button size — also scales the swatch. Forwarded to `Button.size`. |
| `surface?` | `'surface' \| 'cut'` | — | Trigger button surface: `'surface'` (default) or `'cut'` (inset/recessed). |
| `swatchClassName?` | `string` | — | Extra classes for the color swatch. |
| `swatches?` | `ColorInput[]` | — | Preset colors rendered as a row of thumbs in the editor. |
| `throttle?` | `number` | — | Throttle `onChange` calls in ms. Forwarded to the editor. |
| `value?` | `ColorInput` | — | Controlled value. A CSS color string or any one channel set. |
| `valueClassName?` | `string` | — | Extra classes for the value display inside the trigger. |
