bigbullui logobigbullui
Componentpickers

Size Picker

Size chip picker with out-of-stock strikes and stock dots.

Preview

Live Preview
Premium Headphones
25%

Premium Headphones

$299$399

Jordan Daisy

Product Designer

AvailableRemote
The Future of Design Systems

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
ItemQtyPrice
Design Services1$150
Development Hours5$100
SubtotalNaN
Tax (10%)NaN
TotalNaN
THERMAL RECEIPT#Q7P2QZ
Design Consultation$150
Development Hours$200
Total$385.00
Thank you for your purchase!
Premium Headphones
1
Phone Case
2
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 size-picker

Or install the full npm package:

npm install bigbullui

Usage

import { SizePicker } from "@/components/ui/size-picker";

<SizePicker />

Source code

size-picker.tsx

Zero 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 SizeOption {
  label: string;
  available?: boolean;
  stock?: number;
}

export interface SizePickerProps {
  sizes: SizeOption[];
  value?: string;
  defaultValue?: string;
  onValueChange?: (value: string) => void;
  className?: string;
}

const SizeChip = "size-chip";
const Selected = "size-chip-selected";
const OutOfStock = "size-chip-out-of-stock";
const FadeIn = "size-picker-fade-in";

const sizeMap: Record<string, string> = {
  S: "text-xs py-1 px-2 rounded border border-border hover:bg-secondary/20 transition-colors",
  M: "text-sm py-1.5 px-3 rounded border border-border hover:bg-secondary/20 transition-colors",
  L: "text-sm py-1.5 px-3 rounded border border-border hover:bg-secondary/20 transition-colors",
  XL: "text-sm py-1.5 px-3 rounded border border-border hover:bg-secondary/20 transition-colors",
};

export function SizePicker({
  sizes,
  value,
  defaultValue,
  onValueChange,
  className,
}: SizePickerProps) {
  const [controlledValue, setControlledValue] = React.useState<
    | string
    | undefined
  >(defaultValue ?? undefined);

  React.useEffect(() => {
    if (value !== undefined) setControlledValue(value);
  }, [value]);

  return (
    <div className={cn("space-y-1.5", className)}>
      {sizes.map((opt, i) => {
        const isAvailable = opt.available !== false;
        const isSelected = controlledValue === opt.label;
        const isOutOfStock = !isAvailable;

        return (
          <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",
              isSelected && Selected,
              isOutOfStock && OutOfStock,
              !isSelected && !isOutOfStock && SizeChip,
              sizeMap[opt.label],
              "motion-reduce:transition-none",
              "cursor-pointer"
            )}
            onClick={() => {
              if (isAvailable && onValueChange) {
                onValueChange(opt.label);
              }
            }}
            disabled={!isAvailable}
            aria-disabled={!isAvailable}
            title={isAvailable ? `Select ${opt.label}` : "Out of stock"}
          >
            <span className="relative">
              {opt.label}
              {isOutOfStock && (
                <span className="absolute -top-0.5 -right-0.5 size-1.5 rounded-full border-2 border-border bg-card">
                  <span className="size-0.5 rounded-full bg-destructive" />
                </span>
              )}
              {isOutOfStock && (
                <span className="ml-1 text-xxs opacity-50">OOS</span>
              )}
            </span>
          </button>
        );
      })}
    </div>
  );
}

Props

PropTypeDescription
sizesSizeOption[]Available sizes with stock flags (required).
valuestringControlled selected size.
defaultValuestringInitial selection.
onValueChange(value: string) => voidSelection callback.