Forms

Color Swatch Picker

Accessible color picker built on react-aria-components, styled with the quebi design system. A wrapping grid of selectable swatches with a brand-teal selection ring and focus state.

formcolorpickerswatchinteractive

Default

A grid of swatches; the selected one shows a brand-teal ring.

Disabled item

Individual swatches can be disabled.

Controlled

#A855F7

Source

Copy this into your project. Resolve its dependencies from the registryDependencies in the component's API entry.

"use client"

import {
  ColorSwatchPicker as ColorSwatchPickerPrimitive,
  type ColorSwatchPickerItemProps,
  ColorSwatchPickerItem as ColorSwatchPickerItemPrimitive,
  type ColorSwatchPickerProps,
} from "react-aria-components"
import { cn } from "@/lib/utils"

/**
 * ColorSwatchPicker — quebi design system
 *
 * Built on react-aria-components. A wrapping grid of selectable color swatches.
 * The selected swatch gets a quebi-brand ring plus a small dot marker; focus
 * uses the quebi teal ring. Self-contained: render a ColorSwatch (from
 * react-aria-components) inside each item.
 */
export function ColorSwatchPicker({ className, ...props }: ColorSwatchPickerProps) {
  return (
    <ColorSwatchPickerPrimitive
      data-slot="control"
      className={cn("flex flex-wrap gap-2", className)}
      {...props}
    />
  )
}

export function ColorSwatchPickerItem({
  children,
  className,
  ...props
}: ColorSwatchPickerItemProps) {
  return (
    <ColorSwatchPickerItemPrimitive
      data-slot="item"
      className={cn(
        "group relative rounded-quebi-sm outline-hidden",
        "*:rounded-quebi-sm",
        "transition-opacity duration-150",
        "data-[selected]:ring-2 data-[selected]:ring-quebi-brand data-[selected]:ring-offset-2 data-[selected]:ring-offset-quebi-bg",
        "data-[focus-visible]:ring-2 data-[focus-visible]:ring-quebi-brand/50 data-[focus-visible]:ring-offset-2 data-[focus-visible]:ring-offset-quebi-bg",
        "hover:opacity-90",
        "data-[disabled]:opacity-50 data-[disabled]:cursor-not-allowed",
        className,
      )}
      {...props}
    >
      {(values) => (
        <>
          {values.isSelected && (
            <span
              aria-hidden="true"
              className="pointer-events-none absolute bottom-1 left-1/2 size-1.5 -translate-x-1/2 rounded-full bg-white/80 shadow-quebi-glow"
            />
          )}
          {typeof children === "function" ? children(values) : children}
        </>
      )}
    </ColorSwatchPickerItemPrimitive>
  )
}