ColorPicker
A colour chosen by eye: a saturation square with a hue rail beside it, an optional opacity rail, a field for typing a value in, and a grid of ready-made swatches. It reads and writes hex, rgb() and hsl(), and adds no dependency to your bundle.
import { ColorPicker } from 'neba';
const [color, setColor] = useState('#1a58d1');
<ColorPicker value={color} onValueChange={setColor} />;Props
| Prop | Type | Default | Description |
|---|---|---|---|
| value | string | — | The colour, as a CSS string. Pass it to drive the picker yourself |
| defaultValue | string | '#1a58d1' | Where an uncontrolled picker starts |
| onValueChange | (value: string) => void | — | Called with the new colour, written in format |
| format | 'hex' | 'rgb' | 'hsl' | 'hex' | Which notation the value is written in on the way out |
| alpha | boolean | false | Offers an opacity rail, and lets the value carry a fourth channel |
| swatches | readonly string[] | false | — | The ready-made colours under the panel. false draws none; an array replaces the built-in set |
| inline | boolean | false | Draws the panel in the page instead of in a popup, with no trigger |
| editable | boolean | true | The field under the panel that the value can be typed into |
| clearable | boolean | false | Offers the × that empties the control |
| open | boolean | — | Whether the popup is open. Use with onOpenChange for a controlled one |
| defaultOpen | boolean | false | Whether it starts open |
| onOpenChange | (open: boolean) => void | — | Called when the popup opens or closes |
| locale | string | 'en' | Which language the accessible names are written in: a BCP 47 tag. Unsupported tags fall back to English |
| labels | Partial<ColorPickerLabels> | — | Overrides for those names, one at a time: the square, the two rails, the field and the swatch grid all have no text on them |
| name | string | — | Submits with a form under this name |
| fullWidth | boolean | false | Stretches to the width of the container |
| label | ReactNode | — | The label, wired to the control by Base UI's Field |
| description | ReactNode | — | Helper text |
| error | ReactNode | — | Error message. Its presence turns the control invalid and re-points the colour family at danger |
| invalid | boolean | !!error | Forces the invalid state without a message, for when a form library owns validity |
| readOnly | boolean | false | Shown but not changeable. Keeps its colour and edge, drains the saturation |
| disabled | boolean | false | Unavailable. Drops the colour family for neutral grey |
| variantshared | 'solid' | 'outline' | 'text' | 'outline' | Weight of the surface: filled, hairline, or none |
| sizeshared | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'md' | The trigger's height, and the size of the panel and the square on it |
| colorshared | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'primary' | The family of the edge and the focus ring. Nothing to do with the colour being chosen |
| densityshared | 'default' | 'compact' | 'default' | Padding only: never the height, never the type scale |
| elevationshared | 0 | 1 | 2 | 3 | 0 | Drop shadow depth. 0 means no shadow at all |
| required | boolean | — | Whether a value is required before the form is submitted |
Every other <div> attribute passes through to the root, except onChange: the change worth listening for is onValueChange.
The shared axes (variant size color density elevation) are defined in prop conventions. Note that color is the family of the control's own edge and focus ring; it has nothing to do with the colour being chosen.
Examples
inline
By default the panel lives in a popup hanging off a trigger, which is what a form wants. inline draws the panel straight into the page with no trigger at all: for a settings pane, a toolbar, or anywhere the picker is the point of the screen rather than one field on it.
format
format decides the notation the value comes back in: hex (the default), rgb or hsl. It only affects what is written out: a value in any of the three is read correctly whatever format says.
alpha
alpha adds an opacity rail under the hue rail and lets the value carry a fourth channel: #rrggbbaa, rgba() or hsla(). Without it the value is always opaque, so a caller who never asked for opacity never sees a fourth argument come out.
swatches
swatches takes an array of CSS colour strings and replaces the built-in set: the place to put the handful of colours a product actually uses. swatches={false} draws none, and editable={false} drops the text field, which together leave the panel as nothing but the square and the rails.
In a form
label, description and error are the same three slots every field in the library takes, and name submits the value with the form. clearable offers the × that empties the control, after which the value is an empty string.
size
size sets the trigger's height on the shared ladder and the panel's own width with it, so a picker lines up with the fields beside it at every step.
Controlled
Pass value and the picker stops keeping state of its own. open and onOpenChange do the same for the popup.
const [color, setColor] = useState('#1a58d1');
<ColorPicker value={color} onValueChange={setColor} />;The colour strings it reads
Hex in all four lengths (#abc, #abcd, #aabbcc, #aabbccdd), rgb()/rgba() and hsl()/hsla(), in both the comma and the space syntax. Named colours and color() are not: a picker has to be able to write back every value it can read, and there is no point on the panel that means rebeccapurple. A string it cannot read leaves the panel where it was.
Accessibility
- The square and each rail are
role="slider"with an accessible name, a value and arrow-key support. Arrows move by one step; hold shift for ten. - The square reports both axes through
aria-valuetext, since onearia-valuenowcannot describe a point in two dimensions. - Every swatch is a real button named with its own colour, and the chosen one carries
aria-pressed. Its tick is drawn in black or white depending on which can be read on that colour. - Set
localeso the names of the square, the rails and the field are read out in the page's own language, or write them yourself withlabels. disabledandreadOnlyboth take the panel out of the tab order and stop it answering to the pointer and the keyboard.