# Color Swatch Picker (/ui/components/react/color-swatch-picker)




## Usage [#usage]

```tsx
import { ColorSwatchPicker, ColorSwatchPickerItem } from '@appica/ui-react/color-swatch-picker'
import { parseColor } from '@appica/ui-react/color'
```

```tsx
<ColorSwatchPicker aria-label="Accent color" defaultValue="#3b82f6">
  <ColorSwatchPickerItem color="#ef4444" />
  <ColorSwatchPickerItem color="#f59e0b" />
  <ColorSwatchPickerItem color="#3b82f6" />
</ColorSwatchPicker>
```

`ColorSwatchPicker` is a set of colors you have already decided on: a brand palette, a theme's accents, the finishes a product comes in. Where [Color Area](/ui/components/react/color-area) and [Color Slider](/ui/components/react/color-slider) let someone reach any color at all, the picker offers a fixed list and nothing else.

Each `ColorSwatchPickerItem` takes a `color` and renders a [Color Swatch](/ui/components/react/color-swatch) inside a button. The value is the same `Color` the rest of the family uses, so a picker sits in a larger control without converting anything.

```tsx
const [color, setColor] = useState(() => parseColor('#3b82f6'))

<ColorSwatchPicker aria-label="Accent color" value={color} onValueChange={setColor}>
  …
</ColorSwatchPicker>
```

Colors are matched on their 8-digit hex, so the same color written two ways is one entry: an item with `color="#ff0000"` is selected by a value of `hsl(0, 100%, 50%)`. Give the picker a name with `aria-label` - it has no visible text of its own.

Inside a [Color Picker](/ui/components/react/color-picker) the value comes from the picker, so a palette drops in as presets with no wiring: see [Presets](/ui/components/react/color-picker#presets).

## Examples [#examples]

### Layouts [#layouts]

`layout="grid"` is the default: a row of swatches that wraps when it runs out of width. `layout="stack"` puts them in a single column instead, for a sidebar or a narrow panel. The selection indicator travels either way, and the arrow keys follow the layout.

```tsx
import { ColorSwatchPicker, ColorSwatchPickerItem } from '@appica/ui-react/color-swatch-picker'

const palette = ['#ef4444', '#f59e0b', '#22c55e', '#3b82f6', '#a855f7', '#ec4899']

export default function ColorSwatchPickerLayouts() {
  return (
    <div className="flex items-start gap-12">
      <ColorSwatchPicker aria-label="Accent color" defaultValue="#3b82f6" className="max-w-38">
        {palette.map((color) => (
          <ColorSwatchPickerItem key={color} color={color} />
        ))}
      </ColorSwatchPicker>
      <ColorSwatchPicker aria-label="Accent color" layout="stack" defaultValue="#3b82f6">
        {palette.slice(0, 4).map((color) => (
          <ColorSwatchPickerItem key={color} color={color} />
        ))}
      </ColorSwatchPicker>
    </div>
  )
}
```

### Shapes [#shapes]

`shape` applies to the swatch, to the button and to the indicator at once, so the three stay concentric. The corner is a percentage of the button, so it holds its proportions at every size.

```tsx
import { ColorSwatchPicker, ColorSwatchPickerItem } from '@appica/ui-react/color-swatch-picker'

const palette = ['#ef4444', '#f59e0b', '#22c55e', '#3b82f6']

export default function ColorSwatchPickerShapes() {
  return (
    <div className="flex flex-col items-center gap-6">
      <ColorSwatchPicker aria-label="Accent color" defaultValue="#22c55e">
        {palette.map((color) => (
          <ColorSwatchPickerItem key={color} color={color} />
        ))}
      </ColorSwatchPicker>
      <ColorSwatchPicker aria-label="Accent color" shape="circle" defaultValue="#22c55e">
        {palette.map((color) => (
          <ColorSwatchPickerItem key={color} color={color} />
        ))}
      </ColorSwatchPicker>
    </div>
  )
}
```

### Sizing [#sizing]

`size` uses the same ladder as [Color Swatch](/ui/components/react/color-swatch#sizing), `3xs` 16px through `xl` 64px, and sets the **button**. The swatch inside is 80% of it, which leaves the gap the selected ring sits in. Both are ems off one font size, so the ring, the gap and the swatch scale together rather than the gap staying put as the swatch grows.

```tsx
import { ColorSwatchPicker, ColorSwatchPickerItem } from '@appica/ui-react/color-swatch-picker'

const palette = ['#ef4444', '#22c55e', '#3b82f6']
const sizes = ['2xs', 'sm', 'md', 'lg'] as const

export default function ColorSwatchPickerSizes() {
  return (
    <div className="flex flex-col items-center gap-6">
      {sizes.map((size) => (
        <ColorSwatchPicker key={size} aria-label={`Accent color, ${size}`} size={size} defaultValue="#3b82f6">
          {palette.map((color) => (
            <ColorSwatchPickerItem key={color} color={color} />
          ))}
        </ColorSwatchPicker>
      ))}
    </div>
  )
}
```

### Naming colors [#naming-colors]

A hex string read aloud is a string of digits. `colorName` replaces the description the swatch builds from the color with the name your palette actually uses, and that name is what the option is announced as.

```tsx
'use client'

import { useState } from 'react'
import { ColorSwatchPicker, ColorSwatchPickerItem } from '@appica/ui-react/color-swatch-picker'
import { type Color, formatColor, parseColor } from '@appica/ui-react/color'

const palette = [
  { color: '#0f172a', name: 'Midnight' },
  { color: '#ef4444', name: 'Rose red' },
  { color: '#f59e0b', name: 'Amber' },
  { color: '#22c55e', name: 'Meadow' },
  { color: '#3b82f6', name: 'Sky blue' },
]

export default function ColorSwatchPickerNamed() {
  const [color, setColor] = useState<Color>(() => parseColor('#3b82f6'))
  const selected = palette.find(
    ({ color: swatch }) => formatColor(parseColor(swatch), 'hex') === formatColor(color, 'hex'),
  )

  return (
    <div className="flex flex-col items-center gap-3">
      <ColorSwatchPicker aria-label="Accent color" shape="circle" value={color} onValueChange={setColor}>
        {palette.map(({ color: swatch, name }) => (
          <ColorSwatchPickerItem key={swatch} color={swatch} colorName={name} />
        ))}
      </ColorSwatchPicker>
      <span className="text-sm">{selected?.name}</span>
    </div>
  )
}
```

### Translucent swatches [#translucent-swatches]

An item below full opacity gets the checkerboard the swatch always draws, so a palette of tints reads as tints rather than as four shades of the same flat blue.

```tsx
import { ColorSwatchPicker, ColorSwatchPickerItem } from '@appica/ui-react/color-swatch-picker'

const tints = ['#3b82f6', 'rgba(59, 130, 246, 0.6)', 'rgba(59, 130, 246, 0.3)', 'rgba(59, 130, 246, 0.1)']

export default function ColorSwatchPickerOpacity() {
  return (
    <ColorSwatchPicker aria-label="Overlay tint" defaultValue="rgba(59, 130, 246, 0.6)">
      {tints.map((color) => (
        <ColorSwatchPickerItem key={color} color={color} />
      ))}
    </ColorSwatchPicker>
  )
}
```

### Alongside a picker [#alongside-a-picker]

Presets and a full picker over one piece of state: pick a common color in a click, or open up the area and sliders for anything else. Selecting a preset moves the area and both sliders, and dragging any of them clears the preset, because no swatch matches any more.

A hex preset is an RGB color, which has no saturation or brightness, so the area is pinned with `colorSpace="hsb"` rather than left to follow the value's own space.

```tsx
'use client'

import { useState } from 'react'
import { ColorArea } from '@appica/ui-react/color-area'
import { ColorSlider } from '@appica/ui-react/color-slider'
import { ColorSwatchPicker, ColorSwatchPickerItem } from '@appica/ui-react/color-swatch-picker'
import { type Color, formatColor, parseColor } from '@appica/ui-react/color'

const presets = ['#ef4444', '#f59e0b', '#22c55e', '#3b82f6', '#a855f7', '#ec4899']

export default function ColorSwatchPickerWithPicker() {
  const [color, setColor] = useState<Color>(() => parseColor('#3b82f6'))

  return (
    <div className="flex w-full max-w-56 flex-col gap-3">
      <ColorArea
        value={color}
        onValueChange={setColor}
        colorSpace="hsb"
        xChannel="saturation"
        yChannel="brightness"
        aria-label="Saturation and brightness"
      />
      <ColorSlider channel="hue" value={color} onValueChange={setColor} />
      <ColorSlider channel="alpha" value={color} onValueChange={setColor} />
      <ColorSwatchPicker aria-label="Presets" size="sm" value={color} onValueChange={setColor}>
        {presets.map((preset) => (
          <ColorSwatchPickerItem key={preset} color={preset} />
        ))}
      </ColorSwatchPicker>
      <span className="font-mono text-xs text-nowrap">
        {color.alpha < 1 ? formatColor(color, 'rgba') : formatColor(color, 'hex')}
      </span>
    </div>
  )
}
```

### Disabled [#disabled]

`disabled` on an item takes it out of the tab order and out of the arrow-key run, and gives it the treatment the whole family shares: a flat muted fill inside a dashed outline, dimmed. On the root it does the same to every swatch.

```tsx
import { ColorSwatchPicker, ColorSwatchPickerItem } from '@appica/ui-react/color-swatch-picker'

export default function ColorSwatchPickerDisabled() {
  return (
    <div className="flex flex-col items-center gap-6">
      <ColorSwatchPicker aria-label="Accent color" defaultValue="#ef4444">
        <ColorSwatchPickerItem color="#ef4444" />
        <ColorSwatchPickerItem color="#f59e0b" disabled />
        <ColorSwatchPickerItem color="#22c55e" />
        <ColorSwatchPickerItem color="#3b82f6" disabled />
      </ColorSwatchPicker>
      <ColorSwatchPicker aria-label="Accent color" shape="circle" defaultValue="#ef4444" disabled>
        <ColorSwatchPickerItem color="#ef4444" />
        <ColorSwatchPickerItem color="#f59e0b" />
        <ColorSwatchPickerItem color="#22c55e" />
        <ColorSwatchPickerItem color="#3b82f6" />
      </ColorSwatchPicker>
    </div>
  )
}
```

## RTL [#rtl]

Every Appica UI component supports right-to-left layouts out of the box. Set the `dir` attribute on a container (commonly your `<html>` element) so CSS logical properties resolve correctly, and wrap your tree in `DirectionProvider` so direction-aware behavior (roving focus, popup placement, and the like) follows the same direction.

```tsx
import { DirectionProvider } from '@appica/ui-react/providers/direction-provider'

export default function RootLayout({ children }) {
  return (
    <html lang="ar" dir="rtl">
      <body>
        <DirectionProvider dir="rtl">{children}</DirectionProvider>
      </body>
    </html>
  )
}
```

The swatches run from the right, and &#x2A;*←/→** swap so the arrow pointing at the next swatch is the one that reaches it. A stack is unaffected in either direction. The indicator is measured from the rendered layout rather than from an index, so it lands where the swatches actually are. For setup details and caveats, see the [RTL guide](/ui/docs/react/rtl).


## API reference [#api-reference]

### ColorSwatchPicker [#colorswatchpicker]

| Prop            | Type                                   | Default     | Description                                                                                                                                |
| --------------- | ----------------------------------------------------------------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `value`         | `Color \| string`                                                  | -           | Selected color. Pass a `Color` or any CSS color string to control the component. Inside a `ColorPicker` it can be left off: the control then reads and writes the picker's color. |
| `defaultValue`  | `Color \| string`                                                  | -           | Color selected before any interaction, when the component is uncontrolled.                                                                                                        |
| `onValueChange` | `(value: Color) => void`                                           | -           | Fires with the color that was picked.                                                                                                                                             |
| `layout`        | `'grid' \| 'stack'`                                                | `'grid'`    | Wrapping rows of swatches, or a single column.                                                                                                                                    |
| `size`          | `'3xs' \| '2xs' \| 'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| number` | `'md'`      | A preset scale, or a pixel number for an exact size. Sizes the button; the swatch inside is smaller, so the selected ring has somewhere to sit.                                   |
| `shape`         | `'rounded' \| 'circle'`                                            | `'rounded'` | Rounded square or full circle, applied to every swatch and to the ring.                                                                                                           |
| `disabled`      | `boolean`                                                                     | `false`     | Prevent interaction and swap every color for a flat muted fill.                                                                                                                   |
| `aria-label`    | `string`                                                                      | -           | Accessible name for the palette.                                                                                                                                                  |
| `className`     | `string`                                                                      | -           | Extra classes on the root, merged via `tailwind-merge`.                                                                                                                           |

Every other `<div>` attribute is forwarded to the root. The root carries `data-layout` and `data-disabled`; the parts are addressable through `data-slot` (`color-swatch-picker`, `color-swatch-picker-item`, `color-swatch-picker-indicator`).

### ColorSwatchPickerItem [#colorswatchpickeritem]

| Prop        | Type | Default     | Description                                     |
| ----------- | ------------------------------------------- | ----------- | -------------------------------------------------------------------------------------- |
| `color`     | `Color \| string`                | -           | Color this swatch offers. Pass a `Color` or any CSS color string.                      |
| `colorName` | `string`                                    | color's own | Name announced for the color, in place of the description built from the color itself. |
| `disabled`  | `boolean`                                   | `false`     | Prevent this swatch from being picked, and swap its color for a flat muted fill.       |
| `className` | `string`                                    | -           | Extra classes on the button, merged via `tailwind-merge`.                              |

Every other `<button>` attribute is forwarded to the button, which carries `data-value` (the color's 8-digit hex) and `data-disabled`.

The color model - `Color`, `parseColor`, `formatColor`, `convertColor` and the channel helpers - ships from `@appica/ui-react/color` and is documented on the [Color Area](/ui/components/react/color-area#api-reference) page.

## Accessibility [#accessibility]

* The palette is a `role="listbox"` of `role="option"` buttons with `aria-selected`, following React Aria's model: a set of colors is a list you choose from, not a set of form fields.
* Each option is named by its color - `vivid blue`, `dark muted green` - or by `colorName` when you supply one. The swatch inside is hidden from assistive technology so the name is not read twice.
* Keyboard: the palette is **one*&#x2A; tab stop, landing on the selected swatch. &#x2A;*←/→*&#x2A; move between swatches (swapped in RTL), &#x2A;*↑/↓** jump a row in a grid or a step in a stack, and **Home/End** go to the ends. Selection follows focus, so arrowing through the palette picks as it goes.
* Selection is drawn as a single indicator that slides to the chosen swatch, rather than a border toggled on each one, so what a sighted user sees is one object moving. It honors `prefers-reduced-motion`, as does the press animation.
* The indicator's position is measured from the rendered layout, so it stays correct when the row wraps, the container resizes, or the direction flips.
* A disabled swatch takes the same flat muted fill and dashed outline the area, the slider and the swatch use, so an unavailable color reads the same wherever it appears.
