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.jsonPlayground
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.jsonOr, 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
| Prop | Type | Default | Description |
|---|---|---|---|
colors | SwatchColor[] | — | List of selectable color swatches ({ name, value, light? }); value also accepts a gradient string |
sizes | string[] | — | List of selectable sizes |
stock | Record<string, Record<string, number>> | — | Stock table, stock[color][size] = units; combinations that are not listed count as sold out |
lowStockThreshold | number | 5 | Low stock threshold; when the selected combination's stock is less than or equal to this, "Only N left" is shown |
defaultColor | string | First color in stock | Name of the color selected by default |
defaultSize | string | — | Size selected by default; treated as unselected when that combination is sold out |
showPreview | boolean | true | Whether to show the product preview block above |
onChange | (selection) => void | — | Selection change callback, carrying { color, size, stock }; a size of null means no size has been chosen yet |
className | string | — | Forwarded to the outermost container |
Accessibility
- Colors and sizes are each a
role="radiogroup", and the options arerole="radio"carryingaria-checkedstate - Sold-out colors and sizes are set
disabled, and theiraria-labelnotes "(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-visiblefocus ring