Back to Buttons
Components / Buttons
Quantity Stepper Button
Free Buttons React component — QuantityStepperButton. MIT licensed, copy-paste ready for Next.js and Tailwind CSS.
Inline minus / count / plus control for carts and forms. Clamps between min and max, disables at the edges.
Preview
1
Setup
How to use
Free for personal and commercial use. No UI attribution required. Include the MIT copyright notice when copying component source into your project. Read the MIT license.
- 1
Run in your terminal:
$npm install clsx tailwind-merge lucide-react - 2
Copy
lib/cn.tsbelow. Skip if you already havecn(). - 3
Copy the code below and create
components/buttons/quantity-stepper-button.tsxin your project. - 4
Import and render:
Exampleimport { QuantityStepperButton } from "@/components/buttons/quantity-stepper-button";
lib/cn.ts
import { type ClassValue, clsx } from "clsx";import { twMerge } from "tailwind-merge";export function cn(...inputs: ClassValue[]) {return twMerge(clsx(inputs));}
components/buttons/quantity-stepper-button.tsx
"use client";import { forwardRef, useState, type ComponentPropsWithoutRef } from "react";import { cn } from "@/lib/cn";import { Minus, Plus } from "lucide-react";export type QuantityStepperButtonProps = Readonly<{value?: number;min?: number;max?: number;onChange?: (value: number) => void;} & Omit<ComponentPropsWithoutRef<"div">, "onChange">>;// Quantity stepper — minus, count, plus in one inline control.export const QuantityStepperButton = forwardRef<HTMLDivElement,QuantityStepperButtonProps>(({className,value: defaultValue = 1,min = 0,max = 99,onChange,...props},ref,) => {const [value, setValue] = useState(defaultValue);const update = (next: number) => {const clamped = Math.min(max, Math.max(min, next));setValue(clamped);onChange?.(clamped);};return (<divref={ref}data-slot="quantity-stepper-button"className={cn("inline-flex h-11 items-stretch overflow-hidden rounded-xl border border-neutral-200 bg-white font-sans text-sm font-medium shadow-sm select-none",className,)}{...props}><buttontype="button"aria-label="Decrease quantity"disabled={value <= min}onClick={() => update(value - 1)}className="flex w-11 cursor-pointer items-center justify-center text-neutral-500 transition-colors hover:bg-neutral-50 disabled:cursor-default disabled:opacity-30"><Minus size={15} strokeWidth={2} /></button><spanclassName="flex min-w-10 items-center justify-center border-x border-neutral-100 px-2 tabular-nums text-neutral-900"aria-live="polite">{value}</span><buttontype="button"aria-label="Increase quantity"disabled={value >= max}onClick={() => update(value + 1)}className="flex w-11 cursor-pointer items-center justify-center text-neutral-500 transition-colors hover:bg-neutral-50 disabled:cursor-default disabled:opacity-30"><Plus size={15} strokeWidth={2} /></button></div>);},);QuantityStepperButton.displayName = "QuantityStepperButton";