Skip to content
caliper ui docs

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.

Free
pnpm dlx shadcn@latest add @caliper/swatch-picker

Label colour

Used on cards, filters and the board.

Customer research
Installs components/ui/swatch-picker.tsxDepends on none beyond ReactMotion none in component source

When 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

terminal
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.

example.tsx
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.

PropTypeDefaultDescription
label *string—Names the choice, e.g. "Label colour".
options *Swatch[]—
valuestring—
defaultValuestring—
onValueChange((value: string) => void)—
size"sm" | "md""md"

Accessibility

It exposes radio and radiogroup semantics, with aria-checked, aria-label and aria-hidden kept in step with state.

KeyBehaviour
HomeJumps to the first item or the minimum.
EndJumps to the last item or the maximum.

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.

All Surfaces examples