Color Picker

A color picker with editable hex, RGBA and HSL inputs, and a screen eyedropper.

Hue217°
Opacity100%
import { ColorPicker } from "@fern-ui/color-picker"

const [color, setColor] = useState("#3b82f6")

<ColorPicker value={color} onChange={setColor} />
  • onChange fires every frame of a drag — use it for anything you are painting live.
  • onChangeComplete fires once on settle — use it for saves, undo entries, and anything expensive.
  • format picks 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.

Hue160°
Opacity100%
livesettled · 0
<ColorPicker
  value={color}
  onChange={setColor}
  onChangeComplete={(next) => save(next)}
/>

Opening in another model

HEX is one field, RGBA is four, HSL is three. Switching never loses the color.

Hue32°
Opacity100%
<ColorPicker format="rgb" value={color} onChange={setColor} />

Compact

<ColorPicker
  value={color}
  onChange={setColor}
  sliderLabels={false}
  alpha={false}
/>

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-picker

Or copy these two files into your project. No runtime dependencies beyond React; color.ts is pure math and is imported by the component.

color-picker.tsx
Loading color-picker.tsx…
color.ts
Loading color.ts…

Props

PropTypeDefaultDescription
valuestringControlled value. Accepts hex, rgb() or hsl(), with or without alpha.
defaultValuestring"#3b82f6"Initial value when uncontrolled.
onChange(value, color) => voidFires on every change, including each frame of a drag.
onChangeComplete(value, color) => voidFires once when an interaction settles.
format"hex" | "rgb" | "hsl""hex"Color model to open in, and the format passed to callbacks.
formatTogglebooleantrueLet the user change the model.
alphabooleantrueShow the opacity slider and include alpha in the output.
sliderLabelsbooleantrueShow the name and readout above each slider.
eyedropperbooleantrueOffer the native screen eyedropper where supported.
copyablebooleantrueShow the copy-to-clipboard button.
disabledbooleanfalseBlock interaction and dim the control.
labelstring"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"

On this page