Componentpickers
Variant Picker
Color swatch and size variant picker with animated selection.
Preview
Live Preview

25%
Premium Headphones
$299$399
Jordan Daisy
Product Designer
AvailableRemote

The Future of Design Systems
Alex Rivera8 min read
Oc
t 14
Tech Conference 2026
21:00
Berlin Arena
Invoice #INV-2026-0987draft
Date: 2026-09-15Due: 2026-10-15
| Item | Qty | Price |
|---|---|---|
| Design Services | 1 | $150 |
| Development Hours | 5 | $100 |
SubtotalNaN
Tax (10%)NaN
TotalNaN
THERMAL RECEIPT#D9DZLH
Design Consultation$150
Development Hours$200
Total$385.00
Thank you for your purchase!
Subtotal$377.00
Tax$30.16
Total$407.16
Color
Size
✓✓✓✓✓✓
Order #102938Processing
Design Services1x
Development5x
VIP
LOYALTY TIER
$100
Jordan Daisy
Happy Birthday!
J
Jordan Daisy
Product Designer
UX
Badge: EMP-8942
Wedding Reception
Please confirm your attendance
Installation
Tokens guide →Add to your project via CLI (zero dependencies, code you own):
npx bigbullui add variant-pickerOr install the full npm package:
npm install bigbulluiUsage
import { VariantPicker } from "@/components/ui/variant-picker";
<VariantPicker />Source code
variant-picker.tsxZero dependencies (only React + utils). Copy and paste directly into your project:
"use client";
import * as React from "react";
import { cn } from "./lib/utils";
export interface ColorOption {
name: string;
className?: string;
}
export interface VariantPickerProps {
colors: ColorOption[];
sizes?: string[];
value?: string;
defaultValue?: string;
onColorChange?: (name: string) => void;
onSizeChange?: (size: string) => void;
className?: string;
}
const ColorSwatch = "color-swatch";
const SelectedSwatch = "color-swatch-selected";
const SizeChip = "size-picker-chip";
const FadeIn = "variant-picker-fade-in";
// Explicit color map — NO dynamic class strings
const colorMap: Record<string, string> = {
navy: "bg-navy hover:bg-navy/80",
green: "bg-green-500 hover:bg-green-400",
black: "bg-gray-900 hover:bg-gray-800",
white: "bg-white hover:bg-gray-100",
brown: "bg-amber-500 hover:bg-amber-400",
};
export function VariantPicker({
colors,
sizes,
value,
defaultValue,
onColorChange,
onSizeChange,
className,
}: VariantPickerProps) {
const [controlledValue] = React.useState<string | undefined>(
defaultValue ?? undefined
);
return (
<div className={cn("space-y-3", className)}>
{/* Color swatches */}
<div>
<span className="text-xs uppercase tracking-wider text-muted-foreground mb-1">Color</span>
<div className="flex gap-1.5">
{colors.map((opt, i) => {
const selected = controlledValue === opt.name;
const baseClass = colorMap[opt.name] || "bg-gray-200 hover:bg-gray-300";
return (
<button
key={i}
type="button"
className={cn(
"rounded-full w-6 h-6 transition-colors",
ColorSwatch,
selected && SelectedSwatch,
"motion-reduce:transition-none",
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
)}
style={{ backgroundColor: opt.className ? `var(${opt.className})` : "var(--secondary)" }}
onClick={() => onColorChange?.(opt.name)}
aria-label={`Select ${opt.name} color`}
>
{/* Swatch fills from explicit map */}
</button>
);
})}
</div>
</div>
{/* Size row */}
{sizes && sizes.length > 0 && (
<div>
<span className="text-xs uppercase tracking-wider text-muted-foreground mb-1">Size</span>
<div className="flex gap-1.5">
{sizes.map((size, i) => (
<button
key={i}
type="button"
className={cn(
"inline-flex rounded-md border border-2 border-dashed items-center justify-center gap-1.5 px-3 py-1.5 text-xs font-mono uppercase tracking-wider",
SizeChip,
"motion-reduce:transition-none",
"cursor-pointer"
)}
onClick={() => onSizeChange?.(size)}
aria-label={`Select size ${size}`}
>
{size}
</button>
))}
</div>
</div>
)}
</div>
);
}Props
| Prop | Type | Description |
|---|---|---|
| sizes | string[] | Size options list. |
| value | string | Controlled selection. |
| onColorChange | (name: string) => void | Color swatch callback. |
| onSizeChange | (size: string) => void | Size selection callback. |