Skip to content

ColorSwatchGroup

Form

A named palette of color choices with native radio semantics.

Workspace accentCompact paletteLight and dark swatchesDisabled choices
Open preview

Responsive viewport — resize the window to explore. Direction and density apply only to this preview. Theme applies to the docs too.

Usage

Offer a curated palette when users should choose a known accent or product variant rather than an arbitrary color. Provide unique values, human-readable color names, and valid CSS background colors. Own the selected value and use name for native form submission. Swatches support standard and compact sizes, and unavailable colors can remain visible as disabled choices. This selects a value; it does not modify the global application theme.

Import
import { ColorSwatchGroup } from "@/components/ui/color-swatch-group";

Shipped as editable source in src/components/ui/color-swatch-group.tsx in both apps. Copy the implementation below when moving it to another project. Copy the complete example from Source code for all imports and state. Add "use client" when using hooks or event handlers. See installation and theming for project setup.

Component source

The complete implementation is included in both apps. It uses the React, Radix, Lucide, and shared-core dependencies already installed in Apex. Keep your project’s semantic theme tokens when reusing it.

src/components/ui/color-swatch-group.tsx
"use client";
import { useId } from "react";
import { cn } from "@dashboardpack/core/lib/utils";
export interface ColorSwatch {
  value: string;
  label: string;
  color: string;
  disabled?: boolean;
}
export interface ColorSwatchGroupProps {
  label: string;
  options: ColorSwatch[];
  value: string;
  onValueChange: (value: string) => void;
  disabled?: boolean;
  name?: string;
  size?: "sm" | "default";
  className?: string;
}
export function ColorSwatchGroup({
  label,
  options,
  value,
  onValueChange,
  disabled,
  name,
  size = "default",
  className,
}: ColorSwatchGroupProps) {
  const id = useId();
  return (
    <fieldset
      disabled={disabled}
      className={cn("min-w-0 space-y-3", className)}
    >
      <legend className="mb-2 text-sm font-medium">{label}</legend>
      <div className="flex flex-wrap gap-3">
        {options.map((option) => (
          <label
            key={option.value}
            className={cn(
              "flex cursor-pointer flex-col items-center gap-2 rounded-lg border p-2 has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-ring",
              option.value === value
                ? "border-primary bg-primary/5"
                : "border-transparent",
              (disabled || option.disabled) && "cursor-not-allowed opacity-50",
            )}
          >
            <input
              type="radio"
              className="sr-only"
              name={name ?? id}
              value={option.value}
              checked={option.value === value}
              disabled={disabled || option.disabled}
              onChange={() => onValueChange(option.value)}
            />
            <span
              aria-hidden
              style={{ backgroundColor: option.color }}
              className={cn(
                "rounded-full border border-foreground/15 shadow-xs",
                size === "sm" ? "size-6" : "size-9",
              )}
            />
            <span className="text-xs">{option.label}</span>
          </label>
        ))}
      </div>
      {!options.length && (
        <p className="text-sm text-muted-foreground">No colors available.</p>
      )}
    </fieldset>
  );
}

API reference

Common props and defaults. Slot-specific props are named below; native elements also accept their standard HTML attributes. The component TypeScript definitions are the complete type reference.

ColorSwatchGroup props
PropType / DefaultDescription
labelstring

Default: required

Visible palette legend.
optionsColorSwatch[]

Default: required

Unique value, label, CSS color, and optional disabled flag.
valuestring

Default: required

Controlled selected palette value.
onValueChange(value: string) => void

Default: required

Apply a selected value.
namestring

Default: generated

Native radio form field name.
sizesm | default

Default: default

Swatch visual size; labels remain visible.
disabledboolean

Default: false

Disable the complete palette.
classNamestring

Default: —

Extend the container with semantic theme utilities.

Build with these too