Color Picker

A full HSV color picker in one panel: a HEX/HSL/RGB switcher, a draggable saturation square, hue and alpha rails, channel fields, an eyedropper and a preset palette. The rails ride Radix Slider for keyboard and ARIA.

Colors

#67aa38

Installation#

# One-time setup (tokens + lib helpers)npx koalaui-cli@latest init# Add this component (its dependencies come along)npx koalaui-cli@latest add color-picker

Usage#

The panel paints its own card, so it drops straight onto a page. Pass value with onValueChange for a controlled hex string, or defaultValue to let it own its state.

usage.tsxTSX
import { ColorPicker } from "@/components/ui/color-picker"export function Example() {  const [color, setColor] = useState("#67aa38")  return <ColorPicker value={color} onValueChange={setColor} />}

Anatomy#

Every piece of the panel is a named export, and the default layout is simply this composition. Pass children to write it out yourself: drop a part, reorder it, or wrap it. Nothing here is a showX flag that a part depends on.

Colors

Playground#

Flip the knobs to see the panel and its snippet change together.

Colors

Playground
Props

The panel footprint. sm trades the fields row for a 216px width.

Whether the panel paints its own card, or inherits the surface it sits on.

Parts

The format switcher and its channel fields.

The transparency rail and its field.

The labelled preset palette.

color-picker.tsxTSX
<ColorPicker  value="#67aa38"  onValueChange={() => {}}  showInput  showAlpha  showPresets/>

Formats#

The switcher at the top of the panel chooses how the colour is written: one hex box, or three numeric channels for HSL and RGB. It never changes the colour. HSV stays the working model in every format, so cycling HEX → HSL → RGB round-trips losslessly, and onValueChange always emits hex. Set the initial format with defaultFormat, or drive it with format and onFormatChange.

defaultFormat="hex"
defaultFormat="hsl"
defaultFormat="rgb"

Sizes#

Two footprints. md is the full 324px panel; sm is 216px and drops the fields row, which has no room at that width. Passing density="compact" picks sm as the default size, and size always wins over it. Unlike the form controls, the panel does not read an ambient DensityProvider: these are fixed panel widths, not height tiers, and inheriting compact from an app shell would silently take the fields away.

Colors

md — 324px

Colors

sm — 216px

Alpha#

The transparency rail and its percentage field are on by default. The emitted hex carries the aa byte whenever alpha drops below 100%, so a fully opaque colour is still a plain six-digit hex. Pass showAlpha={false} for an opaque-only picker.

showAlpha (default)#a855f7cc
showAlpha={false}#f0b100

Presets#

The palette is a labelled section fenced off by a hairline. Pass your own presets array, and set label on ColorPickerSwatches to rename the heading or null to drop it. The chosen chip is marked with a ring in its own colour rather than a checkmark, which reads at 24px on light and dark swatches alike.

Colors

a custom palette#f97316
label={null}

Surface#

By default the panel is a card: border, radius, elevation, and a --surface declaration so the fields and rings inside blend with it. Drop to surface="plain" when it already sits in something that draws the frame, such as a Card or a settings panel, so you never nest one card inside another.

surface="card" (default)
Brand color

Fill modes#

Pass modes with two or more entries to add a Figma-style fill-type row at the top: a tile per fill kind (Solid, each gradient geometry, Image), each a live preview. The gradient editor gives you a draggable stop track (click to add a stop, drag to move it, Delete to remove) and an angle rail for linear gradients; the image mode takes an upload or a URL with a cover/contain fit. Use onFillChange to receive one CSS value across every mode: a hex, a linear-gradient(), or a url().

90°

Colors

linear-gradient(90deg, #6366f1 0%, #ec4899 100%)

In a popover#

Wrap a ColorPickerTriggerSwatch in ColorPickerTrigger for the classic field affordance: a swatch of the current color that opens the panel on click. ColorPickerContent only positions and animates: the card you see is the picker's own, so there is exactly one frame either way.

#22c55e

Composition#

Because the parts are droppable, the same component covers the whole range from a full panel down to a bare swatch grid or a square with two rails. Render only what the task needs; the state, the keyboard handling, and the colour maths come along either way.

Brand palette

swatches only#8b5cf6
square and rails only

API reference#

ColorPicker takes value, defaultValue, onValueChange, size, surface, density, format, defaultFormat, onFormatChange, showAlpha, showInput, showPresets, presets, and the fill-mode props modes, mode, defaultMode, onModeChange, onFillChange, defaultGradient, defaultImage.

The parts are ColorPickerFormat, ColorPickerArea, ColorPickerControls (which composes ColorPickerHueSlider and ColorPickerAlphaSlider), ColorPickerFields, ColorPickerHexInput, ColorPickerEyeDropper, ColorPickerPreview, ColorPickerSwatches, ColorPickerModes, ColorPickerGradient, and ColorPickerImage, plus the popover trio ColorPickerPopover, ColorPickerTrigger, ColorPickerContent and the ready-made ColorPickerTriggerSwatch. The colour maths is exported too: hexToHsva, hsvaToHex, hsvaToRgba, rgbaToHsva, hsvaToHsla, hslaToHsva, and gradientToCss.

FAQ#

Reach for ColorPicker when a user needs to choose an arbitrary color: it gives you the 2D saturation/brightness square, hue and alpha rails, the HEX/HSL/RGB fields, and an eyedropper. If you only need a fixed set of brand colors, render ColorPickerSwatches on its own with a presets array, and if you just need a 1D value pick a plain Slider instead.