Color Picker
An inline HSV color picker: a saturation/brightness field, a hue track with an opt-in alpha track, optional per-channel sliders for any model, and a switchable multi-format value field.
Live demo
Color Picker chooses a color by hue, saturation, brightness, and opacity. A saturation/brightness field (the hue painted under white→transparent and transparent→black gradients) carries a draggable role="slider" handle moved by pointer or arrow keys; a rainbow hue track sets the field's hue; an opt-in alpha track (off by default) sets opacity over a checkerboard; and a value field reflects the color in a switchable format (hex, rgb, hsl, oklch, lab, lch, oklab, or cmyk) while accepting any CSS Color 4 string (named, #rrggbb/#rrggbbaa, rgb(), hsl(), oklch(), …) plus profile-free cmyk(), and reformatting on commit.
A format button cycles the readout, and a modes knob narrows which spaces it offers. Set channels to a model (rgb, hsl, hsv, oklch, lab, lch, oklab, or cmyk) and a stack of native range sliders appears, one per channel of that model, each with a live numeric readout and editable directly; every edit round-trips through the same engine math and re-threads the rest of the picker. Set plane and an OKLCH perceptual plane appears: a lightness × chroma <canvas> field at the current hue (its chroma axis sized to the hue's reach), with colors outside the sRGB gamut desaturated and edged with a contour so the boundary reads rather than clamping flat, a draggable handle, and a live L · C readout. The color math lives in @xoji/core (culori-backed), so parsing, formatting, and channel decomposition belong to the engine, not the component. Where the browser supports it, an eyedropper button samples a color from anywhere on screen, a swatches list adds a row of preset chips below the picker, a harmony scheme generates a live row of related colors (complementary, triadic, analogous, and more) each clickable to adopt, contrastAgainst adds a live WCAG panel grading the current color against a reference, and a snap set adds palette-snap buttons: quantize to the 216-color web-safe cube, or jump to the perceptually nearest CSS named color (shown live on the button). It is form-associated. Give it a name and it submits the current value. It renders inline by default, or set trigger to collapse it to a swatch button that opens the full UI in an anchored, light-dismissable popover. It composes the same rail-and-handle mechanics as Slider.
When to use
How this component composes with the rest of the set.
Props
15 props, straight from the manifest.
| Prop | Type | Default | Bindings | Description |
|---|---|---|---|---|
Appearance
States
focus-visible
Keyboard focus on a handle, the value input, or the format button: a token-colored ring plus a transparent outline that becomes real in forced-colors mode.
disabled
Non-interactive: muted and pointer-events suppressed.
Anatomy
The named parts that make up the component, with their selectors.
picker
The role="group" wrapper stacking the optional label, the field, and the controls row.
area
The saturation (x) / brightness (y) field, painted with the current hue under two gradients.
sv-handle
The draggable role="slider" handle marking the chosen saturation and brightness; filled with the current color.
plane
The optional OKLCH perceptual plane (shown when plane is set): a <canvas> painting lightness (y) × chroma (x) at the current hue, with out-of-gamut samples desaturated and a contour at the gamut boundary.
plane-handle
The draggable role="slider" handle on the OKLCH plane, marking the current lightness and chroma; filled with the current color.
plane-readout
The live L · C caption under the OKLCH plane, naming its axes and distinguishing it from the HSV field.
swatch
A static preview of the current color.
hue
The rainbow hue track with its own role="slider" handle.
alpha
The opt-in opacity track: the current color faded to transparent over a checkerboard, with its own role="slider" handle. Present only when alpha is set.
format
The button that cycles the readout format (HEX → RGB → HSL → OKLCH).
value
The monospace input that reflects the color in the active format and parses any CSS Color 4 string.
eyedropper
The screen-sampling button; present only where the browser exposes the EyeDropper API (Chromium today).
presets
The optional row of preset color chips from swatches, over a checkerboard for alpha; the active color's chip is pressed.
harmony
The optional row of related-color chips from harmony, rebuilt live as the color changes; it reuses the preset chip styling.
channels
The optional per-channel slider stack (shown when channels names a model): one labeled native range input per channel, each with a live numeric readout.
snaps
The optional palette-snap buttons (from snap): a web-safe quantizer and a named button that reads out and adopts the nearest CSS named color.
contrast
The optional WCAG panel (shown when contrastAgainst is set): a sample swatch, the ratio, and AA/AAA grades that go green on pass, red on fail.
trigger
The swatch button (shown when trigger is set) that opens the picker popover; reflects the current color over a checkerboard, with a corner caret marking it as openable and a hover lift.
trigger-badge
The WCAG rating chip overlaid on the trigger swatch (shown when trigger and contrastAgainst are both set): AAA reads success, AA reads info, A (large-text floor) reads warn, and a sub-AA hazard reads danger — so the safety of the current color against its reference is legible without opening the picker.
popover
The anchored panel (shown when trigger is set) holding the picker UI in the top layer; native light-dismiss and Esc close it.
label
The optional visible label, referenced as the group's accessible name.
Tokens & coverage
What the component consumes, checked live against what the algorithm produces.
Live coverage check against the xoji-default register
(derive(xojiDefault, { anchors }) →
coverComponent(manifest, register)). Every token this component
consumes must be a key the algorithm produces.
--accent
--bg-0
--bg-2
--border-normal
--border-thick
--border-thin
--danger
--danger-fg
--danger-text
--duration-fast
--ease-standard
--elevation-1
--elevation-3
--fg-0
--fg-1
--fg-disabled
--font-mono
--font-sans
--info
--info-fg
--line-2
--neutral
--neutral-fg
--radius-full
--radius-md
--radius-sm
--ring
--space-1
--space-2
--space-4
--space-6
--state-hover
--success
--success-fg
--success-text
--text-sm
--text-xs
--warn
--warn-fg
--weight-bold
--weight-medium
Accessibility
Code
Inline pickers
A brand color, a perceptual OKLCH picker, and a disabled one: each an always-open HSV field with a hue track and a switchable value field; opacity is opt-in.
<xoji-color-picker label="Brand color" value="#5b8cff"></xoji-color-picker>
<!-- reads out and submits in OKLCH; still accepts any CSS color typed in -->
<xoji-color-picker label="Perceptual" value="oklch(0.72 0.15 250)" format="oklch"></xoji-color-picker>
<!-- per-channel sliders for the chosen model -->
<xoji-color-picker label="RGB channels" value="#5b8cff" channels="rgb"></xoji-color-picker>
<xoji-color-picker label="Palette snap" value="#5b8cff" snap="web-safe,named"></xoji-color-picker>
<xoji-color-picker label="Perceptual plane" value="oklch(0.72 0.15 250)" format="oklch" plane></xoji-color-picker>
<xoji-color-picker label="Brand color" value="#5b8cff" trigger></xoji-color-picker>
<xoji-color-picker label="Locked" value="#e25b99" disabled></xoji-color-picker>
<script lang="ts">
import { ColorPicker } from "@xoji/svelte";
let brand = $state("#5b8cff");
</script>
<ColorPicker label="Brand color" bind:value={brand} />
<ColorPicker label="Perceptual" value="oklch(0.72 0.15 250)" format="oklch" />
<ColorPicker label="RGB channels" value="#5b8cff" channels="rgb" />
<ColorPicker label="Palette snap" value="#5b8cff" snap="web-safe,named" />
<ColorPicker label="Perceptual plane" value="oklch(0.72 0.15 250)" format="oklch" plane />
<ColorPicker label="Brand color" value="#5b8cff" trigger />
<ColorPicker label="Locked" value="#e25b99" disabled />
---
import { ColorPicker } from "@xoji/astro";
---
<ColorPicker label="Brand color" value="#5b8cff" />
<ColorPicker label="Perceptual" value="oklch(0.72 0.15 250)" format="oklch" />
<ColorPicker label="RGB channels" value="#5b8cff" channels="rgb" />
<ColorPicker label="Palette snap" value="#5b8cff" snap="web-safe,named" />
<ColorPicker label="Perceptual plane" value="oklch(0.72 0.15 250)" format="oklch" plane />
<ColorPicker label="Brand color" value="#5b8cff" trigger />
<ColorPicker label="Locked" value="#e25b99" disabled />