Picks a color. A button shows the current color and opens a panel with a saturation and brightness area, a hue slider, an optional opacity slider, a hex field and preset swatches. ColorPickerPanel is the same panel inline. Values are hex strings.
Chosen: #d9480f
import { useState } from "react";
import { ColorPicker } from "@rdloom/react";
export default function ColorPickerBasicExample() {
const [color, setColor] = useState("#d9480f");
return (
<div className="flex justify-center p-6">
<div className="w-64">
<ColorPicker label="Brand color" value={color} onChange={setColor} description={`Chosen: ${color}`} />
</div>
</div>
);
}Installation
npx rdloom add color-pickerCopies the source into src/components/rdloom. Edit it freely: rdloom upgrade merges later versions into your changes. It needs react-aria-components; add --install to install them.
Use another registry client
npx shadcn@latest add https://rdloom.vimalbhatt.com/r/color-picker.jsonWorks, but without upgrade tracking.
Usage
import { ColorPicker } from "@rdloom/react";
<div className="flex justify-center p-6">
<div className="w-64">
<ColorPicker label="Brand color" value={color} onChange={setColor} description={`Chosen: ${color}`} />
</div>
</div>With presets
import { ColorPicker } from "@rdloom/react";
const presets = ["#d9480f", "#c2255c", "#6741d9", "#1971c2", "#0c8599", "#2f9e44", "#f08c00", "#495057"];
export default function ColorPickerWithPresetsExample() {
return (
<div className="flex justify-center p-6">
<div className="w-64">
<ColorPicker label="Label color" defaultValue="#1971c2" presets={presets} format="rgb" />
</div>
</div>
);
}With alpha
The value includes opacity: #rrggbbaa.
import { useState } from "react";
import { ColorPicker } from "@rdloom/react";
export default function ColorPickerWithAlphaExample() {
const [color, setColor] = useState("#1971c2cc");
return (
<div className="flex justify-center p-6">
<div className="w-64">
<ColorPicker label="Overlay color" showAlpha value={color} onChange={setColor} description="The value includes opacity: #rrggbbaa." />
</div>
</div>
);
}Inline
Accent color: #6741d9
import { useState } from "react";
import { ColorPickerPanel } from "@rdloom/react";
export default function ColorPickerInlineExample() {
const [color, setColor] = useState("#6741d9");
return (
<div className="flex flex-col items-center gap-3 p-6">
<ColorPickerPanel label="Accent color" value={color} onChange={setColor} presets={["#6741d9", "#d9480f", "#2f9e44", "#1971c2"]} />
<p className="text-sm" aria-live="polite">
Accent color: {color}
</p>
</div>
);
}In a form
import { useState } from "react";
import { Form, FormColorPicker, FormSubmitButton, FormTextField } from "@rdloom/react";
export default function ColorPickerInAFormExample() {
const [saved, setSaved] = useState<string>();
return (
<div className="flex justify-center p-6">
<Form
defaultValues={{ name: "Design", color: "#d9480f" }}
onSubmit={(values) => setSaved(`${values.name}: ${values.color}`)}
className="flex w-72 flex-col gap-4"
>
<FormTextField name="name" label="Label name" isRequired />
<FormColorPicker
name="color"
label="Label color"
presets={["#d9480f", "#2f9e44", "#1971c2"]}
validate={(value) => (value === "#ffffff" ? "White is hard to see on a light page" : undefined)}
/>
<FormSubmitButton>Save label</FormSubmitButton>
<p className="text-sm" aria-live="polite">
{saved ?? ""}
</p>
</Form>
</div>
);
}API Reference
Defined by the spec. Components also accept the props of the React Aria component they wrap.
| Prop | Type | Default |
|---|---|---|
labelrequiredVisible label, also the accessible name of the button. | string | none |
valueControlled color as a hex string: #rrggbb, or #rrggbbaa when showAlpha is on. | string | none |
defaultValueInitial color when uncontrolled, in the same format. | string | "#000000" |
onChangeCalled with the new color as lowercase hex: #rrggbb, or #rrggbbaa when showAlpha is on. | (value: string) => void | none |
presetsHex colors shown as a row of swatches to pick from. | string[] | none |
showAlphaAdds an opacity slider and makes the value #rrggbbaa. | boolean | false |
formatHow the color is written on the button. The value is always hex. | "hex" | "rgb" | "hsl" | "hex" |
descriptionHelp text under the button. | string | none |
errorMessageShown under the button when isInvalid is true. | string | none |
isInvalidMarks the color as wrong and shows errorMessage. | boolean | none |
isDisabledPrevents opening the panel and dims the field. | boolean | false |
nameSubmits the value with a form under this name. | string | none |
Accessibility
Role dialog, WCAG 2.2 AA. Tested with axe and keyboard tests; screen reader checks are in the audit checklist.
Keyboard
- Enter or Space on the button opens the panel; Esc closes it and returns focus to the button
- Tab moves through the area, hue slider, opacity slider, hex field and presets
- In the color area (one 2D slider), Left and Right Arrow change saturation and Up and Down Arrow change brightness; Page Up and Page Down change by a larger step
- In a slider, arrow keys change the value by one step; Home and End jump to the ends
- Arrow keys move between preset swatches; Enter or Space picks one
- Typing in the hex field changes the color on Enter or when leaving the field
Screen readers announce
- The button reads the label, the color name and its value, as a collapsed or expanded button
- The area reads as a 2D slider with its saturation and brightness values in words; the hue slider reads the hue name
- Presets read as a list of colors by name, with the picked one selected
What your code must do
- Every part of the color can be set without a pointer, by arrows or by typing the hex
- The button and the swatches carry the color's name (for example "vibrant orange"), not only the hex
- The swatch has a visible outline, so a white or very light color is not lost on a white page
- Color is never the only signal: the hex or rgb or hsl text is shown with the swatch
- The area and sliders announce their value in words as they move
Guidelines
Use it when
- Choosing a brand, label or chart color
- Theme settings where a person needs a precise color
- Pass presets when a small palette is enough
Avoid it when
- A fixed set of a few options: use RadioGroup or SegmentedControl
- Choosing a text or background pair: show the contrast result next to the picker
Don't
- Showing only a swatch with no text
- Using the picker for a tiny choice that presets alone would answer
- Accepting a low-contrast color for text without telling the person
Design tokens
The semantic tokens this component uses. Change them once and every component follows; see Design tokens.
--rd-color-surface-default--rd-color-surface-raised--rd-color-border-default--rd-color-border-strong--rd-color-text-default--rd-color-text-muted--rd-color-action-primary--rd-color-feedback-danger--rd-color-focus-ring--rd-radius-control--rd-radius-overlay