Skip to content
← Inventory

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 (or defaultValue), not per-swatch onClick — it's a Radix RadioGroup, so it already handles roving focus and arrow-key selection.

  • Choose SwatchGroup when you want radio semantics + keyboard a11y; reach for SwatchSelector only 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

PropTypeDefaultDescription
sizeSwatchSize"base"

Swatch size applied to every item unless overridden per-item.

iconLucideIcon | React.ComponentType<React.SVGProps<SVGSVGElement>>

Icon to display on selected swatch (e.g., Check). When set, replaces the selection ring.

SwatchGroupItem

PropTypeDefaultDescription
colorstring

CSS color value (hex, rgb, hsl, etc.). If provided along with children, children render on top.

aria-labelstring

Defines a string value that labels the current element. Accessible label for screen readers.

childrenReact.ReactNode

Custom content rendered inside the swatch (e.g., images).

sizeSwatchSize

Override size for this specific swatch.

Composition

Builds on

Swatch