Color Picker
A compact swatch trigger that opens a ColorEditor
in a Popover. Reach for it in dense inspectors
and property panels where a full inline editor would take up too much room.
<ColorPicker
value={value}
onChange={(c) => setValue(c.hex)}
className="w-48"
/>Usage
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 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, input also takes a linear-gradient() string or gradient object and onChange emits the discriminated solid / linear value. The editor's 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 showsgradientLabel(default'Gradient'). Passchildrento render your own value node instead. - Empty state. A missing value (
null,undefined, or'') — or a fully transparent one (alpha0, including a gradient whose every stop is transparent) — collapses the swatch to an outlined "×" and showsplaceholderin 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.
<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 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.
<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 prop. This is the
inspector-row pattern used in tools like PaneFlow.
<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, 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.
<ColorPicker
size="md"
value={value}
onChange={(c) => setValue(c.hex)}
className="w-40"
/>
<ColorPicker
size="md"
rounded
outline
value={value}
onChange={(c) => setValue(c.hex)}
className="w-40"
/>In a toolbar
ColorPickers slot into a Toolbar as a property
row — a fill swatch, a ToolbarSeparator, and a
stroke swatch — for the dense inspector feel cladd was built for.
<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 and a live hex
Chip label the popover, and an "Apply fill"
Button in the footer commits and closes it via the
controlled popoverState.
<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.
{gradient ? (
<ColorPicker
gradient
size="md"
color="brand"
rounded={false}
outline={false}
swatches={PALETTE}
value={grad}
onChange={(c) => setGrad(c.css)}
className="w-48"
/>
) : (
<ColorPicker
size="md"
color="brand"
rounded={false}
outline={false}
swatches={PALETTE}
value={solid}
onChange={(c) => setSolid(c.hex)}
className="w-48"
/>
)}API Reference
ColorPicker is a compact swatch trigger that opens a
ColorEditor inside a
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.
| Name: 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). 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. |