Field.ColorPicker
Field.ColorPicker is a button inside a Field, drawn as a swatch one field line tall and filled with its color. It opens a ColorPicker in a Popover. Its value is a #rrggbb string, and onValueChange gets each new color as it is dragged or keyed.
On this page
const id = useId();
<Field.Group>
<Label.Root htmlFor={id}>Accent</Label.Root>
<Field.Root id={id}>
<Field.ColorPicker
value={color}
onValueChange={(next) => {
setColor(next);
setText(next);
}}
/>
<Field.TextInput
value={text}
onChange={() => undefined}
onValueChange={(next) => {
setText(next);
const hex = normalizeHex(next);
if (hex) setColor(hex);
}}
onBlur={() => setText(color)}
spellCheck={false}
/>
</Field.Root>
</Field.Group>
Here a Field.TextInput in the same field lets people type the color too. The two share state: a picked color sets the text, and normalizeHex turns typed text into a color when it is one. Beside a Field.TextInput, that input is the field's control. It takes the field's id and description, and a press elsewhere in the field focuses it. A Field.Textarea, Field.ChipInput or Field.Select, or a control joined with useField, takes the id and description in the same way. The swatch sends the color with a form, through a text input hidden beside it, under its own name if it has one, and otherwise under the field's name, unless useField gave one of these controls that name, as it does a Field.TextInput with no name of its own.
On its own, Field.ColorPicker is the field's control, a press elsewhere in the field opens the picker, and the field is as wide as the swatch. Put the value after it in a Field.Affix to show it as text.
The picker opens and closes through open with onOpenChange, or defaultOpen. onOpenChange gets the reason it closed: 'escape', 'outside', 'trigger', or 'disabled' when the field becomes disabled. Open, the picker takes focus, and Escape there closes it and returns focus to the swatch.
Accessible composition
Todae handles: On its own, the button takes id, aria-label, aria-labelledby and aria-describedby from Field.Root, and native disabled and aria-invalid from the Root or a surrounding Fieldset. Beside one of the controls above, that control takes the id and aria-describedby in its place, and the button keeps disabled and aria-invalid and is named from the colorPicker string in StringsProvider unless it has its own aria-label. It gets aria-haspopup="dialog" and aria-expanded, and aria-controls while the picker is open. The popover is a role="dialog" named from the colorPicker string in StringsProvider, and its inputs are named and keyed as on ColorPicker.
You supply: A name, through a Label whose htmlFor is the Root id or aria-label / aria-labelledby on the Root.
Watch for: The swatch's name is never the color, so a screen reader does not hear the value from the button. Show the value as text, in a Field.TextInput or a Field.Affix, when people need to read it.
When to use
Field.ColorPicker
When: Pick one color in a form, such as a brand or accent color.
When not: A color in another form, such as oklch() → Field.TextInput. A picker shown inline, without a field → ColorPicker. A fixed set of colors → RadioGroup.
Props
| Name | Type | Description |
|---|---|---|
value Required | string | The color as #rrggbb. |
onValueChange | (value: string) => void | Fires with the new color as #rrggbb while it is dragged or keyed in the picker. |
open | boolean | Controlled open state of the picker. Pass with onOpenChange. |
defaultOpen | boolean | Initial open state when uncontrolled. |
onOpenChange | (open: boolean, reason: 'escape' | 'outside' | 'trigger' | 'disabled') => void | Fires when the picker opens or closes, with why it closed. |