# Color Swatch Picker

Select a named color or image from an application-owned palette.

Label color swatches

[Complete TSX file](/examples/color-swatch-picker/default.tsx)

```tsx
"use client";

import { ColorSwatchPicker } from "@/siteplane/components/color-swatch-picker";
import {
  Field,
  FieldDescription,
  FieldLabel,
} from "@/siteplane/components/field";

const labelColors = [
  { value: "indigo", label: "Indigo", color: "#6366f1" },
  { value: "sky", label: "Sky", color: "#0ea5e9" },
  { value: "green", label: "Green", color: "#22c55e" },
  { value: "amber", label: "Amber", color: "#f59e0b" },
  { value: "rose", label: "Rose", color: "#f43f5e" },
  { value: "slate", label: "Slate", color: "#64748b" },
];

export default function Example() {
  return (
    <Field className="w-full max-w-sm">
      <FieldLabel>Label color</FieldLabel>
      <ColorSwatchPicker defaultValue="indigo" options={labelColors} />
      <FieldDescription>
        Shown on the label in lists and filters.
      </FieldDescription>
    </Field>
  );
}
```

## Installation

Complete [one-time setup](/docs/installation/) first.

```bash
pnpm dlx shadcn@4.18.0 add @siteplane/color-swatch-picker
```

With npm: `npx shadcn@4.18.0 add @siteplane/color-swatch-picker`. [Registry JSON](/registry/color-swatch-picker.json) lists source files and dependencies.

For this page’s examples, also install:

```bash
pnpm dlx shadcn@4.18.0 add @siteplane/button @siteplane/form @siteplane/input
```

Field is installed with Input.

## Usage

```tsx
import { ColorSwatchPicker } from "@/siteplane/components/color-swatch-picker";

<ColorSwatchPicker
  aria-label="Color"
  options={[{ value: "green", label: "Green", color: "#22c55e" }]}
/>;
```

## Controlled selection

The value is the option key, not its CSS color.

Controlled selection

[Complete TSX file](/examples/color-swatch-picker/controlled.tsx)

```tsx
"use client";

import { ColorSwatchPicker } from "@/siteplane/components/color-swatch-picker";
import { Field, FieldLabel } from "@/siteplane/components/field";
import { useState } from "react";

const options = [
  { value: "indigo", label: "Indigo", color: "#6366f1" },
  { value: "green", label: "Green", color: "#22c55e" },
  { value: "rose", label: "Rose", color: "#f43f5e" },
  { value: "slate", label: "Slate", color: "#64748b", disabled: true },
];

export default function Example() {
  const [value, setValue] = useState("indigo");
  return (
    <Field className="w-full max-w-sm">
      <FieldLabel>Label color</FieldLabel>
      <ColorSwatchPicker
        options={options}
        value={value}
        onValueChange={setValue}
      />
    </Field>
  );
}
```

## Selection ring

A selected swatch shows a check by default, drawn in like the [Checkbox](/docs/components/checkbox/) check and instantly under reduced motion. Set `indicator="ring"` to draw an accent ring around it instead, so the swatch keeps its full color.

Label color swatches with a selection ring

[Complete TSX file](/examples/color-swatch-picker/ring.tsx)

```tsx
"use client";

import { ColorSwatchPicker } from "@/siteplane/components/color-swatch-picker";
import { Field, FieldLabel } from "@/siteplane/components/field";

const labelColors = [
  { value: "indigo", label: "Indigo", color: "#6366f1" },
  { value: "sky", label: "Sky", color: "#0ea5e9" },
  { value: "green", label: "Green", color: "#22c55e" },
  { value: "amber", label: "Amber", color: "#f59e0b" },
  { value: "rose", label: "Rose", color: "#f43f5e" },
  { value: "slate", label: "Slate", color: "#64748b" },
];

export default function Example() {
  return (
    <Field className="w-full max-w-sm">
      <FieldLabel>Label color</FieldLabel>
      <ColorSwatchPicker
        defaultValue="indigo"
        indicator="ring"
        options={labelColors}
      />
    </Field>
  );
}
```

## Image swatches

Give an option an `image` instead of a `color` and the picture fills the swatch. The `color` is optional and shows until the image has loaded. Use `indicator="ring"` so the selection doesn’t cover the picture.

Reviewer swatches with photos

[Complete TSX file](/examples/color-swatch-picker/images.tsx)

```tsx
"use client";

import { ColorSwatchPicker } from "@/siteplane/components/color-swatch-picker";
import {
  Field,
  FieldDescription,
  FieldLabel,
} from "@/siteplane/components/field";

const reviewers = [
  {
    value: "mara",
    label: "Mara Ellis",
    image: "https://ui.siteplane.io/avatars/mara.webp",
  },
  {
    value: "ben",
    label: "Ben Carter",
    image: "https://ui.siteplane.io/avatars/ben.webp",
  },
  {
    value: "sofia",
    label: "Sofia Nguyen",
    image: "https://ui.siteplane.io/avatars/sofia.webp",
  },
];

export default function Example() {
  return (
    <Field className="w-full max-w-sm">
      <FieldLabel>Reviewer</FieldLabel>
      <ColorSwatchPicker
        defaultValue="mara"
        indicator="ring"
        options={reviewers}
      />
      <FieldDescription>
        Gets a notification when the draft is ready.
      </FieldDescription>
    </Field>
  );
}
```

## Read-only and disabled

Read-only preserves selection and is announced as read-only; disabled also removes the submitted value.

Read-only and disabled swatches

[Complete TSX file](/examples/color-swatch-picker/states.tsx)

```tsx
"use client";

import { ColorSwatchPicker } from "@/siteplane/components/color-swatch-picker";
import { Field, FieldLabel } from "@/siteplane/components/field";

const options = [
  { value: "indigo", label: "Indigo", color: "#6366f1" },
  { value: "green", label: "Green", color: "#22c55e" },
  { value: "rose", label: "Rose", color: "#f43f5e" },
  { value: "slate", label: "Slate", color: "#64748b", disabled: true },
];

export default function Example() {
  return (
    <div className="grid w-full max-w-sm gap-4">
      <Field>
        <FieldLabel>Label color</FieldLabel>
        <ColorSwatchPicker options={options} defaultValue="green" readOnly />
      </Field>
      <Field>
        <FieldLabel>Project color</FieldLabel>
        <ColorSwatchPicker options={options} defaultValue="rose" disabled />
      </Field>
    </div>
  );
}
```

## Form integration

Inside a `Field`, the picker submits the selected option’s `value` under the field’s `name`. With `required`, the form isn’t submitted until a color is chosen, and `FieldError` explains why.

Required swatch selection in a form

[Complete TSX file](/examples/color-swatch-picker/form.tsx)

```tsx
"use client";

import { Button } from "@/siteplane/components/button";
import { ColorSwatchPicker } from "@/siteplane/components/color-swatch-picker";
import { Field, FieldError, FieldLabel } from "@/siteplane/components/field";
import { Form } from "@/siteplane/components/form";

const options = [
  { value: "indigo", label: "Indigo", color: "#6366f1" },
  { value: "green", label: "Green", color: "#22c55e" },
  { value: "rose", label: "Rose", color: "#f43f5e" },
  { value: "slate", label: "Slate", color: "#64748b", disabled: true },
];

export default function Example() {
  return (
    <Form
      className="grid w-full max-w-sm gap-4"
      onSubmit={(event) => {
        event.preventDefault();
      }}
    >
      <Field name="labelColor">
        <FieldLabel>Label color</FieldLabel>
        <ColorSwatchPicker options={options} required />
        <FieldError>Choose a label color.</FieldError>
      </Field>
      <Button type="submit">Save</Button>
    </Form>
  );
}
```

## API reference

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| options | readonly ColorSwatchPickerOption[] | Required | Each option has label (accessible name), value (unique string), and optional disabled, plus a color (CSS color), an image (image URL), or both. An image fills the swatch; a color shows behind it until the image has loaded. imageSize and imagePosition (CSS background-size and background-position) show part of the image. |
| value / defaultValue | string | — | Controlled option key or uncontrolled initial key. |
| onValueChange | (value: string) => void | — | Emits selected option key. |
| indicator | check \| ring | check | How the selected swatch shows it: a check inside the swatch, or an accent ring around it. |
| disabled / readOnly / required | boolean | false | Disable interaction, prevent changes, or require selection. readOnly and required are exposed on the listbox as aria-readonly and aria-required; a disabled Field also disables the picker. |
| name / form | string | — | Native form field name / form id; a Field name takes precedence. |
| inputRef | React.Ref<HTMLInputElement> | — | Reference to the visually hidden form input. |
| size | sm \| md \| lg \| xl | provider / lg | Control dimensions; swatches are 28, 32, 36 and 40 px. |
| className / style / aria-label / aria-labelledby / aria-describedby | div props | — | Applied to the collection root; inside a Field, FieldLabel, FieldDescription and FieldError are connected automatically. |

Exported types: `ColorSwatchPickerProps`, `ColorSwatchPickerOption` and `ColorSwatchIndicator`. These describe the contracts above.

[Read the installed source](/source/color-swatch-picker).

## Usage notes

Native reset restores uncontrolled selection. Inside a Field, FieldLabel names the group and clicking it focuses the selected swatch; outside a Field, pass `aria-label` or `aria-labelledby`. Each option label is announced. Keyboard navigation follows React Aria [ColorSwatchPicker](https://react-spectrum.adobe.com/react-aria/ColorSwatchPicker.html). Swatches are round by default; the `radius` of SiteplaneUIProvider squares them off (`none`) or rounds them only slightly (`sm`). To end a palette with a custom color, use the swatch trigger of [Color Picker](/docs/components/color-picker/#custom-color-swatch). Arbitrary children and native onChange are not part of this wrapper.
