Skip to content

ColorField

Terminal window
$ npx shadcn@latest add https://lacodda.github.io/dowel/r/color-field.json
See it liveThe line's palette, and a colour of the reader's own.→

This 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.

PropTypeDefault
valuerequiredstringThe chosen colour: a token name, or a hex string.
onValueChangerequired(value: string) => voidTold the next colour: a token name from the palette, or a hex string once the free box holds a complete one.
allowCustombooleanWhether the reader may type a colour of their own.
aria-labelstringNames the palette for a screen reader.
aria-labelledbystringThe id of the element that names the palette, when a visible label does it.
swatchLabel(name: string) => stringHow a swatch is named. Takes the colour's name, so the product can say "Lime" in its own language rather than shipping ours.
customLabelReactNodeThe label on the free-entry box, when allowCustom is on.
disabledbooleanDims the field and stops it taking input: every swatch and the free box.
classNamestring

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.

  1. v0.41.0
    • docs read every props table from the component's types ee3b159
  2. v0.20.0
    • feat five components for the harder half of a form cfb317a