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

Inputs

Number Input

Inputs

Number Input React component for Tailwind CSS v4

Multi SelectOTP Input
Live props
Field
label
description
error
required
invalid
disabled
readOnly
Content
placeholder
prefix
suffix
incrementLabel
decrementLabel
name
Appearance
size
variant
radius
steppers
Behavior
min
max
step
largeStep
decimals
clampBehavior
holdToRepeat
wheel
selectOnFocus
formatOnBlur
locale
currency
Code
import { NumberInput } from "@/components/ui/number-input"

<NumberInput />
  1. Home
  2. Components
  3. Inputs
  4. Number Input
Pro

Number Input React component for Tailwind CSS v4

v1.0.0·Added in 1.0.0

A React number input to type or step values: steppers with hold to repeat, min, max and step, and Intl formatting for money and units.

Type a number the way you write it: 1.234,5 in Romanian or 1,234.5 in English both work, a comma or a dot is taken as the decimal, and letters are simply refused (a refused key typed over a selection keeps it selected). At rest the field shows the formatted value ($125,000, 2.5 kg); focus it and it turns into the plain number, ready to edit, and Tab selects it all like any field. Press + or - to step, and hold either one: after a short pause it repeats, faster the longer you hold, and stops at min or max, where that button dims. ArrowUp and ArrowDown step, Shift or PageUp and PageDown take the large step, Home and End jump to min and max, Enter commits and still submits the form, Escape puts back the last committed value. With the field focused the mouse wheel steps too, without scrolling the page. A number typed past a bound shows the red ring until you leave the field, where it snaps back inside (or cannot be typed at all with strict clamping). On a phone the keypad is numeric (decimal when the step allows decimals), the text is 16px so iOS never zooms, the md box grows to 44px, each + and - has a 44px hit area (the stacked chevrons turn into a row of two 44px buttons), and a finger that starts scrolling on a stepper undoes its step. iOS keypads have no minus key, so negative values come from the steppers.

Number Input is a Pro React component in the React input components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add number-input, together with Field, Number format, useLocale, Motion presets, useControllableState and 1 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 number-input

Number Input 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 NumberInput from @/components/ui/number-input.

import { useState } from "react"
import { NumberInput } from "@/components/ui/number-input"

export function PropertyDetails() {
  const [area, setArea] = useState<number | null>(85)
  const [rooms, setRooms] = useState<number | null>(3)

  return (
    <form className="flex flex-col gap-4">
      <NumberInput
        name="area"
        label="Floor area"
        suffix="m²"
        min={10}
        max={1000}
        value={area}
        onValueChange={setArea}
        locale="en-US"
        required
      />
      <NumberInput name="rooms" label="Rooms" steppers="split" min={1} max={10} value={rooms} onValueChange={setRooms} />
      <NumberInput
        name="price"
        label="Price"
        currency="USD"
        locale="en-US"
        step={1000}
        decimals={0}
        defaultValue={485000}
        onValueCommit={(price) => console.log("Price saved:", price)}
      />
    </form>
  )
}

Props

  • labeldefault ""
    ReactNode

    Renders the field anatomy around the box and links it to the input with htmlFor. Leave it empty for a bare field.

  • descriptiondefault ""
    ReactNode

    Helper text under the box, linked with aria-describedby. The error takes its place while it shows.

  • errordefault ""
    ReactNode

    Replaces the description with the animated error and marks the field invalid (red ring, aria-invalid).

  • requireddefault false
    boolean

    The mark after the label, and required on the input, so native form validation stops an empty submit.

  • invaliddefault false
    boolean

    The invalid look and aria-invalid without a message. A value outside min and max (typed or controlled) sets it on its own until it is back in range.

  • disableddefault false
    boolean

    Dims the field to 50%, blocks typing and the steppers, and leaves the hidden form input out of the submit.

  • readOnlydefault false
    boolean

    The formatted value stays visible and selectable; the steppers, keys and the wheel stop and the steppers hide.

  • placeholderdefault ""
    string

    Hint text while the field is empty.

  • prefixdefault ""
    ReactNode

    Text or an element before the number, like €. A click on it lands in the input.

  • suffixdefault ""
    ReactNode

    Text or an element after the number: m², buc., %, kg. With steppers split it hugs the value in the middle.

  • incrementLabeldefault "Increase"
    string

    The + button's accessible name and hover title. The demo passes Increase.

  • decrementLabeldefault "Decrease"
    string

    The - button's accessible name and hover title. The demo passes Decrease.

  • namedefault ""
    string

    Renders a hidden input with this name and the value as "1234.5" (dot decimal, empty when null), so FormData and plain form posts get a number, not the formatted text. The visible input has no name.

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

    32 / 40 / 48px, like a button. md grows to 44px on touch screens and the text is 16px on phones so iOS never zooms.

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

    The box: a soft fill, an outline, a bottom border that grows a bar on focus, or ghost for tables and toolbars.

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

    Corner shape, scaled per size like the button (md is 8 / 10 / 12px). Ignored by underline.

  • steppersdefault "end"
    "end" | "split" | "stacked" | "none"

    end: - and + at the end. split: - at the start and + at the end with the value centered, the best layout for quantities on a phone. stacked: small chevrons in a column at the end, the desktop classic; on touch screens the column turns into a row of two 44px chevron buttons. none: keys and the wheel only.

  • mindefault -9007199254740991
    number

    The lowest value. Steppers and keys stop here (the - button dims), Home jumps here, typed values clamp to it on blur, and a value below it is flagged invalid. Unbounded by default (the safe integer range). min above max is swapped with a dev warning.

  • maxdefault 9007199254740991
    number

    The highest value, the same way: + dims at max, End jumps here, typed values clamp to it on blur.

  • stepdefault 1
    number

    What + / - and the arrows add, counted from min, so an off-grid value first lands on the next multiple. It also sets the precision (0.1 keeps one decimal) unless decimals is set, and the keypad (numeric for whole steps, decimal otherwise).

  • largeStepdefault 10
    number

    What Shift + arrows, PageUp and PageDown (and Shift + wheel) add.

  • decimalsdefault -1
    number

    Fraction digits kept: typing more is refused and values round to it. The default -1 takes them from step and format (2 for a USD price, 1 for step 0.1, 0 for step 1).

  • clampBehaviordefault "blur"
    "blur" | "strict" | "none"

    blur: out-of-range typing is allowed (the red ring shows) and clamped when the field loses focus. strict: a keystroke that can only move further out of range is refused. none: never clamped, validate it yourself.

  • holdToRepeatdefault true
    boolean

    Holding a stepper repeats after 400ms, speeding up from every 100ms to every 30ms, and stops at min or max.

  • wheeldefault true
    boolean

    The mouse wheel or trackpad steps while the field is focused and hovered (and the page does not scroll). Touch screens have no wheel.

  • selectOnFocusdefault false
    boolean

    Selects the whole number when the field takes focus, clicks included. Keyboard focus selects it either way, like a native field.

  • formatOnBlurdefault true
    boolean

    Shows the formatted value at rest ("$1,234.50") and the plain number while editing ("1234.5"). Off, the plain number always shows.

  • localedefault ""
    string

    Formats and parses with this locale: separators, the currency symbol and its position. Empty uses the nearest LocaleProvider, then the page language. The demo passes "en-US".

  • currencydefault ""
    string

    An ISO code like "USD", "EUR" or "RON": shorthand for a currency format with the narrow symbol ($1,234.50, 1.234,50 €).

  • value
    number | null

    The controlled value; null is an empty field. A value outside min and max is shown as given and flagged invalid, never clamped silently.

  • defaultValuedefault "null"
    number | null

    The starting value while uncontrolled; null is an empty field. A form reset returns to it.

  • onValueChange
    (value: number | null) => void

    Every change: live while typing (out of range until blur with clampBehavior blur), every stepper tick and key, the wheel. null when the field is empty or holds only a minus.

  • onValueCommit
    (value: number | null) => void

    Once per edit, for expensive updates: on blur, on Enter, when a held stepper is released and when a stepping key goes up. Only when the value changed.

  • onChange
    (event: ChangeEvent<HTMLInputElement>) => void

    The native input event, for keystrokes the field accepts.

  • format
    Intl.NumberFormatOptions

    How the value reads at rest: { style: "unit", unit: "kilogram" } for "2,5 kg", { style: "percent" } for fractions (0.15 shows 15% and is typed as 15), { useGrouping: false } for years. Invalid options fall back to plain numbers.

  • leftIcon
    ReactNode

    An icon at the start of the box (inside the centered group with steppers split). Unsized svgs follow the size.

  • incrementIcon
    ReactNode

    Replaces the + (or the up chevron when stacked).

  • decrementIcon
    ReactNode

    Replaces the - (or the down chevron when stacked).

  • fieldProps
    Partial<FieldProps>

    Anything else for the Field wrapper: optional and optionalLabel, labelAction, orientation, labelWidth, hideLabel.

  • id
    string

    The input's id. Defaults to useId(); inside a <Field> the Field's id wins.

  • ref
    RefObject<HTMLInputElement | null> or (node) => void

    The native input (role spinbutton, with aria-valuenow, min, max and valuetext). Every other native prop lands there too: inputMode (decimal or numeric by precision), enterKeyHint (done), autoComplete (off), onBlur, onKeyDown, aria-* and data-*.

  • classNames
    Partial<Record<"field" | "label" | "description" | "error" | "counter" | "box" | "input" | "icon" | "prefix" | "suffix" | "steppers" | "increment" | "decrement", string>>

    Extra classes for the inner parts. box targets the field box, input the native element, steppers the stepper group or column.

  • className
    string

    Classes for the outermost element: the Field root when there is a label, description or error, else the box.

Examples

Stepper layouts

end, split for quantities, stacked chevrons for dense desktop forms, or none (keys and wheel only).

Currency

currency="RON" in ro-RO reads 1.234,50 lei at rest and 1234,5 while editing; the same value in en-US USD.

Decimals and units

step 0.1 keeps one decimal and a kilogram unit format; a percentage from 0 to 100 in steps of 5.

Hold to repeat

Hold a stepper: it repeats after 400ms and speeds up. Every key a spinbutton should have works too.

Clamping

blur lets you type past a bound (the error shows) and clamps on blur; strict refuses the keystroke.

Cart row

Small split steppers with a pill radius, max from the stock, totals that roll.

Sizes and variants

32, 40 and 48px, and the four field boxes, so it lines up with every other input.

States

Read-only hides the steppers, disabled dims, an out-of-range value from outside is flagged instead of clamped.

Dependencies

motionlucide-reactfieldnumber-formatuse-localemotionuse-controllable-stateui-config

Files

  • components/ui/number-input.tsx

FAQ

Is the React Number Input component free?

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

How do I install Number Input?

Run npx wingo-ui@latest add number-input. It also adds the Wingo UI items it builds on (Field, Number format, useLocale, Motion presets, useControllableState and 1 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/number-input.

How does Number Input behave on phones?

On a phone the keypad is numeric (decimal when the step allows decimals), the text is 16px so iOS never zooms, the md box grows to 44px, each + and - has a 44px hit area (the stacked chevrons turn into a row of two 44px buttons), and a finger that starts scrolling on a stepper undoes its step. iOS keypads have no minus key.

See also

FieldInputSliderNumber formatlibuseLocalehook

Used in

Data TableFilter panelCron PickerFilter BuilderInline EditInvoice line itemsEditable GridRule BuilderPost Listing WizardPromo PageRestaurant PageROI Calculator Page

and 1 more.

More React input components

Password InputPhone InputRadio GroupRatingSearch InputSelect
All 40 React input components

Articles using this component

  • Shadcn Number Input: Steppers, Min, Max and Decimals

    Component guides · Nov 6, 2026 · 8 min read

  • React Currency Input: Intl Formatting Without Caret Jumps

    Component guides · Oct 16, 2026 · 10 min read

PreviousMulti SelectNext OTP Input

Property details

3-room apartment, Austin

Step 2 of 4
m²
3
Price per m²$5,706012345678901234567890,012345678901234567890012345678901234567890012345678901234567890
↑↓Up arrow Down arrow±1ShiftShift±10HomeEndHome End10 / 1,000ScrollScroll±1Hold + or − to repeat

onValueChange 85 · onValueCommit 85
clamp blur · formatOnBlur