Skip to content
shadcn.io

shadcn.io is not affiliated with official shadcn/ui

Components

Free

Number Input for React Aria

Step buttons beside a field for a quantity, a price, or any other number. For React with React Aria, when a plain text field would accept junk.

Framework
UI library

A numeric field with buttons that step the value up and down. Typing still works.

Use it when the value has a minimum, a maximum, or a fixed step. A free-form amount with only a symbol beside it is an Input. In React, React Aria keeps the value in range and steps it with the buttons.

Preview width

Installation

Sign in to get your install command
Free with an account. It carries your token, so the CLI installs everything in one go.

Anatomy

Number Input anatomy: label, scrubber, group, decrement, input, increment
NumberInput
├── NumberInputInput
├── NumberInputGroup
├── NumberInputValueText
├── NumberInputIncrement
├── NumberInputDecrement
└── NumberInputScrubber
    └── NumberInputLabel

Usage

import {
  NumberInput, NumberInputDecrement, NumberInputGroup,
  NumberInputIncrement, NumberInputInput,
} from "@/components/ui/number-input";
<NumberInput className="w-full max-w-48" defaultValue="1">
  <NumberInputGroup>
    <NumberInputDecrement />
    <NumberInputInput />
    <NumberInputIncrement />
  </NumberInputGroup>
</NumberInput>

Accessibility

Arrow Up
Increments the value of the number input by a predefined step.
Arrow Down
Decrements the value of the number input by a predefined step.
Shift+Arrow Up
Increments the value of the number input by the largeStep amount.
Shift+Arrow Down
Decrements the value of the number input by the largeStep amount.
Alt+Arrow Up
Increments the value of the number input by the smallStep amount.
Alt+Arrow Down
Decrements the value of the number input by the smallStep amount.
Home
Sets the value of the number input to its minimum allowed value.
End
Sets the value of the number input to its maximum allowed value.
Enter
Submits the value entered in the number input.
Space
Activate the focused control.

Demos

Controlled

The number lives in your state, starting at 1, and the line under the field only settles once it reads 3. Hold value when another part of the screen depends on it, or when you want to refuse a change before it sticks. The callback hands you a string, so a cleared field stays empty instead of becoming zero. Leave it uncontrolled if nothing else needs to read the number.

Preview width

Disabled

disabled locks the field and both step buttons. Use it for a quantity the current plan or permission cannot change, while still showing the number that would have been edited. It is not a read-only caption: the control stays in the form, it just will not take input. Pair it with nearby text that says why it is off, or people will keep hitting the buttons.

Preview width

Field Only

The group holds the input and nothing else. Drop the increment and decrement buttons when the row is narrow, like a table cell or a toolbar, and people will type the number. You still get a numeric field rather than a text box that only looks like one. If the step is awkward to type, put the buttons back.

Preview width

Formatted

formatOptions takes Intl.NumberFormat options. Here the style is currency and the currency is USD, so 19 shows as dollars while the stored value stays a number. Use it for money or percents when the field should speak the format, not when you only want a symbol painted beside the box. A prefix you design yourself belongs in an input group instead.

Preview width

Invalid

invalid is the error paint, before or after submit. Turn it on when the number fails a rule the field itself does not know, such as a stock check, and put the reason in a description next to it. The prop does not block typing or stepping. It only marks the control, so the message has to say what is wrong.

Preview width

Mouse Wheel

allowMouseWheel lets a scroll over the focused field step the value. It stays off unless you set it, because a page scroll that crosses the input should not change a quantity. Turn it on in a tool panel where the pointer is already on the control and a wheel nudge is faster than the buttons. Leave it off in a long form.

Preview width

Range

min is 0 and max is 10, with 5 as the start. The buttons stop at the ends, and a typed value outside that span is pulled back in. Set both when a percentage, a rating, or a small count cannot leave a known band. A step still applies inside the band, and if you only need to block negatives, a minimum is enough.

Preview width

Scrub

NumberInputScrubber wraps the Quantity label, and dragging across it moves the value, the gesture design tools use when tiny plus and minus buttons are the slow path. Keep the buttons for people who click or use a keyboard. The scrubber is an extra handle, not a replacement for the field. Skip it where a drag fights page scrolling.

Preview width

Size Lg

size set to lg raises the field and the step buttons. Pick the large size when this number is the main control on the screen, or when a coarse pointer makes the default hit area feel mean. It is the same component, not a different widget. Match it to other large fields in the form so the row does not jump.

Preview width

Size Md

md is the default size, shown on its own so you can compare it with the small and large fields. Leave size unset in most forms. It lines up with the usual input height. Reach for another size only when the surrounding density actually changes, not to make one field look more important than its label.

Preview width

Size Sm

size set to sm tightens the field for a toolbar, a table, or a side panel with too many rows. The buttons shrink with it, so do not use the small size as the only control on a phone. If the number is rare and the chrome should get out of the way, this is the one. Keep a visible label, because the shorter box is easy to miss.

Preview width

Step

Two fields, one with step of 5 and one with step of 0.1. The first jumps in whole units, which fits inventory, and the second moves in tenths, which fits a measurement. The buttons move by that step, so pick one people can predict. A step of 7 on a price looks broken even when the math is right.

Preview width

With Field

The number sits in a Field with the label Quantity and a description that says 0 to 100. min and max match that sentence. Use Field when the number needs a label, a hint, and later an error, instead of a placeholder that disappears as soon as someone types. The description is the place for the range, so do not hide it in an accessible name only.

Preview width

With Input Group

The number sits in an Input Group with a euro sign before it and EUR after it, and there are no step buttons. The field has its own accessible name, because the symbols are not a label. Use this when the unit is chrome and formatOptions would force a locale currency format you do not want. The group is layout, and the number input holds the value.

Preview width

FAQ

How do I control the Number Input value in React?

Pass value and onValueChange on NumberInput. The callback gets { value } as a string, so an empty field stays empty instead of turning into 0; convert it with Number(value) when you save it.

See the Controlled demo above.

How do I set a minimum, maximum and step on the Number Input?

Set min, max and step on NumberInput. The increment and decrement buttons stop at the bounds and move by step, and a value typed outside the range is clamped back into it. The Range and Step demos show both.

How do I format the Number Input as currency or a percentage?

Pass formatOptions, which takes Intl.NumberFormat options: { style: "currency", currency: "USD" } shows dollars and { style: "percent" } a percentage. The Formatted demo above shows one.

Can the Number Input change with the mouse wheel?

Yes: set allowMouseWheel on NumberInput, and scrolling over the focused field steps the value. It's off by default so that scrolling the page past a field never changes a value by accident.

How do I use the React Aria Number Input in a form?

Give NumberInput a name, and its value submits with the form.

Wrap it in Field for a label, a description and an error message, as in the With Field demo. React Hook Form, TanStack Form and Formisch each have a number field demo in the forms guides.

How do I let people drag to change the Number Input value?

Add NumberInputScrubber, usually around the label: dragging across it scrubs the value up and down by step. The Scrub demo above puts one on the label.

Is the React Aria Number Input accessible?

Yes. ArrowUp and ArrowDown change the value by step, and the increment and decrement buttons carry their own labels.

Pair it with a visible label, as in the With Field demo.

How do I install the Number Input in React?

Run the command from the Installation section above: it adds the Number Input and the parts it uses to your project's ui folder. The shadcn CLI writes components/ui/number-input.tsx, a file you own and can edit.

API Reference

NumberInput

PropType
value?number | string

The value. Pair with onValueChange to control it.

Type
number | string
defaultValue?number | string

The value it starts with, when you don't control value.

Type
number | string
onValueChange?(details: { value: string }) → void

Called when the value changes.

Type
(details: { value: string }) → void
Parameters

details – { value: string }

Returns
void
allowMouseWheel?boolean

Whether to allow mouse wheel to change the value.

Default
false
disabled?boolean

Whether it's disabled.

formatOptions?Intl.NumberFormatOptions

The options to pass to the Intl.NumberFormat constructor.

id?string

The element's id.

invalid?boolean

Whether it's invalid.

Default
false
max?number

The highest value.

Default
Number.MAX_SAFE_INTEGER
min?number

The lowest value.

Default
Number.MIN_SAFE_INTEGER
name?string

The name it submits with its form.

readOnly?boolean

Whether the number input is readonly.

size?"sm" | "md" | "lg"

The size.

Type
"sm" | "md" | "lg"
Default
"md"
step?number

How much each step changes the value.

Default
1
className?string

Merged with the part's classes.

AttributeType
data-disabled?string

Present when disabled is true.

data-focus?string

Present when focused is true.

data-invalid?string

Present when invalid is true.

data-scrubbing?string

Present when scrubbing is true.

NumberInputGroup

data-slot="number-field-group"

PropType
children?ChildrenOrFunction<T>

The children of the component. A function may be provided to alter the children based on component state.

Type
ChildrenOrFunction<T>
isDisabled?boolean

Whether the group is disabled.

isInvalid?boolean

Whether the group is invalid.

isReadOnly?boolean

Whether the group is read only.

role?"group" | "region" | "presentation"

An accessibility role for the group. By default, this is set to group. Use region when the contents of the group is important enough to be included in the page table of contents. Use presentation if the group is visual only and does not represent a semantic grouping of controls.

Type
"group" | "region" | "presentation"
Default
"group"
slot?string | null

A slot name for the component. Slots allow the component to receive props from a parent component. An explicit null value indicates that the local props completely override all props received from a parent.

Type
string | null
style?StyleOrFunction<T>

The inline [style](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/style) for the element. A function may be provided to compute the style based on component state.

Type
StyleOrFunction<T>
render?DOMRenderFunction<E, T>

Overrides the default DOM element with a custom render function. This allows rendering existing components with built-in styles and behaviors such as router links, animation libraries, and pre-styled components. Requirements: - You must render the expected element type (e.g. if <button> is expected, you cannot render an <a>). - Only a single root DOM element can be rendered (no fragments). - You must pass through props and ref to the underlying DOM element, merging with your own prop as appropriate.

Type
DOMRenderFunction<E, T>
className?string

Merged with the part's classes.

AttributeType
data-disabled?string

Present when disabled is true.

data-focus?string

Present when focused is true.

data-invalid?string

Present when invalid is true.

data-scrubbing?string

Present when scrubbing is true.

NumberInputDecrement

data-slot="number-field-decrement"

PropType
children?ReactNode

The content to display in the button.

clickEffect?boolean

Apply a click effect to the button.

Default
true
download?LinkPrimitiveProps["download"]

With href, downloads the linked file instead of opening it.

href?string

When set, the button renders as a link.

isDisabled?boolean

Whether the button is disabled.

isLoading?boolean

Show a loading indicator.

Default
false
pill?boolean

Fully rounded corners.

Default
false
rel?string

With href, the link's relationship to the page (noreferrer, …).

size?"icon-lg" | "icon-md" | "icon-sm" | "icon-xl" | "icon-xs" | "lg" | "md" | "sm" | "xl" | "xs"

The size.

Type
"icon-lg" | "icon-md" | "icon-sm" | "icon-xl" | "icon-xs" | "lg" | "md" | "sm" | "xl" | "xs"
Default
"md"
target?string

With href, where the linked page opens (_blank, …).

variant?"default" | "destructive" | "ghost" | "link" | "outline" | "secondary"

Ark default: default.

Type
"default" | "destructive" | "ghost" | "link" | "outline" | "secondary"
Default
ghost
className?string

Merged with the part's classes.

AttributeType
data-disabled?string

Present when disabled is true.

data-focus?string

Present when focused is true.

data-invalid?string

Present when invalid is true.

data-scrubbing?string

Present when scrubbing is true.

NumberInputIncrement

data-slot="number-field-increment"

PropType
children?ReactNode

The content to display in the button.

clickEffect?boolean

Apply a click effect to the button.

Default
true
download?LinkPrimitiveProps["download"]

With href, downloads the linked file instead of opening it.

href?string

When set, the button renders as a link.

isDisabled?boolean

Whether the button is disabled.

isLoading?boolean

Show a loading indicator.

Default
false
pill?boolean

Fully rounded corners.

Default
false
rel?string

With href, the link's relationship to the page (noreferrer, …).

size?"icon-lg" | "icon-md" | "icon-sm" | "icon-xl" | "icon-xs" | "lg" | "md" | "sm" | "xl" | "xs"

The size.

Type
"icon-lg" | "icon-md" | "icon-sm" | "icon-xl" | "icon-xs" | "lg" | "md" | "sm" | "xl" | "xs"
Default
"md"
target?string

With href, where the linked page opens (_blank, …).

variant?"default" | "destructive" | "ghost" | "link" | "outline" | "secondary"

Ark default: default.

Type
"default" | "destructive" | "ghost" | "link" | "outline" | "secondary"
Default
ghost
className?string

Merged with the part's classes.

AttributeType
data-disabled?string

Present when disabled is true.

data-focus?string

Present when focused is true.

data-invalid?string

Present when invalid is true.

data-scrubbing?string

Present when scrubbing is true.

NumberInputInput

data-slot="number-field-input"

PropType
children?ReactNode

The children of the component.

enterKeyHint?"enter" | "done" | "go" | "next" | "previous" | "search" | "send"

An enumerated attribute that defines what action label or icon to preset for the enter key on virtual keyboards. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/enterkeyhint).

Type
"enter" | "done" | "go" | "next" | "previous" | "search" | "send"
style?object

The inline [style](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/style) for the element.

Type
{ prop?: string]: string | number }
className?string

Merged with the part's classes.

AttributeType
data-disabled?string

Present when disabled is true.

data-focus?string

Present when focused is true.

data-invalid?string

Present when invalid is true.

data-scrubbing?string

Present when scrubbing is true.

NumberInputScrubber

<span> data-slot="number-field-scrubber"

PropType
className?string

Merged with the part's classes.

With other components