WebberUI

Variant Swatch Picker

A color swatch and size picker with sold-out slashes, a springing selection ring, and low stock hints.

The variant picker for an e-commerce product page: the selection ring around the color swatches springs and slides between them, cross-fading the product preview block above in sync; sold-out size cells are struck through with a slash and cannot be selected; and when the selected combination falls below the stock threshold, an "Only N left" hint pops up.

Loading preview…
npx shadcn@latest add https://webberui.com/r/variant-swatch-picker.json

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

5
<VariantSwatchPicker />

Installation

npx shadcn@latest add https://webberui.com/r/variant-swatch-picker.json

Or, once registries are configured in components.json, install it as @webberui/variant-swatch-picker.

Usage

import { VariantSwatchPicker } from "@/components/ui/variant-swatch-picker";

<VariantSwatchPicker
  colors={[
    { name: "Graphite", value: "#26262b" },
    { name: "Oat", value: "#ece5d8", light: true },
  ]}
  sizes={["S", "M", "L"]}
  stock={{
    Graphite: { S: 12, M: 8, L: 0 },
    Oat: { S: 0, M: 4, L: 15 },
  }}
  defaultColor="Graphite"
  onChange={({ color, size, stock }) => console.log(color, size, stock)}
/>

Props

PropTypeDefaultDescription
colorsSwatchColor[]List of selectable color swatches ({ name, value, light? }); value also accepts a gradient string
sizesstring[]List of selectable sizes
stockRecord<string, Record<string, number>>Stock table, stock[color][size] = units; combinations that are not listed count as sold out
lowStockThresholdnumber5Low stock threshold; when the selected combination's stock is less than or equal to this, "Only N left" is shown
defaultColorstringFirst color in stockName of the color selected by default
defaultSizestringSize selected by default; treated as unselected when that combination is sold out
showPreviewbooleantrueWhether to show the product preview block above
onChange(selection) => voidSelection change callback, carrying { color, size, stock }; a size of null means no size has been chosen yet
classNamestringForwarded to the outermost container

Accessibility

  • Colors and sizes are each a role="radiogroup", and the options are role="radio" carrying aria-checked state
  • Sold-out colors and sizes are set disabled, and their aria-label notes "(sold out)" for assistive technology to announce
  • The stock hint block carries aria-live="polite", so stock changes get announced, and its height is fixed so the layout does not jump
  • When the user has "reduce motion" enabled at the system level, the ring and the background land directly and the preview block switches instantly, with no spring animation played
  • Every option is keyboard operable and carries a clear focus-visible focus ring

On this page