Quantity stepper
Accessible quantity control with min and max.
ui
$
npx shadcn@latest add @storekit/stepperstepper
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/stepperSource
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.