Color Picker

HSL-native color picker that edits an HSL triplet directly via three sliders (H/S/L). Hex input is a display adapter; sliders are the source of truth so the value round-trips losslessly through the `@hex-core/tokens` triplet format.

Single token

Primary
262 83% 58%

Additional tokens

Accent
199 89% 48%
Destructive
0 84% 60%

Disabled — locked while a parent operation is in flight

Background
240 5.9% 10%

Installation

pnpm
pnpm dlx @hex-core/cli add color-picker

Disabled

Prevent edits while a parent operation is in flight.

tsx
<ColorPicker value="240 5.9% 10%" onChange={() => {}} disabled />

With hex input

Pair the picker with a sibling hex Input. The triplet stays the source of truth; the hex field is derived for display via hslTripletToHex and parsed back via hexToHslTriplet. Both helpers are re-exported from the @hex-core/components barrel.

tsx
import { useState } from "react";
import { ColorPicker } from "@/components/ui/color-picker";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { hexToHslTriplet, hslTripletToHex } from "@hex-core/components";

export function BrandColorField() {
  const [triplet, setTriplet] = useState("262 83% 58%");
  const hex = hslTripletToHex(triplet);

  return (
    <div className="flex items-end gap-3">
      <div className="space-y-1">
        <Label>Brand color</Label>
        <ColorPicker value={triplet} onChange={setTriplet} aria-label="Brand color" />
      </div>
      <div className="space-y-1">
        <Label htmlFor="brand-hex">Hex</Label>
        <Input
          id="brand-hex"
          value={hex}
          onChange={(event) => {
            const next = hexToHslTriplet(event.target.value);
            if (next) setTriplet(next);
          }}
          className="w-28 font-mono text-sm uppercase"
          spellCheck={false}
        />
      </div>
    </div>
  );
}

API Reference

PropTypeDefaultDescription
valuerequired
stringCurrent color as an HSL triplet string (`"<H> <S>% <L>%"`, e.g. `"240 5.9% 10%"`). Match the format used by `@hex-core/tokens`.
onChangerequired
functionCalled with the next HSL triplet when the user drags a slider or commits a valid hex value. Not called for invalid hex input.
disabled
booleanfalseDisable interaction. Trigger renders dimmed; mouse and keyboard activation are blocked by the native `disabled` attribute.
aria-label
string"Pick color"Accessible name for the trigger button.
className
stringAdditional class names merged onto the trigger.

AI Guidance

When to use

Use whenever the user is editing a color that will round-trip through the `@hex-core/tokens` HSL triplet format — token editors, theme builders, branding panels, custom-color surfaces in design tools.

When not to use

Don't use for picking a color from a fixed palette — use a `Select` or `RadioGroup` of swatches. Don't use for image-based color sampling (eyedropper) — that's a separate primitive. Don't reach for ColorPicker when only a hex string matters: bind it directly to `<input type="color">` for the simplest cases.

Common mistakes

  • Treating the value as hex — the prop is an HSL triplet, not a hex string. Use `hexToHslTriplet` and `hslTripletToHex` from `@hex-core/components` if you need to bridge.
  • Forgetting to wrap the value in `hsl(...)` when applying it as a CSS color: `style={{ color: \`hsl(${value})\` }}`.
  • Calling `onChange` synchronously inside a parent's render — the picker batches slider updates and that pattern can desync controlled state.

Accessibility

Each slider has a per-axis `aria-label` (Hue / Saturation / Lightness). The trigger button needs an explicit `aria-label` describing what color is being edited (e.g. `"Primary color"`) — the default `"Pick color"` is generic. The hex input is keyboard-accessible and round-trips with the sliders.

Related components

Token budget: 350