Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelogContact
Components
Buttons & Actions
ButtonButton GroupCopy ButtonDelete ButtonTheme ToggleToggleToggle Group
Inputs
Avatar UploadCalendarCheckboxColor PickerComboboxCountry SelectCron PickerCurrency InputDate PickerDate Range PickerDate Time PickerDuration PickerEmail InputEmoji PickerFile UploadFilter BuilderInputInput GroupListboxMasked InputMention InputMonth PickerMulti SelectNumber InputOTP InputPassword InputPhone InputRadio GroupRatingSearch InputSegmented ControlSelectSignature PadSliderSwitchTag InputTextareaTime PickerTimezone SelectTree Select
Forms
Address InputBooking WidgetCSV ImportFieldForm WizardInline EditInvoice line itemsRich Text EditorSchema Builder
Navigation
Back To TopBounce SidebarBreadcrumbCommand MenuGlobal SearchGooey NavHook SidebarNav MenuNav TabsPaginationProximity SidebarScroll ProgressSidebarStepsTable Of ContentsTabsTopbarUser MenuWorkspace Switcher
Overlays
Alert DialogContext MenuDialogDrawerDropdown MenuFamily DrawerHover CardPopoverShare DialogSheetShortcuts DialogTooltipTour
Feedback
AlertBannerCookie ConsentEmoji ReactionEmpty StateNotification BellNotification CenterProgressRoute ProgressSetup ChecklistSkeletonSpinnerToast
Data Display
Approval FlowAvatarBadgeCalendar ViewCode BlockComment ThreadContact cardCountdownDescription ListDiff ViewerFile ListFile managerFolderGanttJSON ViewerKbdLog ViewerMarkdownPresenceSchedulerTagTimelineTree View
Tables & Lists
Audit LogData TableDeal PipelineEditable GridKanbanListMembers tablePermissions MatrixTableTask ListVirtual List
Charts & Stats
Animated CounterArea chartBar chartDonut chartFunnel chartGaugeGitHub ActivityHeatmap calendarLeaderboardLine chartRadar chartRadial barSparklineStat CardTrackerTrend badge
Layout
AccordionApp ShellCardCollapsibleDetail LayoutEntity HeaderPage HeaderScroll AreaSeparatorSettings LayoutSplit ViewToolbar
Media
Audio PlayerCarouselCSV ViewerDOCX EditorDOCX ViewerExcel ViewerFile SystemImage compareImage CropperImage GalleryImage LightboxLocation MapPDF ViewerPowerPoint ViewerQR CodeStep PlayerVideo Player
AI Kit
Agent Status CardChat MessageChat ThreadChat WidgetConversation ListFluid OrbGrid RevealMatrix OrbModel PickerPrompt InputReasoning IndicatorRule BuilderSuggestion ChipsTool Call TimelineUsage MeterVoice InputWorkflow Canvas
Text & Effects
Background PatternGravity LettersMarqueeTerminalText AnimateText Rotate
Mobile
Action SheetBottom Tab BarFABHaptic ButtonHero HeaderInstall PromptLarge Title HeaderNotification BannerOnboarding CarouselPicker WheelPull To RefreshSlide To ConfirmSticky CTA BarSwipe DeckSwipeable Tabs
Commerce
Cart drawerCategory pickerCheckout stepsCoupon inputFavorite buttonFilter panelListing cardListing gridOrder trackerPricePrice rangePricing tableProduct galleryQuantity stepperReviewsSeller card
Marketing Sections
App Download SectionBlog Cards SectionCTA SectionFAQ SectionFeature GridFeature ShowcaseHero SectionIntegrations SectionLogo CloudMarketing FooterMarketing HeaderMarketing SectionNewsletter SectionPricing SectionStats SectionSteps SectionTeam SectionTestimonials SectionTimeline SectionVideo Section
Blocks
Admin Dashboard PageAdmin ShellAI Chat PageAPI Keys PageAutomation Builder PageCheckout PageCRM Contact PageCRM Pipeline PageError PagesInbox PageInvoice PageKPI DashboardListing Detail PageLogin PageMarketplace Home PageMarketplace Search PageMarketplace ShellMobile App HomeOnboarding FlowOrder History PagePost Listing WizardPricing PageProfile PageRegister PageSettings PageUsers Admin Page
Templates
About PageAcademy PageAccessibility PageAffiliate PageAgency PageAI Product PageAppointment Booking PageBlog PageBlog Post PageBug Bounty PageCareers PageChangelog PageCommunity PageCompare PageContact PageCustomer Story PageCustomers PageDemo Booking PageDevelopers PageDocs Home PageDownload PageEnterprise PageEvents PageExperts PageFAQ PageFeature PageGlossary PageHelp Article PageHelp Center PageHome Services PageImpact PageIntegration Detail PageIntegrations PageJob Detail PageLanding PageLaunch PageLegal PageManifesto PagePortfolio PagePress PageProduct Tour PagePromo PageReal Estate PageReferral PageResearch Report PageRestaurant PageRoadmap PageROI Calculator PageSecurity PageSolutions PageStartups PageStatus PageStorefront PageSwitch PageTemplate Gallery PageTravel PageWaitlist PageYear in Review Page
Hooks & Utilities
AI ChatChart kitCommerceDate utilsFile exportFile typesFiltersForm validationHapticsInvoicingMotion presetsNavigationNumber formatRelative timeRomanian locationsSpreadsheetText matchTonesUIProvideruseClipboarduseConsentuseControllableStateuseCooldownuseDebounceuseKeyboardInsetuseLocaleuseLongPressuseMediaControlsuseMediaQueryuseOverflowItemsusePersistedStateuseScrollEdgesuseScrollStateuseSelectionuseShortcutuseStickToBottom
GalleryTheme

Commerce

Quantity stepper

Commerce

Quantity stepper React component for Tailwind CSS v4

Product galleryReviews
Live props
State
defaultValue1
loading
disabled
Behavior
min
max
step
stockNot tracked
lowStockThreshold5
removable
addToCart
editable
holdToRepeat
haptics
Content
suffix
locale
aria-label
Appearance
size
variant
radius
tone
color
Code
import { QuantityStepper } from "@/components/ui/quantity-stepper"

<QuantityStepper />
  1. Home
  2. Components
  3. Commerce
  4. Quantity stepper
Pro

Quantity stepper React component for Tailwind CSS v4

v1.0.0·Added in 1.0.0

A React quantity stepper for carts and product pages, with stock hints, hold to repeat, rolling digits and an Add to cart morph.

Tap or click minus and plus to change the quantity; the new number rolls in from below when it grows and from above when it shrinks. Hold a button and it repeats after 400ms, speeding up, and stops at the limit; on a phone a finger that starts to scroll never changes the value. Pressing past a limit shakes the control (and buzzes on a phone), and at the maximum a line under it says why. With removable, minus turns into a trash button at the minimum; with addToCart the control starts as an Add to cart button that morphs into the stepper and collapses back from the first step. Keyboard: Tab reaches the value (one stop), Up and Down step, Page Up and Page Down move 10 steps, Home and End jump to the minimum and maximum, Delete removes the line at the minimum when removable. With editable, type a number and press Enter or leave the field (Escape drops what was typed); it is snapped to the step and clamped, and pressing plus or minus right after typing steps from the typed number. While the cart syncs a spinner replaces the digits, and a failed save rolls back with a message.

Quantity stepper is a Pro React component in the React commerce components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add quantity-stepper, together with Button, Spinner, useControllableState, useLocale, Haptics and 4 more and the npm packages motion and lucide-react.

Add to a project

Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup

npx wingo-ui@latest add quantity-stepper

Quantity stepper is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans

Usage

Import QuantityStepper from @/components/ui/quantity-stepper.

import { QuantityStepper } from "@/components/ui/quantity-stepper"

export function CartLine({ quantity, onQuantity, onRemove }: { quantity: number; onQuantity: (value: number) => Promise<void>; onRemove: () => void }) {
  return (
    <QuantityStepper
      value={quantity}
      onValueChange={onQuantity}
      removable
      onRemove={onRemove}
      stock={3}
      aria-label="Quantity, iPhone 14 case"
      labels={{
        remove: "Remove from bag",
        lowStock: (_, n) => `Hurry, only ${n} left`,
      }}
    />
  )
}

Props

  • defaultValuedefault 1
    number

    The starting quantity while uncontrolled. Without it, an addToCart stepper starts at 0 (the button).

  • value
    number

    The controlled quantity. A value outside min and max is shown clamped and never reported back.

  • onValueChange
    (value: number) => (void | Promise<unknown>)

    Called with every new quantity. Return a promise (a cart sync) to show the loading state until it settles; a rejection reverts the value and announces labels.error. Controlled: set the value when the promise resolves.

  • mindefault 1
    number

    The lowest quantity. With addToCart it is the first step after the button (at least step), and 0 collapses back to the button.

  • maxdefault 99
    number

    The highest quantity, capped by stock when stock is tracked. A max below min becomes min.

  • stepdefault 1
    number

    How much one press changes. Decimals work (0.5 kg) without float noise; typed values snap to it.

  • stockdefault 0
    number

    Units left. Caps max and shows labels.lowStock at or below lowStockThreshold. A quantity above it is never changed silently: the hint says so and plus stops. 0 means not tracked.

  • lowStockThresholddefault 5
    number

    The stock at or below which the low stock line shows.

  • removabledefault false
    boolean

    At the minimum, minus becomes a red trash button that calls onRemove (Delete on the keyboard does the same).

  • onRemove
    () => void

    Called by the trash button. The stepper keeps its value; remove the line (and offer an undo) in your cart.

  • addToCartdefault false
    boolean

    At 0 the control is an Add to cart button in the tone. Pressing it sets the first step and morphs into the stepper; minus at the first step collapses it back.

  • editabledefault false
    boolean

    The value is a text field for bulk quantities: type 120 and press Enter. Clamped and snapped on Enter and blur, numeric keyboard on phones.

  • holdToRepeatdefault true
    boolean

    Holding a button repeats after 400ms, every 100ms speeding up to every 30ms.

  • suffixdefault ""
    string

    A unit after the number, like "buc." or "kg". Also read by screen readers.

  • sizedefault "md"
    "sm" | "md" | "lg"

    Height 32 / 40 / 48px with square buttons. md grows to 44px on touch; sm keeps its look with a 44px hit area.

  • variantdefault "soft"
    "soft" | "outline" | "ghost"

    The field box look: a filled well, a hairline outline, or nothing until hovered.

  • radiusdefault "full"
    "none" | "sm" | "md" | "lg" | "full"

    Corner shape, from the button's per-size map; full is a pill.

  • tonedefault "neutral"
    "neutral" | "brand" | "success" | "warning" | "danger" | "info"

    The color of the Add to cart button. Brand orange is opt-in for the one hero action of a product page.

  • colordefault ""
    string

    Any CSS color for the Add to cart button; replaces the tone.

  • loadingdefault false
    boolean

    The cart is syncing: the buttons stop, a spinner takes the digits' place and the width holds still.

  • disableddefault false
    boolean

    Dims the control and blocks every input.

  • hapticsdefault true
    boolean

    On phones: a "selection" tick per step (every third while a hold accelerates) and a "warning" buzz at a limit. Never from a mouse.

  • localedefault ""
    string

    Intl locale for the digits ("1.5" in en-US, "1,5" in de-DE). Empty uses the UIProvider locale, then the page.

  • labels
    Partial<QuantityStepperLabels>

    Every string: group "Quantity", increment "Increase quantity", decrement "Decrease quantity", remove "Remove", add "Add to cart", lowStock (count, formatted) => "Only 3 left", overStock (count, formatted) => "Only 3 in stock", max (count, formatted) => "Max. 3 per order", maxReached "Maximum reached", error "Could not update the quantity". DEFAULT_QUANTITY_STEPPER_LABELS exports them.

  • classNames
    Partial<Record<"box" | "decrement" | "increment" | "remove" | "value" | "digits" | "suffix" | "add" | "hint", string>>

    Extra classes for the inner parts. className goes on the root, which stacks the box and the hint line.

  • aria-labeldefault ""
    string

    Names the group and the value, e.g. "Quantity, iPhone 14 case" in a cart with many lines. Empty uses labels.group.

Parts

  • DEFAULT_QUANTITY_STEPPER_LABELS

    constant

    The English strings: the group and button names, Add to cart, the stock hints, the per order maximum and the error. Spread it and override what you translate.

Examples

Product card

addToCart: Add to cart morphs into the stepper, in the brand tone.

Cart rows

removable: at 1 the minus becomes a trash button; the row leaves and Undo brings it back.

Low stock

stock={3}: Only 3 left in stock, then a shake and At most 3 per order.

Bulk order

editable with max={500}: type a quantity of boxes instead of pressing 120 times.

By weight

step={0.5} min={0.5} suffix="lb": decimals formatted for the locale.

Sizes and variants

sm, md and lg in soft, outline and ghost.

Syncing

onValueChange returns a promise: a spinner while it saves, a rollback and a message when it fails.

Dependencies

motionlucide-reactbuttonspinneruse-controllable-stateuse-localehapticsmotionnumber-formattonesui-config

Files

  • components/ui/quantity-stepper.tsx

FAQ

Is the React Quantity stepper component free?

The preview, props and docs are free; copying the source needs a Pro plan. See plans.

How do I install Quantity stepper?

Run npx wingo-ui@latest add quantity-stepper. It also adds the Wingo UI items it builds on (Button, Spinner, useControllableState, useLocale, Haptics and 4 more) and the npm packages motion and lucide-react. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/quantity-stepper.

How does Quantity stepper behave on phones?

Hold a button and it repeats after 400ms, speeding up, and stops at the limit; on a phone a finger that starts to scroll never changes the value. Pressing past a limit shakes the control (and buzzes on a phone), and at the maximum a line under it says why.

See also

Number InputButtonPriceHapticslib

Used in

Cart drawerCheckout PageStorefront Page

More React commerce components

Coupon inputCategory pickerFilter panelPrice rangeListing cardProduct gallery
All 16 React commerce components
PreviousProduct galleryNext Reviews
Your cart
iPhone 14 case, silicone, Midnight$29.90
1
$29.90012345678901234567890012345678901234567890.012345678901234567890012345678901234567890
value=1 · last none yetmin=1 max=99 step=1 lowStockThreshold=5holdToRepeat=true haptics=true tone=neutral color=none

Hold + to repeat; press past a limit to feel it stop