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 @xtyle/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 |
|---|---|---|---|---|
.value and submits to a form in the active format. | ||||
format button cycles it live, through the modes set. | ||||
format button cycles, in order (e.g. hex,oklch,lab). Defaults to all eight; unknown names are ignored. Lets an author offer exactly the color spaces they want. | ||||
rgb, hsl, hsv, oklch, lab, lch, oklab, or cmyk. A bare or unknown value falls back to rgb. Each slider edits one channel and re-derives the color through the engine. | ||||
web-safe quantizes each channel to the 216-color web cube, and named snaps to the perceptually nearest CSS named color (its name shown live on the button). A bare or unknown value offers both. | ||||
<canvas> with colors outside the sRGB gamut desaturated and a contour drawn at the boundary so the gamut edge reads rather than clamping flat. The chroma axis is sized to the hue's in-gamut reach. Drag or arrow the handle to set lightness and chroma; a live L · C readout sits below. | ||||
Esc) instead of rendering inline. The button reflects the current color and sits where the picker would; the panel opens below it. | ||||
complementary, triadic, analogous, split-complementary, tetradic, monochromatic, shades, or tints. Each chip is clickable to adopt that color. | ||||
trigger mode a rating chip (AAA/AA/A/hazard) is also overlaid on the swatch so the grade reads without opening the picker. | ||||
aria-labelledby. | ||||
label. | ||||
Events
What the component emits, and what rides along on event.detail. The name in the
first column is the one addEventListener takes.
| Event | Detail | 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. A ::part() handle is reachable from your own stylesheet; the class beside it is the component's internal selector, which a shadow boundary keeps to itself.
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. The scheme fixes how many chips there are, so they are built once and only recolored as the color changes; each 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 xtyle-default register
(derive(xtyleDefault, { 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-mediumAccessibility
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.
<xtyle-color-picker label="Brand color" value="#5b8cff"></xtyle-color-picker>
<!-- reads out and submits in OKLCH; still accepts any CSS color typed in -->
<xtyle-color-picker label="Perceptual" value="oklch(0.72 0.15 250)" format="oklch"></xtyle-color-picker>
<!-- per-channel sliders for the chosen model -->
<xtyle-color-picker label="RGB channels" value="#5b8cff" channels="rgb"></xtyle-color-picker>
<xtyle-color-picker label="Palette snap" value="#5b8cff" snap="web-safe,named"></xtyle-color-picker>
<xtyle-color-picker label="Perceptual plane" value="oklch(0.72 0.15 250)" format="oklch" plane></xtyle-color-picker>
<xtyle-color-picker label="Brand color" value="#5b8cff" trigger></xtyle-color-picker>
<xtyle-color-picker label="Locked" value="#e25b99" disabled></xtyle-color-picker><script lang="ts">
import { ColorPicker } from "@xtyle/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 "@xtyle/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 />