A numeric input with increment and decrement steppers. Formats and parses numbers in the user's locale, supports currency, percent and unit formatting, min/max/step constraints, keyboard and scroll-wheel stepping, validation and form submission.
The value is always a number. The field formats it for display with formatOptions and the user's locale, parses what the user types, and on blur clamps it to minValue / maxValue and snaps it to step.
An empty field is NaN
When the user clears the input, onChange receives NaN, not 0 or
null. Check with Number.isNaN(value) before doing arithmetic, and use
isRequired if an empty value isn't allowed.
stepper="stacked" (default) puts compact chevrons on the right. split puts larger − and + buttons on either side, which suits touch and quantity pickers. none hides the buttons; the arrow keys and scroll wheel still work.
formatOptions takes any Intl.NumberFormat option: currency, percent, units, fraction digits, sign display, grouping. Users can type in their locale's format, including the currency symbol or percent sign. Percent values are fractions: 0.15 displays as 15%.
minValue and maxValue bound the value; the steppers disable at each end. step sets the increment, and typed values snap to the nearest step (counted from minValue) on blur. Fractional steps work with any format.
value and onChange let you derive UI from the number, like a live total. onChange fires when the value is committed (on blur, Enter or a step), not on every keystroke.
By default an out-of-range value is clamped on blur. Set commitBehavior="validate" to keep what the user typed and show a range error instead, so they notice the limit. With native validation, the form won't submit until the field is valid.
import { Form } from "react-aria-components";import { Button } from "@/components/ui/button";import { NumberField } from "@/components/ui/number-field";export default function NumberFieldValidation() { return ( <Form className="flex w-full max-w-56 flex-col gap-4" onSubmit={(e) => e.preventDefault()} > <NumberField label="Guests" name="guests" isRequired minValue={1} maxValue={8} commitBehavior="validate" description="Up to 8 guests per booking." /> <Button type="submit" className="self-start"> Book table </Button> </Form> );}
validate receives the parsed number (NaN when empty) and returns an error string or null. With validationBehavior="aria" on the Form, errors show live.
import { Form } from "react-aria-components";import { Button } from "@/components/ui/button";import { NumberField } from "@/components/ui/number-field";export default function NumberFieldCustomValidation() { return ( <Form className="flex w-full max-w-56 flex-col gap-4" validationBehavior="aria" onSubmit={(e) => e.preventDefault()} > <NumberField label="Batch size" name="batch" defaultValue={48} minValue={8} maxValue={512} step={8} description="A multiple of 8 between 8 and 512." validate={(value) => value > 256 ? "Batches over 256 need a GPU plan." : null } /> <Button type="submit" className="self-start"> Start training </Button> </Form> );}
The input is a text field with aria-roledescription="number field", so screen readers announce it as a number field along with its formatted value.
The input and steppers are wrapped in a role="group" labelled by the field label.
Stepper buttons are named "Increase Seats" / "Decrease Seats" from the label (override with incrementAriaLabel / decrementAriaLabel). They're skipped in the tab order because the arrow keys do the same job.
Values are formatted and parsed with the user's locale, including their decimal and grouping separators and numbering system.
inputMode is set automatically (numeric, decimal or text) so mobile keyboards include a minus sign or decimal point only when needed.
The scroll wheel steps the value only while the field has focus. Disable it with isWheelDisabled in scrollable layouts.
On the NumberField root. Children can react with group-data-*/field: variants.
Attribute
Present when
data-disabled
isDisabled is true
data-readonly
isReadOnly is true
data-required
isRequired is true
data-invalid
Validation failed, or isInvalid is true
On the FieldGroup: data-hovered, data-focus-within, data-focus-visible, data-invalid and data-disabled. On each stepper button: data-hovered, data-pressed, data-disabled (at a bound) and data-focus-visible.