ColorSwatchGroup
A group of color swatches with toggle-group selection behavior. Supports single and multiple selection modes with full keyboard navigation.
Preview
Source code
tsx
import { useState } from "react";
import "internationalized-color/css";
import { ColorSwatchGroup } from "@urcolor/react";
const colors = [
"hsl(210, 80%, 50%)",
"hsl(350, 90%, 60%)",
"hsl(120, 60%, 45%)",
"hsl(45, 100%, 55%)",
"hsl(280, 70%, 55%)",
"hsl(15, 85%, 55%)",
];
export default function ColorSwatchGroupBasic() {
const [selected, setSelected] = useState<string[]>([colors[0]!]);
return (
<div className="flex flex-col gap-4">
<ColorSwatchGroup.Root
value={selected}
onValueChange={setSelected}
type="single"
className="flex items-center gap-2"
>
{colors.map((color) => (
<ColorSwatchGroup.Item
key={color}
value={color}
className="
flex size-10 cursor-pointer items-center justify-center rounded-lg
outline-none
"
>
<svg
className={`size-5 text-white drop-shadow-[0_1px_2px_rgba(0,0,0,0.5)] transition-opacity duration-150 ${selected.includes(color) ? "opacity-100" : "opacity-0"}`}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
>
<polyline points="20 6 9 17 4 12" />
</svg>
</ColorSwatchGroup.Item>
))}
</ColorSwatchGroup.Root>
<p className="text-sm text-gray-500">
Selected: <code>{selected[0] ?? "none"}</code>
</p>
</div>
);
}Anatomy
tsx
<ColorSwatchGroup.Root>
<ColorSwatchGroup.Item />
<ColorSwatchGroup.Item />
<ColorSwatchGroup.Item />
</ColorSwatchGroup.Root>Examples
Single Selection
Click a swatch to select it. Clicking the selected swatch deselects it.
Source code
tsx
import { useState } from "react";
import "internationalized-color/css";
import { ColorSwatchGroup } from "@urcolor/react";
const colors = [
"hsl(210, 80%, 50%)",
"hsl(350, 90%, 60%)",
"hsl(120, 60%, 45%)",
"hsl(45, 100%, 55%)",
"hsl(280, 70%, 55%)",
"hsl(15, 85%, 55%)",
];
export default function ColorSwatchGroupBasic() {
const [selected, setSelected] = useState<string[]>([colors[0]!]);
return (
<div className="flex flex-col gap-4">
<ColorSwatchGroup.Root
value={selected}
onValueChange={setSelected}
type="single"
className="flex items-center gap-2"
>
{colors.map((color) => (
<ColorSwatchGroup.Item
key={color}
value={color}
className="
flex size-10 cursor-pointer items-center justify-center rounded-lg
outline-none
"
>
<svg
className={`size-5 text-white drop-shadow-[0_1px_2px_rgba(0,0,0,0.5)] transition-opacity duration-150 ${selected.includes(color) ? "opacity-100" : "opacity-0"}`}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
>
<polyline points="20 6 9 17 4 12" />
</svg>
</ColorSwatchGroup.Item>
))}
</ColorSwatchGroup.Root>
<p className="text-sm text-gray-500">
Selected: <code>{selected[0] ?? "none"}</code>
</p>
</div>
);
}Multiple Selection
Toggle any number of swatches independently.
Source code
tsx
import { useState } from "react";
import "internationalized-color/css";
import { ColorSwatchGroup } from "@urcolor/react";
const colors = [
"hsl(210, 80%, 50%)",
"hsl(350, 90%, 60%)",
"hsl(120, 60%, 45%)",
"hsl(45, 100%, 55%)",
"hsl(280, 70%, 55%)",
"hsl(15, 85%, 55%)",
];
export default function ColorSwatchGroupMultiple() {
const [selected, setSelected] = useState<string[]>([]);
return (
<div className="flex flex-col gap-4">
<ColorSwatchGroup.Root
value={selected}
onValueChange={setSelected}
type="multiple"
className="flex items-center gap-2"
>
{colors.map((color) => (
<ColorSwatchGroup.Item
key={color}
value={color}
className="
flex size-10 cursor-pointer items-center justify-center rounded-lg
outline-none
"
>
<svg
className={`size-5 text-white drop-shadow-[0_1px_2px_rgba(0,0,0,0.5)] transition-opacity duration-150 ${selected.includes(color) ? "opacity-100" : "opacity-0"}`}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
>
<polyline points="20 6 9 17 4 12" />
</svg>
</ColorSwatchGroup.Item>
))}
</ColorSwatchGroup.Root>
<p className="text-sm text-gray-500">
Selected: <code>{selected.length ? selected.join(", ") : "none"}</code>
</p>
</div>
);
}API Reference
ColorSwatchGroup.Root
The root container that manages selection state and keyboard navigation.
| Prop | Type | Default | Description |
|---|---|---|---|
type | 'single' | 'multiple' | 'single' | Whether to allow single or multiple selection. |
value | string[] | [] | The selected color value(s). |
defaultValue | string[] | [] | Default selected value(s) when uncontrolled. |
disabled | boolean | false | Disables all items in the group. |
orientation | 'horizontal' | 'vertical' | 'horizontal' | Orientation for arrow key navigation. |
onValueChange | (value: string[]) => void | — | Called when the selection changes. |
ColorSwatchGroup.Item
An individual selectable swatch.
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | required | The color value. Serves as both the selection key and the displayed color. |
disabled | boolean | false | Disables this item. |
checkerSize | number | 16 | The checkerboard tile size in pixels. |
alpha | boolean | false | When true, reflects the color's alpha channel. |
Data Attributes
| Attribute | Values | Description |
|---|---|---|
data-state | 'on' | 'off' | Whether the item is selected. |
data-disabled | Present when disabled | Whether the item is disabled. |
Accessibility
ColorSwatchGroup uses toggle-group semantics with keyboard navigation across swatch items.
Keyboard Navigation
| Key | Action |
|---|---|
| Tab | Focus the selected item (or first if none selected). |
| Arrow Left / Right | Move focus between items (horizontal). |
| Arrow Up / Down | Move focus between items (vertical). |
| Home / End | Move focus to first / last item. |
| Space / Enter | Toggle the focused item. |