Skip to content

Color Picker

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.

Inputv0.1.0experimentalWCAG 2.2 AAView spec
Brand color

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-picker

Copies 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.json

Works, 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

Label color
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

Overlay color

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

Label color

 
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.

PropTypeDefault
labelrequired

Visible label, also the accessible name of the button.

stringnone
value

Controlled color as a hex string: #rrggbb, or #rrggbbaa when showAlpha is on.

stringnone
defaultValue

Initial color when uncontrolled, in the same format.

string"#000000"
onChange

Called with the new color as lowercase hex: #rrggbb, or #rrggbbaa when showAlpha is on.

(value: string) => voidnone
presets

Hex colors shown as a row of swatches to pick from.

string[]none
showAlpha

Adds an opacity slider and makes the value #rrggbbaa.

booleanfalse
format

How the color is written on the button. The value is always hex.

"hex" | "rgb" | "hsl""hex"
description

Help text under the button.

stringnone
errorMessage

Shown under the button when isInvalid is true.

stringnone
isInvalid

Marks the color as wrong and shows errorMessage.

booleannone
isDisabled

Prevents opening the panel and dims the field.

booleanfalse
name

Submits the value with a form under this name.

stringnone

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