NumberInput
Numeric input with increment / decrement buttons, min/max clamping, configurable step + precision, and full keyboard support. Controlled and uncontrolled. Use null to represent an empty input.
Features
Example
Value: 3
Anatomy
import { NumberInput } from '@wire-ui/react'
<NumberInput.Root defaultValue={1} min={0} max={10}>
<NumberInput.Decrement>−</NumberInput.Decrement>
<NumberInput.Field aria-label="Quantity" />
<NumberInput.Increment>+</NumberInput.Increment>
</NumberInput.Root>Styling
NumberInput Root exposes data-disabled and data-readonly. Increment / Decrement buttons additionally expose data-hover, data-focus-visible, and data-active from the underlying interactive state tracker. The buttons are auto-disabled when the value reaches the relevant boundary.
<NumberInput.Root className="
data-[disabled]:opacity-50
data-[readonly]:bg-[#f5f5f5]
">
<NumberInput.Decrement className="
data-[hover]:bg-[#f5f5f5]
disabled:cursor-not-allowed disabled:opacity-40
">−</NumberInput.Decrement>
<NumberInput.Field />
<NumberInput.Increment>+</NumberInput.Increment>
</NumberInput.Root>Using data attributes
Root sets data-disabled and data-readonly. Increment / Decrement buttons expose interactive state attributes and the native disabled attribute when at boundary.
[data-disabled] { opacity: 0.5; }
[data-readonly] { background: #f5f5f5; }
button[data-hover] { background: #f5f5f5; }
button:disabled { opacity: 0.4; cursor: not-allowed; }Root props
| Prop | Type | Default | Description |
|---|---|---|---|
value | number | null | — | Controlled value (null = empty) |
defaultValue | number | null | null | Initial value (uncontrolled) |
onChange | (value: number | null) => void | — | Called when the value changes |
min | number | -Infinity | Minimum allowed value |
max | number | Infinity | Maximum allowed value |
step | number | 1 | Increment / decrement step |
precision | number | step decimals | Decimal precision |
disabled | boolean | false | Disables input + buttons |
readOnly | boolean | false | Read-only field (buttons still disabled) |
Field props
Accepts all standard <input> props except value, defaultValue, onChange, and type (the field is always rendered as type="text" with inputMode="decimal").
Increment / Decrement props
Accept all standard <button> attributes. Native disabled is automatically applied when the value is at the relevant boundary.
Data attributes
| Attribute | Element | When present |
|---|---|---|
data-disabled | Root | disabled prop is true |
data-readonly | Root | readOnly prop is true |
data-hover | Increment, Decrement | Mouse is over the button |
data-focus-visible | Increment, Decrement | Button has keyboard focus |
data-active | Increment, Decrement | Button is being pressed |
Accessibility
- Field is rendered with
role="spinbutton"andaria-valuenow/aria-valuemin/aria-valuemax. - Increment / Decrement buttons receive
aria-label="Increment"/"Decrement"andtabIndex={-1}(the field handles keyboard). - Field commits the typed value on blur — partial input like
"-"or"."is treated as empty.
Keyboard Interactions
| Key | Description |
|---|---|
| ArrowUp | Increment by step. |
| ArrowDown | Decrement by step. |
| PageUp | Increment by step * 10. |
| PageDown | Decrement by step * 10. |
| Home | Jump to min (when finite). |
| End | Jump to max (when finite). |