Color Picker
A color picker with editable hex, RGBA and HSL inputs, and a screen eyedropper.
onChangefires every frame of a drag — use it for anything you are painting live.onChangeCompletefires once on settle — use it for saves, undo entries, and anything expensive.formatpicks the model the picker opens in, and the format your callbacks receive.- Turn things off to get a smaller picker; everything is on by default.
Examples
Live value vs settled value
The left swatch tracks every frame. The counter only moves when you let go.
Opening in another model
HEX is one field, RGBA is four, HSL is three. Switching never loses the
color.
Compact
Theming
The picker reads CSS custom properties, each with a literal fallback, so it
picks up your theme where you define these and still renders correctly where
you do not. Set them anywhere in scope — :root, a wrapper, or the picker
itself.
:root {
--surface: #ffffff; /* card and channel inputs */
--default: #ebebec; /* select, tray, action buttons */
--default-hover: #e0e0e2; /* those buttons on hover */
--foreground: #18181b; /* values and labels */
--muted: #71717a; /* slider names and icons */
--focus: #0485f7; /* focus ring */
--overlay-shadow: …; /* card elevation */
}Every part carries a data-slot, so you can restyle the internals from your own
stylesheet without forking the component.
[data-slot="track"] { border-radius: 4px; }
[data-slot="channels"] { background: #3b1d5e; }
[data-slot="slider"][data-channel="opacity"] { display: none; }color-picker · field · slider (with data-channel="hue" | "opacity") ·
track · thumb · model-select · preview · action · channels ·
hex-input · channel-input
className on the root is appended, not merged — passing a utility that
conflicts with one the component already sets will not reliably win. Use the
variables and slots above, or copy the component in and edit it directly.
Keyboard
Arrow keys step 1, Shift+arrows step 10, Home/End jump — on the field and both sliders. Channel inputs are spinbuttons that commit on blur or Enter; invalid input reverts instead of clamping.
Installation
bun add @fern-ui/color-pickerOr copy these two files into your project. No runtime dependencies beyond
React; color.ts is pure math and is imported by the component.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | – | Controlled value. Accepts hex, rgb() or hsl(), with or without alpha. |
defaultValue | string | "#3b82f6" | Initial value when uncontrolled. |
onChange | (value, color) => void | – | Fires on every change, including each frame of a drag. |
onChangeComplete | (value, color) => void | – | Fires once when an interaction settles. |
format | "hex" | "rgb" | "hsl" | "hex" | Color model to open in, and the format passed to callbacks. |
formatToggle | boolean | true | Let the user change the model. |
alpha | boolean | true | Show the opacity slider and include alpha in the output. |
sliderLabels | boolean | true | Show the name and readout above each slider. |
eyedropper | boolean | true | Offer the native screen eyedropper where supported. |
copyable | boolean | true | Show the copy-to-clipboard button. |
disabled | boolean | false | Block interaction and dim the control. |
label | string | "Color picker" | Accessible name for the group. |
Both callbacks receive the full color as a second argument — hex, rgb,
hsl, hsv and alpha. The conversion helpers are importable on their own:
import { formatColor, parseHex, rgbToHsv } from "@fern-ui/color-picker/color"