Components
Swatch picker
A row of colour swatches with one selection ring that glides to the chosen colour, wrapping rows included. The chosen swatch steps in to make room for the ring.
pnpm dlx shadcn@latest add @caliper/swatch-pickerWhen to use
Use it for
- Label, tag, calendar and folder colours from a set palette.
- Theme accents in settings.
Skip it when
- Any colour at all; use `ColorPicker`.
- Colours without names; every swatch needs a label people can hear.
Decisions
Why it is built this way, so you know what to keep when you change your copy.
Behaviour
- One ring
- A single ring travels between swatches instead of each swatch drawing its own, so the change reads as a move.
Usage
- A radio group
- Tab lands on the chosen swatch and arrow keys move and choose, as with any set of radio buttons; each swatch is named.
Installation
pnpm dlx shadcn@latest add @caliper/swatch-picker
Adds its 2 files to components/ui/, each with its own stylesheet, and installs their packages. No account needed. The first part also adds Caliper UI’s tokens; import them once as in Installation.
Usage
This is the code behind the preview above.
import { useState, type ReactNode } from "react" import { SwatchPicker, type Swatch } from "@/components/ui/swatch-picker" function Panel({ title, context, className = "max-w-md", children }: { title: string; context: string; className?: string; children: ReactNode }) { return ( <section className={`grid w-full gap-5 rounded-overlay border border-border bg-background p-6 shadow-resting max-sm:p-4 ${className}`}> <div className="grid gap-1"> <p className="font-medium">{title}</p> <p className="text-sm text-muted-foreground">{context}</p> </div> {children} </section> ); } const colours: Swatch[] = [ { value: "slate", label: "Slate", color: "oklch(0.55 0.03 260)" }, { value: "blue", label: "Blue", color: "oklch(0.6 0.16 255)" }, { value: "teal", label: "Teal", color: "oklch(0.65 0.11 195)" }, { value: "green", label: "Green", color: "oklch(0.64 0.15 150)" }, { value: "amber", label: "Amber", color: "oklch(0.76 0.15 82)" }, { value: "orange", label: "Orange", color: "oklch(0.68 0.17 50)" }, { value: "rose", label: "Rose", color: "oklch(0.62 0.2 10)" }, { value: "violet", label: "Violet", color: "oklch(0.58 0.18 295)" }, ]; export function SwatchPickerDemo() { const [colour, setColour] = useState("blue"); const chosen = colours.find((option) => option.value === colour)!; return ( <Panel title="Label colour" context="Used on cards, filters and the board."> <div className="flex items-center gap-2 text-sm"> <span className="inline-flex items-center gap-2 rounded-full border border-border px-3 py-1"> <span className="size-2 rounded-full transition-colors duration-300" style={{ background: chosen.color }} /> Customer research </span> </div> <SwatchPicker label="Label colour" options={colours} value={colour} onValueChange={setColour} /> <SwatchPicker label="Compact label colour" size="sm" options={colours} defaultValue="rose" /> </Panel> ); }
API reference
SwatchPicker
component
Also accepts the native props of its root element and a ref.
Accessibility
It exposes radio and radiogroup semantics, with aria-checked, aria-label and aria-hidden kept in step with state.
Motion
It declares no animation in its component source. Any transition comes from its stylesheet, which uses the duration tokens that collapse to instant under prefers-reduced-motion.