SwatchGroup
Row of selectable circular color/image swatches with single-select behavior,
built on Radix RadioGroup. Selection is controlled via value +
onValueChange, or uncontrolled via defaultValue. When icon is set the
selected swatch shows that icon instead of the default selection ring.
Finish: Terracotta
Usage
@desk/ui/components/swatch-group
<Stack align="center" gap="base">
<SwatchGroup
aria-label="Choose a finish"
onValueChange={setValue}
value={value}
>
{FINISHES.map((finish) => (
<SwatchGroupItem
aria-label={finish.label}
color={finish.color}
key={finish.value}
value={finish.value}
/>
))}
</SwatchGroup>
<Text size="sm" variant="muted">
Finish: {selected?.label ?? "None"}
</Text>
</Stack>Best practices
Drive selection with
value+onValueChange(ordefaultValue), not per-swatchonClick— it's a RadixRadioGroup, so it already handles roving focus and arrow-key selection.Choose
SwatchGroupwhen you want radio semantics + keyboard a11y; reach forSwatchSelectoronly for a lighter controlled row without the RadioGroup roles.Set
icon(e.g.Check) on the group to swap the selection ring for a glyph on the chosen swatch, rather than styling items individually.
Props
SwatchGroup
| Prop | Type | Default | Description |
|---|---|---|---|
| size | SwatchSize | "base" | Swatch size applied to every item unless overridden per-item. |
| icon | LucideIcon | React.ComponentType<React.SVGProps<SVGSVGElement>> | Icon to display on selected swatch (e.g., Check). When set, replaces the selection ring. |
SwatchGroupItem
| Prop | Type | Default | Description |
|---|---|---|---|
| color | string | CSS color value (hex, rgb, hsl, etc.). If provided along with children, children render on top. | |
| aria-label | string | Defines a string value that labels the current element. Accessible label for screen readers. | |
| children | React.ReactNode | Custom content rendered inside the swatch (e.g., images). | |
| size | SwatchSize | Override size for this specific swatch. |
Composition
Builds on