ColorField
$ npx shadcn@latest add https://lacodda.github.io/dowel/r/color-field.jsonThis is for the product’s data, not for its appearance. A tag’s colour, a project’s colour, a calendar’s colour — things stored beside a name. It is not how a reader repaints the interface: the theme decides that, from one accent, and a field that let anyone override it would undo the argument the whole system rests on.
<ColorField value={tag.color} onValueChange={(color) => save({ ...tag, color })} aria-label={t('colour')} swatchLabel={(name) => t(`colour.${name}`)}/>The palette comes first, and it reports a name. The swatches are the
line’s nineteen accents plus the four semantic tokens, so a chosen value is
kilna or good — a word from the vocabulary the product already speaks. A
name keeps meaning the right thing when the theme changes underneath it; a hex
captured at the moment of choosing would not.
Free hex is opt-in, through allowCustom. It does not contradict the rule
against raw colours: that rule is about a component hardcoding its own
appearance, while a colour a reader picked for their own tag is data — the
same kind of thing as the tag’s name.
A typed colour commits only once it is one. Half of #3fa9 is not a
colour, and pushing every keystroke up would either reject the reader
mid-word or repaint the product with something they were passing through. The
box holds a draft; value moves when the draft is a colour.
The swatches are real radios in a radiogroup. That is what makes the
arrow keys walk the palette, what makes the choice announce itself, and what
keeps the whole palette one tab stop — eighteen buttons would put eighteen
stops between it and the next field.
swatchLabel is yours. Without it the swatches are named by their token,
which is right for a developer and wrong for a reader: lime is not a word
your product necessarily uses.
Props
Read from the types in ui/color-field.tsx, the file a product copies - so the description of each prop is also what an editor shows over it there.
| Prop | Type | Default | |
|---|---|---|---|
valuerequired | string | The chosen colour: a token name, or a hex string. | |
onValueChangerequired | (value: string) => void | Told the next colour: a token name from the palette, or a hex string once the free box holds a complete one. | |
allowCustom | boolean | Whether the reader may type a colour of their own. | |
aria-label | string | Names the palette for a screen reader. | |
aria-labelledby | string | The id of the element that names the palette, when a visible label does it. | |
swatchLabel | (name: string) => string | How a swatch is named. Takes the colour's name, so the product can say "Lime" in its own language rather than shipping ours. | |
customLabel | ReactNode | The label on the free-entry box, when allowCustom is on. | |
disabled | boolean | Dims the field and stops it taking input: every swatch and the free box. | |
className | string |
Accessibility
The examples on the stand, through axe 4.14.0 in a browser, in both themes, against WCAG 2.2 A and AA and axe's best practices: 19 rules applied and passed, and none failed. The accessibility report has every item.
History
Each release that changed ui/color-field.tsx - the file a product copies - newest first.