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.
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.
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.
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.
Props
NumberInputProps extends InputProps (minus type,
value, defaultValue, onChange, size) with:
| Prop | Type | Default | Description |
|---|---|---|---|
value | number | null | — | Controlled value; null is empty |
defaultValue | number | null | null | Uncontrolled initial value |
onChange | (value: number | null) => void | — | Fires with every parseable edit (unclamped), and with the clamped, rounded value on blur / Enter / stepping |
min | number | — | Lower bound, applied on commit; Home jumps to it |
max | number | — | Upper bound, applied on commit; End jumps to it |
step | number | 1 | Amount one button press or arrow key moves the value |
precision | number | decimals of step | Decimal places the value is rounded to on commit |
formatOptions | Intl.NumberFormatOptions | — | Display formatting while not focused (grouping, currency, units) |
locale | string | runtime locale | BCP 47 locale for formatting and parsing |
controls | boolean | true | Show the increment / decrement buttons |
holdToRepeat | boolean | true | Holding a stepper button keeps stepping until the bound |
incrementLabel | string | 'Increment' | aria-label of the increment button |
decrementLabel | string | 'Decrement' | aria-label of the decrement button |
trailingContent | ReactNode | — | After the value, before the stepper |
size | 'sm' | 'md' | 'lg' | 'md' | |
inputMode | InputHTMLAttributes['inputMode'] | 'decimal' when min >= 0, else 'text' | The iOS decimal keypad has no minus key |
fullWidth | boolean | false | |
className | string | — | Merged onto the root wrapper |
inputClassName | string | — | Merged onto the native input |
Slots
| Slot | Element | Notes |
|---|---|---|
[data-slot="number-input"] | div | Root wrapper, with data-size |
[data-slot="input"] | div | The Input frame, with Headless UI data-focus / data-hover / data-disabled / data-invalid |
[data-slot="number-input-control"] | input | The native input (role="spinbutton"); replaces input-control |
[data-slot="input-trailing"] | span | Wraps trailingContent and the stepper |
[data-slot="number-input-stepper"] | div | The column of two buttons |
[data-slot="number-input-increment"] | button | tabIndex -1; disabled at max |
[data-slot="number-input-increment-icon"] | svg | — |
[data-slot="number-input-decrement"] | button | tabIndex -1; disabled at min |
[data-slot="number-input-decrement-icon"] | svg | — |
Tokens
| Token | Used for |
|---|---|
--control-height-sm/md/lg | Field height per size (from Input) |
--input, --background, --foreground | Frame border and surface |
--ring, --destructive | Focus ring, error state |
--accent, --accent-foreground, --muted-foreground | Stepper button hover and colour |
--duration-fast | Colour transitions |
Accessibility
- The native input is
role="spinbutton"witharia-valuemin,aria-valuemax,aria-valuenowandaria-valuetext(the formatted display). - Keyboard: ArrowUp / ArrowDown step, PageUp / PageDown step ten times, Home / End jump to
min/maxwhen set, Enter commits (clamp and round). Unparseable text reverts on blur. - The stepper buttons are
tabIndex={-1}and labelled byincrementLabel/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
FormItemwith aLabel, oraria-label. inputmode="decimal"opens the numeric keypad on touch devices only when negative values are impossible.