NumberField
A number in the reader's locale, a price, a percent or a unit, in Input's box. It formats on blur, parses what is typed in the locale, clamps to min and max, and steps with the arrow keys; step buttons are optional. A form gets the plain number.
Import
import { NumberField } from '@nizivo/react/number-field';
// Once per app (Getting started), after
// @nizivo/tokens/tokens.css and @nizivo/react/css/base.css and @nizivo/react/css/input.css
import '@nizivo/react/css/number-field.css';Static or interactive
Client. It is React Aria's NumberField, which reads its Field through FieldScope, so a page with a NumberField also loads FieldScope. The server HTML already shows the value formatted in the locale, and a form submits it before hydration; typing, formatting and stepping work once the page has hydrated. How to choose.
In a client app (a Vite or React Router single-page app, or a 'use client' file) it is an ordinary React component: event handlers, refs and state work as usual. In a client app.
Parts
NumberField.Root is the box and holds the value. NumberField.Input is the text input.
NumberField.Decrement before it and NumberField.Increment after it add step buttons; leave them
out for a price or an amount, where the arrow keys are enough.
Formatting and parsing
formatOptions takes Intl.NumberFormat options: { style: 'currency', currency: 'EUR' },
{ style: 'percent' } (0.25 shows as 25 %), { style: 'unit', unit: 'kilogram' }, or fraction
digits. The locale is NizivoConfig's locale, en-US without one: in de-DE, "1.234,5" is
1234.5 and shows as "1.234,50 €". The value is formatted on the server too, so the first paint
already shows "€12.50".
While the reader types, the text is theirs; on blur or Enter it is parsed, clamped to min and
max, snapped to step, and formatted. onValueChange(value, details) reports that committed
number, or null when the field is cleared. React Aria reports no cause, so details.reason is
'unknown'.
Forms
With name (or a Field's), a hidden input carries the plain number, 12.5 and never "€12.50",
into FormData. Inside a Field, required is checked natively on the visible input, so the
Field's messages.valueMissing applies.
Step buttons
The buttons are out of the tab order, as the arrow keys step the input, and each is disabled at its
end of the range. Their names come from React Aria, translated in 36 locales and ending with the
field's label: "Decrease Price" and "Increase Price". labels={{ decrement, increment }} replaces
them for one field. A child replaces the internal minus or plus mark.
Engine-owned
The input's role description, "Number field" in English, comes from React Aria's catalogue (it is
left out on iOS, where VoiceOver would not announce required with it). labels does not set it,
and NizivoConfig's labels do not reach the step buttons: the Strings guide
lists the engine-owned strings.
Examples
Basic
A price in euros: typed as "12.5", shown as "€12.50" on blur; the form gets 12.5.
import type { JSX } from 'react';
import { Field } from '@nizivo/react/field';
import { NumberField } from '@nizivo/react/number-field';
export function NumberFieldExample(): JSX.Element {
return (
<Field name="price" label="Price" description="Before tax." required>
<NumberField.Root
defaultValue={12.5}
min={0}
formatOptions={{ style: 'currency', currency: 'EUR' }}
>
<NumberField.Input />
</NumberField.Root>
</Field>
);
}
Percent
A discount in percent, with step buttons: 5 % a step, 0 to 100 %.
import type { JSX } from 'react';
import { Field } from '@nizivo/react/field';
import { NumberField } from '@nizivo/react/number-field';
export function NumberFieldPercentExample(): JSX.Element {
return (
<Field name="discount" label="Discount">
<NumberField.Root
defaultValue={0.15}
min={0}
max={1}
step={0.05}
formatOptions={{ style: 'percent' }}
>
<NumberField.Decrement />
<NumberField.Input />
<NumberField.Increment />
</NumberField.Root>
</Field>
);
}
States
Units and sizes, an error from the server, read-only and disabled.
import type { JSX } from 'react';
import { Field } from '@nizivo/react/field';
import { NumberField } from '@nizivo/react/number-field';
export function NumberFieldStatesExample(): JSX.Element {
return (
<div style={{ display: 'grid', gap: 16, maxInlineSize: 320 }}>
<Field label="Weight">
<NumberField.Root
defaultValue={1.2}
formatOptions={{ style: 'unit', unit: 'kilogram', maximumFractionDigits: 2 }}
size="sm"
>
<NumberField.Input />
</NumberField.Root>
</Field>
<Field label="Stock" error="Stock cannot be below the 4 already ordered.">
<NumberField.Root defaultValue={2} size="lg">
<NumberField.Decrement />
<NumberField.Input />
<NumberField.Increment />
</NumberField.Root>
</Field>
<Field label="Shipping" readOnly>
<NumberField.Root defaultValue={4.9} formatOptions={{ style: 'currency', currency: 'USD' }}>
<NumberField.Input />
</NumberField.Root>
</Field>
<Field label="Tax rate" disabled>
<NumberField.Root defaultValue={0.2} formatOptions={{ style: 'percent' }}>
<NumberField.Input />
</NumberField.Root>
</Field>
</div>
);
}
Parts
Use the parts through the namespace (NumberField.Root) or by their flat names (NumberFieldRoot); both work from Server Components.
NumberField.Root
Client component Renders data-slot="number-field" with the class nz-number-field. Props: NumberFieldRootProps. Source: src/number-field/number-field.client.tsx.
| Name | Type | Default | Description |
|---|---|---|---|
aria-describedby | string | – | |
aria-label | string | – | |
aria-labelledby | string | – | Inside a Field, the Field label's id, so the step buttons' names include it. |
childrenrequired | ReactNode | – |
|
className | string | – | |
defaultValue | number | null | – | |
disabled | boolean | – | |
formatOptions | Intl.NumberFormatOptions | – | How the value is shown and parsed, as |
id | string | – | The input's id. Inside a Field, the Field's, which its label points at. |
invalid | boolean | – | An error message is presented (owned by Field, never by the engine). Inside a Field, the Field sets it. |
labels | Partial<NumberFieldLabels> | – | Overrides the step buttons' names, for example |
max | number | – | The highest value: a typed higher number is clamped to it on commit. |
min | number | – | The lowest value: a typed lower number is clamped to it on commit. |
name | string | – | With |
onValueChange | (value: number | null, details: ChangeDetails<NumberFieldValueReason>) => void | – | Called when the value is committed: on blur, Enter, a step button or an arrow key, after parsing and clamping to |
readOnly | boolean | – | |
required | boolean | – | |
size | ControlSize"sm""md""lg" | 'md' | 32 / 40 / 48 px, as Input. |
step | number | 1 | The step of the arrow keys and step buttons; a committed value snaps to it. |
value | number | null | – | Controlled value; |
NumberField.Input
Client component Renders data-slot="number-field-input" with the class nz-number-field-input. Props: NumberFieldInputProps. Source: src/number-field/number-field.client.tsx.
| Name | Type | Default | Description |
|---|---|---|---|
render | RenderProp<NumberFieldInputState> | – | Must render an |
- Also takes every attribute of
<input>exceptchildren,defaultValue,disabled,id,max,min,name,readOnly,required,size,step,type,value. (Omit<ComponentPropsWithRef<"input">, "children" | "value" | "defaultValue" | "type" | "size" | "min" | "max" | "step" | "id" | "name" | "required" | "disabled" | "readOnly">)
render takes an element, or, in client code, a function of the props and this state (NumberFieldInputState):
| Name | Type | Default | Description |
|---|---|---|---|
disabledrequired | boolean | – | |
focusVisiblerequired | boolean | – |
NumberField.Decrement
Client component Renders data-slot="number-field-decrement" with the class nz-number-field-decrement. Props: NumberFieldButtonProps. Source: src/number-field/number-field.client.tsx.
| Name | Type | Default | Description |
|---|---|---|---|
children | ReactNode | – | The button's mark; defaults to the internal |
render | RenderProp<NumberFieldButtonState> | – | Must render a |
- Also takes every attribute of
<button>exceptchildren,disabled,type. (Omit<ComponentPropsWithRef<"button">, "children" | "disabled" | "type">)
render takes an element, or, in client code, a function of the props and this state (NumberFieldButtonState):
| Name | Type | Default | Description |
|---|---|---|---|
disabledrequired | boolean | – | |
pressedrequired | boolean | – |
NumberField.Increment
Client component Renders data-slot="number-field-increment" with the class nz-number-field-increment. Props: NumberFieldButtonProps. Source: src/number-field/number-field.client.tsx.
| Name | Type | Default | Description |
|---|---|---|---|
children | ReactNode | – | The button's mark; defaults to the internal |
render | RenderProp<NumberFieldButtonState> | – | Must render a |
- Also takes every attribute of
<button>exceptchildren,disabled,type. (Omit<ComponentPropsWithRef<"button">, "children" | "disabled" | "type">)
render takes an element, or, in client code, a function of the props and this state (NumberFieldButtonState):
| Name | Type | Default | Description |
|---|---|---|---|
disabledrequired | boolean | – | |
pressedrequired | boolean | – |
Other exports
NumberFieldButtonPropstypeimport type { NumberFieldButtonProps } from '@nizivo/react/number-field'The props of
NumberField.Decrement, listed above.NumberFieldButtonStatetypeimport type { NumberFieldButtonState } from '@nizivo/react/number-field'The render state of
NumberField.Decrement, listed above.NumberFieldInputPropstypeimport type { NumberFieldInputProps } from '@nizivo/react/number-field'The props of
NumberField.Input, listed above.NumberFieldInputStatetypeimport type { NumberFieldInputState } from '@nizivo/react/number-field'The render state of
NumberField.Input, listed above.NumberFieldLabelstypeimport type { NumberFieldLabels } from '@nizivo/react/number-field'The step buttons' names (§9.3 rule 7). Unset, React Aria renders its own localized names, which include the field's label ("Increase Price"; 36 locales), so they are not in the
enUScatalogue andNizivoConfigdoes not set them.Name Type Default Description decrementrequiredstring– The Decrement button's name.
incrementrequiredstring– The Increment button's name.
NumberFieldRootPropstypeimport type { NumberFieldRootProps } from '@nizivo/react/number-field'The props of
NumberField.Root, listed above.NumberFieldRootStatetypeimport type { NumberFieldRootState } from '@nizivo/react/number-field'Name Type Default Description disabledrequiredboolean– invalidrequiredboolean– readOnlyrequiredboolean– requiredrequiredboolean– NumberFieldValueReasontypeimport type { NumberFieldValueReason } from '@nizivo/react/number-field'React Aria reports no cause for a change.
type NumberFieldValueReason = "unknown"
Data attributes and slots
Style parts by class (.nz-<slot>) and state by data attribute. Attribute meanings come from contracts/vocabulary.json.
| Slot | Rendered by | Data attributes |
|---|---|---|
number-field.nz-number-field | NumberField.Rootroot (no render); interactive, focus: within |
|
number-field-decrement.nz-number-field-decrement | NumberField.Decrementroot (takes render) | none |
number-field-increment.nz-number-field-increment | NumberField.Incrementroot (takes render) | none |
number-field-input.nz-number-field-input | NumberField.Inputroot (takes render) | none |
CSS hooks
Set a hook on the component's class or any ancestor to restyle it without specificity games; leave it unset for the default shown.
| Hook | Default | Read as |
|---|---|---|
--nz-number-field-button-fg | --nz-icon-primary |
|
Layers: nz.components. Classes: .nz-input, .nz-number-field, .nz-number-field-button, .nz-number-field-icon, .nz-number-field-input.
Tokens this stylesheet reads (10)
--nz-bg-hover, --nz-bg-pressed, --nz-duration-fast, --nz-easing-standard, --nz-icon-primary, --nz-size-control-lg, --nz-size-control-md, --nz-size-control-sm, --nz-size-icon-sm, --nz-text-disabled
Accessibility
These parts are interactive. Each gets the focus outline and touch-action rule from @nizivo/react/css/base.css (the nz.base layer), so the floor survives a restyle:
number-field: draws the outline when the field inside it has focus.
Behaviour comes from the react-aria engine through Nizivo's adapter: focus management, ARIA wiring and keyboard handling are the engine's.
Keyboard
| Keys | Action |
|---|---|
| Tab | Moves focus to the input. The step buttons are skipped. |
| Arrow Up, Arrow Down | Adds or takes away one step, within min and max. |
| Page Up, Page Down | As Arrow Up and Arrow Down, one step. |
| Home, End | Goes to min or max, when they are set. |
| Enter | Commits the typed value (parsed, clamped and formatted), as leaving the field does. |
Source: React Aria Components 1.22.1 NumberField (_engines/react-aria/number-field.client.tsx). The browser lane (test/browser/number-field.spec.tsx) checks typing and the commit, de-DE parsing, the arrow keys, the step buttons, Tab skipping them, FormData, controlled use and a Field's required message.
Figma
Node ids from design/component-map.json, which nz gen writes from component.json. The links open the Nizivo Figma library, which may ask you to sign in.
- Canonical: none
- Ghost masters: none
NavItem
One sidebar destination, as a native link with an icon, a label and an optional count. The current one is aria-current="page"; a router's link goes in through render. In a Sidebar's icon rail it folds to its icon, with no prop; SidebarItem adds the rail's Tooltip.
NumberValue
A number formatted once with Intl.NumberFormat, in a <data> element whose value attribute keeps the raw number. Tabular figures, so a column of values lines up. Every dashboard part renders its numbers with it.