Quantity stepper

Accessible quantity control with min and max.

ui
$npx shadcn@latest add @storekit/stepper
stepper

Live and interactive. Switch the brand in the frame header to see the same source under another token file.

Installation

$npx shadcn@latest add @storekit/stepper
$pnpm dlx shadcn@latest add @storekit/stepper
$yarn dlx shadcn@latest add @storekit/stepper
$bunx --bun shadcn@latest add @storekit/stepper

Source

components/storekit/ui/stepper.tsx
48 lines
"use client"

import { Minus, Plus } from "lucide-react"
import { cn } from "@/components/storekit/lib/cn"

/* Quantity stepper. Buttons are 40px+ targets at every size; the value is a
   live region so screen readers hear each change. Holds min/max so cart
   rules live in one place. */

export function Stepper({
  value,
  onChange,
  min = 1,
  max = 99,
  size = "md",
  label = "Quantity",
  className,
}: {
  value: number
  onChange: (next: number) => void
  min?: number
  max?: number
  size?: "sm" | "md"
  /** Accessible name for the group, e.g. "Quantity, Daily Greens". */
  label?: string
  className?: string
}) {
  const h = size === "sm" ? "h-10" : "h-11"
  const btn = cn(
    "grid aspect-square h-full place-items-center text-sk-ink transition-colors hover:bg-sk-ink/[0.05] active:bg-sk-ink/[0.08] disabled:opacity-30 disabled:hover:bg-transparent"
  )
  return (
    <div
      role="group"
      aria-label={label}
      className={cn("inline-flex items-stretch overflow-hidden rounded-sk-btn border border-sk-line bg-sk-card", h, className)}
    >
      <button type="button" className={btn} aria-label="Decrease" disabled={value <= min} onClick={() => onChange(Math.max(min, value - 1))}>
        <Minus size={14} />
      </button>
      <span aria-live="polite" aria-atomic="true" className="grid min-w-10 place-items-center px-1 text-sm font-medium tabular-nums">{value}</span>
      <button type="button" className={btn} aria-label="Increase" disabled={value >= max} onClick={() => onChange(Math.min(max, value + 1))}>
        <Plus size={14} />
      </button>
    </div>
  )
}

Usage

Import from "@/components/storekit/ui/stepper" and pass your product data. The demo above exercises the full prop surface with sample values.

Quantity stepper, StoreKit