Input
Color picker
A flower of overlapping discs that blooms out of the swatch you clicked. Three rings choose hue and saturation, a band takes the current colour, and an arc hangs off the right edge for lightness.
Usage
import { ColorPicker } from '@carabine/ui/color-picker';Any hex in, six uppercase digits out. One shape of value means nobody downstream has to normalise it before comparing two colours.
How it blooms
The petals overlap on purpose — a ring of tangent circles reads as a dial, a ring of overlapping ones reads as a flower — and each arrives on its own spring with stagger milliseconds added per petal, so the bloom has a direction rather than appearing all at once.
The spring is 520 stiffness at 31 damping with a mass of 0.62. Critical damping is 2 × √stiffness, so about 46 — this is light and well past critical, and the overshoot is what makes a petal arrive rather than slide in.
One thing here is CSS rather than React: the hovered petal's z-index. A petal that grows under its neighbours looks broken, and re-ordering the DOM on hover would move focus. color-picker.css owns it, because that is the one property React must not.
Examples
Your own flower
rings replaces rather than merges: a colour set is a whole, not a table of variants to patch. A ring with a single colour sits in the middle whatever its radius says, and a swatch with no label is named by its colour string — the one label that never needs translating.
Customization
One number drives the geometry. size is the disc's diameter and the petal, the band, the arc and the knob are all fractions of it — so the whole flower scales by changing one prop rather than six.
<ColorPicker
size={140}
petal={32} // they overlap on purpose
band={13} // the ring that takes the current colour
plate={false} // no blurred plate under the petals
arcSpread={70} // degrees of lightness arc, centred on the right edge
minLightness={10} // the slice of HSL lightness the arc maps to
maxLightness={90}
triggerSize={22}
theme="light"
/>Styling reference
The picker is the toast's material at another size — a glass shell with a lit core — and plate is what puts it under the petals. Turn it off and the flower floats on the page, which is right when it is already inside a panel of your own.
| Prop | What it moves |
|---|---|
| stiffness / damping / mass | The bloom, and a petal answering a hover |
| stagger | Milliseconds added per petal on the way in |
| openStiffness / openDamping | The panel opening and closing |
| openScale | The scale the panel grows from — 0.42, and it grows from the trigger |
API reference
| Prop | Type | |
|---|---|---|
| className | string | Added to the panel’s own classes, so yours wins |
| style | CSSProperties | Merged after the component’s own inline styles, so yours wins |
Both land on the panel. The trigger is already your element — you style it where you write it — and the panel is the part that portals away from your markup and is otherwise out of reach. State is legible from CSS as well — data-state, data-disabled, data-side — so a rule can answer it without knowing a single class name of ours. See Theming.
| Prop | Type | Default | |
|---|---|---|---|
| value | string | — | Controlled. Any hex in, six uppercase digits out |
| defaultValue | string | '#FC8835' | |
| onValueChange | (value: string) => void | — | |
| onOpenChange | (open: boolean) => void | — | |
| rings | ColorRing[] | defaultRings | Replaces the palette outright |
| size | number | 107 | Diameter of the disc. Everything else follows it |
| petal | number | size × 0.224 | |
| band | number | size × 0.0896 | The ring that takes the current colour |
| plate | boolean | true | A blurred plate under the petals |
| arcRadius | number | size × 0.7531 | From the disc’s centre |
| arcSpread | number | 50 | Degrees, centred on the right edge |
| arcWidth | number | size × 0.0933 | |
| knob | number | size × 0.1306 | |
| minLightness | number | 0 | The slice of HSL lightness the arc maps to |
| maxLightness | number | 100 | |
| triggerSize | number | 18 | |
| offset | number | 12 | Smallest gap kept from the viewport’s edges |
| zIndex | number | 50 | |
| container | HTMLElement | null | document.body | Where it portals to |
| disabled | boolean | false | |
| label | string | 'Color' | Names the trigger and the panel |
| theme | 'dark' | 'light' | 'dark' |