Swatch Grid

A grid of pickable samples, a color or a glyph over a color, of which at most one is chosen. The arrow keys move through it in two dimensions and only a click, Enter or Space chooses, so a pick with side effects never fires on a focus move.

Label colorBlue

Installation#

# One-time setup (tokens + lib helpers)npx koalaui-cli@latest init# Add this component (its dependencies come along)npx koalaui-cli@latest add swatch-grid

Usage#

usage.tsxTSX
import { SwatchGrid, SwatchGridItem } from "@/components/ui/swatch-grid"export function Example() {  const [color, setColor] = React.useState("blue")  return (    <SwatchGrid aria-label="Label color" className="grid-cols-8" value={color} onValueChange={setColor}>      <SwatchGridItem value="tomato" label="Tomato" color="#e5484d" />      <SwatchGridItem value="blue" label="Blue" color="#0090ff" />      <SwatchGridItem value="jade" label="Jade" color="#29a383" />    </SwatchGrid>  )}

Anatomy#

import { SwatchGrid, SwatchGridItem } from "@/components/ui/swatch-grid"<SwatchGrid>  <SwatchGridItem /></SwatchGrid>

Glyphs over a color#

size="md" makes 2rem cells with the glyph inset 0.1875rem, for an icon, a sprite <img> or a Phosphor <svg>; sm (the default) makes 1.75rem cells for plain colors. Here the icon grid is painted in the color chosen above it, so every cell shows the marker as it will look. A cell without color paints nothing, which suits the "Automatic" way back to a default.

Unit marker

Automatic

Translucent colors#

checkered lays a checkerboard under the cell's color. An opaque color hides it; a translucent one lets it through, so 50% black reads as half-transparent rather than as a dim grey.

Disabled#

A disabled cell dims, takes no press and is skipped by the arrow keys. Say why in its label, since that is all a screen reader gets.

Keyboard#

The grid is one stop in the Tab order. Moving never chooses: a pick can cost a request, so only a deliberate key does. ↑ and ↓ read the rows from the layout, so they follow whatever column count the grid has at that width.

KeyAction
← →Move to the previous or next cell, wrapping at the ends.
↑ ↓Move to the nearest cell in the row above or below.
Home EndMove to the first or last cell.
Enter SpaceChoose the focused cell.

API reference#

SwatchGrid

The root: Radix ToggleGroup.Root with type="single", laid out as a CSS grid. Set the columns with className (e.g. grid-cols-8), merged last. Name it with aria-label.

PropTypeDefaultDescription
valuestring-The chosen cell's value; "" for none.
defaultValuestring""The chosen cell when uncontrolled.
onValueChange(value: string) => void-Called with the newly chosen cell, never with "": pressing the chosen cell again keeps it. Listen to the cell's onClick to act on a repeat press.
size"sm" | "md""sm"Reaches every cell: sm (1.75rem) for colors, md (2rem, the glyph inset 0.1875rem) for glyphs.
disabledbooleanfalseDisables every cell.
loopbooleantrue←/→ wrap from the last cell to the first.
rovingFocusbooleantrueRadix roving focus: the grid is one Tab stop and the arrow keys move inside it.
dir"ltr" | "rtl"-The reading direction ←/→ follow.
staticbooleanfalseDrops the press scale on every cell.
Data attributeDescription
data-slot"swatch-grid".

SwatchGridItem

A cell: a Radix ToggleGroup.Item (role="radio"). Every button prop is forwarded; children are a glyph (an <img> or an <svg>, sized to the cell).

PropTypeDefaultDescription
value*string-The cell's value.
label*string-The accessible name: a swatch carries no visible text.
colorstring-The fill: any CSS color, set as --swatch. Without it, no fill.
checkeredbooleanfalseLays a checkerboard under the color, so a translucent one reads as translucent, not dim.
disabledbooleanfalseTakes the cell out of the roving focus and dims it.
staticboolean-Drops the press scale on this cell. Falls back to the grid's static.
Data attributeDescription
data-slot"swatch-grid-item".
data-state"on" on the chosen cell, "off" on the rest.
data-disabledPresent when the cell is disabled.
CSS variableDescription
--swatchThe cell's color, from color. transparent on the root, so a cell without one inherits nobody's.
--surfaceRead for the gap between the cell and its selection ring: the ground the grid sits on (falls back to --background).

FAQ#

RadioSwatch is for a handful of product colors in a form, where the arrow keys choosing as they move is the radio convention. Swatch Grid is for dozens of samples where a pick has effects, so moving never chooses, ↑ and ↓ move by rows, and a cell can hold a glyph.