Skip to content
Nizivo UI

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.

Client componentEngine: react-aria

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.

Before tax.
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.

Stock cannot be below the 4 already ordered.
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.

NameTypeDefaultDescription
aria-describedbystring–
aria-labelstring–
aria-labelledbystring–

Inside a Field, the Field label's id, so the step buttons' names include it.

childrenrequiredReactNode–

NumberField.Input, with NumberField.Decrement before it and NumberField.Increment after it to show step buttons.

classNamestring–
defaultValuenumber | null–
disabledboolean–
formatOptionsIntl.NumberFormatOptions–

How the value is shown and parsed, as Intl.NumberFormat options: a currency ({ style: 'currency', currency: 'EUR' }), a percent ({ style: 'percent' }, where 0.25 shows as 25 %), a unit or fraction digits. The locale is NizivoConfig's (en-US by default).

idstring–

The input's id. Inside a Field, the Field's, which its label points at.

invalidboolean–

An error message is presented (owned by Field, never by the engine). Inside a Field, the Field sets it.

labelsPartial<NumberFieldLabels>–

Overrides the step buttons' names, for example labels={{ increment: 'Preis erhöhen' }}.

maxnumber–

The highest value: a typed higher number is clamped to it on commit.

minnumber–

The lowest value: a typed lower number is clamped to it on commit.

namestring–

With name, a hidden input carries the plain number (never the formatted text) into FormData. Inside a Field, the Field's name, required, disabled, readOnly and presented error apply unless set here (an explicit false wins).

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 min and max. null when the field is cleared.

readOnlyboolean–
requiredboolean–
sizeControlSize
"sm""md""lg"
'md'

32 / 40 / 48 px, as Input.

stepnumber1

The step of the arrow keys and step buttons; a committed value snaps to it.

valuenumber | null–

Controlled value; null is an empty field.

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.

NameTypeDefaultDescription
renderRenderProp<NumberFieldInputState>–

Must render an <input>.

  • Also takes every attribute of <input> except children, 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):

NameTypeDefaultDescription
disabledrequiredboolean–
focusVisiblerequiredboolean–

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.

NameTypeDefaultDescription
childrenReactNode–

The button's mark; defaults to the internal minus or plus glyph. Decorative.

renderRenderProp<NumberFieldButtonState>–

Must render a <button>.

  • Also takes every attribute of <button> except children, 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):

NameTypeDefaultDescription
disabledrequiredboolean–
pressedrequiredboolean–

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.

NameTypeDefaultDescription
childrenReactNode–

The button's mark; defaults to the internal minus or plus glyph. Decorative.

renderRenderProp<NumberFieldButtonState>–

Must render a <button>.

  • Also takes every attribute of <button> except children, 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):

NameTypeDefaultDescription
disabledrequiredboolean–
pressedrequiredboolean–

Other exports

NumberFieldButtonProps type

import type { NumberFieldButtonProps } from '@nizivo/react/number-field'

The props of NumberField.Decrement, listed above.

NumberFieldButtonState type

import type { NumberFieldButtonState } from '@nizivo/react/number-field'

The render state of NumberField.Decrement, listed above.

NumberFieldInputProps type

import type { NumberFieldInputProps } from '@nizivo/react/number-field'

The props of NumberField.Input, listed above.

NumberFieldInputState type

import type { NumberFieldInputState } from '@nizivo/react/number-field'

The render state of NumberField.Input, listed above.

NumberFieldLabels type

import 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 enUS catalogue and NizivoConfig does not set them.

NameTypeDefaultDescription
decrementrequiredstring–

The Decrement button's name.

incrementrequiredstring–

The Increment button's name.

NumberFieldRootProps type

import type { NumberFieldRootProps } from '@nizivo/react/number-field'

The props of NumberField.Root, listed above.

NumberFieldRootState type

import type { NumberFieldRootState } from '@nizivo/react/number-field'

NameTypeDefaultDescription
disabledrequiredboolean–
invalidrequiredboolean–
readOnlyrequiredboolean–
requiredrequiredboolean–
NumberFieldValueReason type

import 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.

SlotRendered byData attributes
number-field
.nz-number-field
NumberField.Root
root (no render); interactive, focus: within
  • data-disabled (present or absent) ← disabled
    Mirrors the disabled attribute
  • data-invalid (present or absent) ← invalid
    An error message is currently presented (not "fails constraints"); always the Field's own state, never the engine's
  • data-readonly (present or absent)
    Mirrors the readonly attribute
  • data-required (present or absent) ← required
    Mirrors the required attribute
  • data-size: "sm""md""lg" ← size
    The size prop; values in each component manifest
number-field-decrement
.nz-number-field-decrement
NumberField.Decrement
root (takes render)
none
number-field-increment
.nz-number-field-increment
NumberField.Increment
root (takes render)
none
number-field-input
.nz-number-field-input
NumberField.Input
root (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.

HookDefaultRead as
--nz-number-field-button-fg--nz-icon-primary
  • color on .nz-number-field-button

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

KeysAction
TabMoves focus to the input. The step buttons are skipped.
Arrow Up, Arrow DownAdds or takes away one step, within min and max.
Page Up, Page DownAs Arrow Up and Arrow Down, one step.
Home, EndGoes to min or max, when they are set.
EnterCommits 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

On this page