Skip to content
All Taste components

Inputs

Colour swatch picker

Pick one colour from a set of swatches, or add your own.

selected ring · custom swatch · keyboard (arrow keys)

Label colourBrand
Used for the “Festive menu” tag on your orders.
"use client";
import { Plus } from "lucide-react";
import { cn } from "@/lib/utils";

// Full class names (fill + ring) so Tailwind can see them; all come from your Taste preset.
export const SWATCHES = [
  { name: "Brand", fill: "bg-brand-600", ring: "border-brand-600" },
  { name: "Brand light", fill: "bg-brand-300", ring: "border-brand-300" },
  { name: "Blue", fill: "bg-info", ring: "border-info" },
  { name: "Green", fill: "bg-success", ring: "border-success" },
  { name: "Amber", fill: "bg-warning", ring: "border-warning" },
  { name: "Red", fill: "bg-danger", ring: "border-danger" },
  { name: "Grey", fill: "bg-neutral-400", ring: "border-neutral-400" },
  { name: "Ink", fill: "bg-neutral-900", ring: "border-neutral-900" },
];

/* Native radios: Tab enters the group, arrow keys move the selection. */
export function SwatchPicker({ label, value, onChange, onAddCustom, swatches = SWATCHES }:
  { label: string; value: string; onChange: (name: string) => void; onAddCustom?: () => void; swatches?: typeof SWATCHES }) {
  return (
    <fieldset className="grid gap-2">
      <legend className="mb-2 flex w-full items-baseline justify-between text-[13px] font-medium text-fg">
        {label}
        <span className="font-normal text-fg-muted">{value}</span>
      </legend>
      <div className="flex flex-wrap gap-2.5 p-1">
        {swatches.map((s) => (
          <label key={s.name} title={s.name} className="relative size-8 cursor-pointer">
            <input
              type="radio"
              name={label}
              value={s.name}
              aria-label={s.name}
              checked={value === s.name}
              onChange={() => onChange(s.name)}
              className={cn(
                "peer absolute inset-0 m-0 size-full cursor-pointer appearance-none rounded ring-1 ring-inset ring-fg/15 transition",
                "focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-[6px] focus-visible:outline-ring",
                s.fill,
              )}
            />
            {/* Selected: a gap in the page colour, then a ring in the swatch's own colour. */}
            <span aria-hidden className={cn("pointer-events-none absolute -inset-1 hidden rounded-lg border-2 peer-checked:block", s.ring)} />
          </label>
        ))}
        {onAddCustom && (
          <button type="button" aria-label="Add a custom colour" onClick={onAddCustom}
            className="grid size-8 place-items-center rounded border-[1.5px] border-dashed border-border-strong text-fg-muted transition hover:border-fg-subtle hover:text-fg">
            <Plus className="size-4" />
          </button>
        )}
      </div>
    </fieldset>
  );
}

The code uses the Taste Tailwind preset. Make yours on the Taste page with Export my system.