Skip to main content

NumberInput

A number input is an Input with role="spinbutton", a numeric keyboard on touch devices, stepper buttons and keyboard stepping. The value is a number | null (null is empty), never a string. It takes every Input prop (variant, state, invalid, leadingContent, fullWidth, …) and inherits an enclosing FormItem's label and state.

import NumberInput from '@zuilib/components/number-input'

Range and step

min / max clamp on commit (blur, Enter, stepping); step is what one press or arrow key moves. Page Up / Page Down move ten steps, Home / End jump to min / max. A stepper button disables at its bound.

Loading example

Formatting and precision

formatOptions is passed to Intl.NumberFormat for the display while the field is not focused: grouping, currency, units. On focus the plain number shows in the same locale (its decimal separator, no grouping). precision is the number of decimals the value is rounded to on commit; it defaults to the decimals of step.

Loading example

Sizes and controls

The three sizes come from Input. controls={false} drops the stepper (keyboard stepping still works); trailingContent sits between the value and the stepper.

Loading example

In a form item

Inside a FormItem the control takes the field's id, aria-labelledby, aria-describedby, aria-invalid and disabled state from context.

Loading example

Props

NumberInputProps extends InputProps (minus type, value, defaultValue, onChange, size) with:

PropTypeDefaultDescription
valuenumber | nullControlled value; null is empty
defaultValuenumber | nullnullUncontrolled initial value
onChange(value: number | null) => voidFires with every parseable edit (unclamped), and with the clamped, rounded value on blur / Enter / stepping
minnumberLower bound, applied on commit; Home jumps to it
maxnumberUpper bound, applied on commit; End jumps to it
stepnumber1Amount one button press or arrow key moves the value
precisionnumberdecimals of stepDecimal places the value is rounded to on commit
formatOptionsIntl.NumberFormatOptionsDisplay formatting while not focused (grouping, currency, units)
localestringruntime localeBCP 47 locale for formatting and parsing
controlsbooleantrueShow the increment / decrement buttons
holdToRepeatbooleantrueHolding a stepper button keeps stepping until the bound
incrementLabelstring'Increment'aria-label of the increment button
decrementLabelstring'Decrement'aria-label of the decrement button
trailingContentReactNodeAfter the value, before the stepper
size'sm' | 'md' | 'lg''md'
inputModeInputHTMLAttributes['inputMode']'decimal' when min >= 0, else 'text'The iOS decimal keypad has no minus key
fullWidthbooleanfalse
classNamestringMerged onto the root wrapper
inputClassNamestringMerged onto the native input

Slots

SlotElementNotes
[data-slot="number-input"]divRoot wrapper, with data-size
[data-slot="input"]divThe Input frame, with Headless UI data-focus / data-hover / data-disabled / data-invalid
[data-slot="number-input-control"]inputThe native input (role="spinbutton"); replaces input-control
[data-slot="input-trailing"]spanWraps trailingContent and the stepper
[data-slot="number-input-stepper"]divThe column of two buttons
[data-slot="number-input-increment"]buttontabIndex -1; disabled at max
[data-slot="number-input-increment-icon"]svg
[data-slot="number-input-decrement"]buttontabIndex -1; disabled at min
[data-slot="number-input-decrement-icon"]svg

Tokens

TokenUsed for
--control-height-sm/md/lgField height per size (from Input)
--input, --background, --foregroundFrame border and surface
--ring, --destructiveFocus ring, error state
--accent, --accent-foreground, --muted-foregroundStepper button hover and colour
--duration-fastColour transitions

Accessibility

  • The native input is role="spinbutton" with aria-valuemin, aria-valuemax, aria-valuenow and aria-valuetext (the formatted display).
  • Keyboard: ArrowUp / ArrowDown step, PageUp / PageDown step ten times, Home / End jump to min / max when set, Enter commits (clamp and round). Unparseable text reverts on blur.
  • The stepper buttons are tabIndex={-1} and labelled by incrementLabel / decrementLabel; pressing one keeps (or moves) focus on the field so the new value is announced. Enter / Space and assistive-technology activation step once; a held pointer repeats after 400 ms.
  • Give it a label: an enclosing FormItem with a Label, or aria-label.
  • inputmode="decimal" opens the numeric keypad on touch devices only when negative values are impossible.
  • Input: the frame, variants and states this builds on.
  • Slider: the other numeric control, for a bounded range.
  • FormItem: label, description and message wiring.