ColorPicker
A full color picker composed from parts — a 2D saturation/brightness field, hue and alpha sliders, a live preview swatch, an eyedropper, a format selector, and a text input. Drop it in inline, or wrap it in a Popover for a compact trigger.
Inline panel
ColorPicker.Root owns the color and format state; compose the parts inside a ColorPicker.Panel to lay out an always-visible picker. Arrange the Area, HueSlider, AlphaSlider, Swatch, EyeDropper, FormatTabs, and Input however the surface needs — each reads from and writes to the shared store. Drive it with value / onValueChange, or seed it with defaultValue.
"use client";
import { ColorPicker } from "@stridge/noctis";
export default function ColorPickerInline() {
return (
<ColorPicker.Root defaultValue="oklch(0.58 0.18 18)">
<ColorPicker.Panel className="w-60">
<ColorPicker.Area />
<div className="flex items-center gap-2">
<ColorPicker.Swatch />
<div className="flex min-w-0 flex-1 flex-col gap-1.5">
<ColorPicker.HueSlider />
<ColorPicker.AlphaSlider />
</div>
<ColorPicker.EyeDropper />
</div>
<ColorPicker.FormatTabs />
<ColorPicker.Input />
</ColorPicker.Panel>
</ColorPicker.Root>
);
}
In a popover
The picker is only a picker — it owns no floating behavior of its own. For a compact control, wrap ColorPicker.Root around a Popover: the picker provides the colour state and parts, the popover provides the trigger, the elevated surface, and the open/close. Drop the picker parts straight into Popover.Popup (no ColorPicker.Panel — the popup is already a surface), and reuse ColorPicker.Swatch or the live value in the trigger so it reflects the current colour.
"use client";
import { Button, ColorPicker, Popover } from "@stridge/noctis";
import { useState } from "react";
export default function ColorPickerPopover() {
const [value, setValue] = useState("oklch(0.56 0.18 305)");
return (
// ColorPicker.Root owns the colour; the standalone Popover owns the floating surface and
// open/close. The picker parts compose straight into the Popover.Popup — no ColorPicker.Panel,
// which would double the surface.
<ColorPicker.Root value={value} onValueChange={setValue}>
<Popover.Root>
<Popover.Trigger
render={
<Button variant="outline">
<span className="size-4 rounded-xs border border-border" style={{ background: value }} />
{value}
</Button>
}
/>
<Popover.Popup className="w-64">
<ColorPicker.Area />
<div className="flex items-center gap-2">
<ColorPicker.Swatch />
<div className="flex min-w-0 flex-1 flex-col gap-1.5">
<ColorPicker.HueSlider />
<ColorPicker.AlphaSlider />
</div>
<ColorPicker.EyeDropper />
</div>
<ColorPicker.FormatTabs />
<ColorPicker.Input />
</Popover.Popup>
</Popover.Root>
</ColorPicker.Root>
);
}
Anatomy
Compose a picker from its parts. ColorPicker.Root owns the color and the active format and shares them with every part through context; it renders no element of its own.
ColorPicker.Root— owns the value (value/onValueChange, ordefaultValue), the format (format/defaultFormat),alpha,disabled, andreadOnly. Passlabelsto localize the parts' accessible names. To float the picker, wrap aPopoverinside it (see In a popover).ColorPicker.Panel— the layout surface the controls sit in (aSurface, so it takes the surface props). Skip it inside aPopover.Popup, which is already a surface.ColorPicker.Area— the 2D saturation/brightness field with a draggable thumb.ColorPicker.HueSlider+ColorPicker.AlphaSlider— the hue and alpha rails.ColorPicker.Swatch— the current-color preview.ColorPicker.EyeDropper— samples a color from the screen (where the browser supports it).ColorPicker.FormatTabs— switches the value between hex, RGB, HSL, and OKLCH.ColorPicker.Input— the text field for typing or pasting a value.
Every rendered part carries a data-slot under the noctis-color-picker-* prefix (-panel, -area, -hue, -alpha, -swatch, -eye-dropper, -format-tabs, -input, plus the -thumb slots on the field and rails) for host-side styling, and dims on data-disabled.
Keyboard
| Key | Action |
|---|---|
| Tab / Shift + Tab | Move between the field, the rails, the eyedropper, the format tabs, and the input. |
| ← / → / ↑ / ↓ | On the field or a rail: nudge the value; the field moves in two axes, a rail in one. Arrows mirror under RTL. |
When wrapped in a Popover, the trigger and dismissal keys (open on Enter / Space, close on Esc) come from the popover.
On surfaces
The same control re-tuned across the elevation scopes — the root canvas, an elevated panel, a menu, and a sunken well. It stays legible on every layer.
Design tokens
Generated from the component's declaration — the same graph that mints the CSS, so a variable name or its resolution default can't drift. The minted tokens are the public override seam: set one on any ancestor and every picker in that region retunes — e.g. .compact { --noctis-color-picker-panel-width: 14rem; } narrows every panel beneath it. Knobs that aren't minted are reached through the part's data-slot. See Customization for the full override ladder and Tokens for the whole graph.
API reference
Generated from the component's types — every prop, type, default, and description comes straight from the source. Each part gets its own table; the Root carries the value and format props that the parts read through context. Expand a row for the full type and description.