NumberInput
Numeric input with steppers, a clamped range, and decimal precision. Built on Input, so it inherits its sizes, variants, and validation surface.
Playground
<NumberInput
helper="Use the steppers or the arrow keys."
label="Quantity"
max={10}
min={0}
precision={0}
/>01 Examples
Quantity in a line item
min keeps it out of negatives, max caps it at what is in stock, and the stepper clamps immediately instead of waiting for blur.<NumberInput label="Quantity" bind:value={quantity} min={0} max={99} step={1} />Decimal rate with fixed precision
step and precision are separate: step is how far one press moves, precision is how many decimals are displayed and rounded to. Without precision the value shows as typed and the step's own decimals drive the rounding.<NumberInput
label="Commission rate"
bind:value={rate}
min={0}
max={1}
step={0.05}
precision={2}
helper="Between 0 and 1"
/>Without the stepper
hideStepper removes the buttons but not the behaviour — Arrow keys and the wheel still step. Useful in a dense row where two more hit targets per field would crowd the layout.<NumberInput label="Servings" bind:value={servings} min={1} max={12} hideStepper />02 Customization
NumberInput wraps <Input>, so size, variant, label, helper, error and the Input slotClasses keys all behave exactly as they do there.
What it does not forward is deliberate: the numeric event handlers, the raw string value, the fixed type/inputmode,
and clearable — Input's clear button would replace the stepper
and write only Input's internal string, drifting the numeric model out of sync.
rightIcon replaces the stepper with an adornment of
your own. Pair it with hideStepper or supply your own
controls — otherwise the field loses its increment affordance while keeping the keyboard one.
See Customization for the general contract.
For money use CurrencyInput instead: it stores minor units, so summing and comparison stay exact. NumberInput's values are plain numbers and carry the usual float caveats.
03 Accessibility
Spinbutton semantics
The field is a role="spinbutton" carrying aria-valuenow, aria-valuemin and aria-valuemax, so assistive tech announces both the
current value and the range it sits in.
Keyboard
↑ / ↓ step by step and
clamp immediately. Typing is free-form while focused — a leading - and a single decimal separator (. or ,) are both accepted — and the value clamps to the
range on blur.
The steppers stay out of the tab order
Both stepper buttons carry aria-hidden and tabindex="-1". The spinbutton role already exposes increment and decrement
on the field itself, so the buttons are the pointer affordance for what the Arrow keys do — not
a second pair of tab stops.
Numeric keyboard on mobile
inputmode="decimal" is fixed, so mobile keyboards open
the numeric pad with a decimal separator rather than the full alphabetic layout.
The wheel only steers a focused field
Wheel stepping is gated on focus. An unfocused field scrolled past in a long form would otherwise change its value while the user is only trying to reach the bottom of the page.
04 API Reference
Prop | Type | Default | Description | |
|---|---|---|---|---|
autoComplete inherited | string | — | HTML autocomplete hint for browser autofill. | |
class inherited | string | — | Extra classes merged onto the root wrapper element. | |
disabled inherited | boolean | false | Whether the NumberInput is disabled and non-interactive | |
error inherited | string | — | Error message below the input. When set, overrides helper and
forces danger border styling regardless of intent. | |
helper inherited | string | — | Helper text below the input — hidden when error is present. | |
hideStepper | boolean | false | Hide the up/down stepper buttons (Arrow keys + wheel still work). | |
label inherited | string | — | Label text displayed above the input, auto-linked via for/id. | |
leftIcon inherited | Snippet | — | Icon snippet rendered on the left side of the input field. | |
leftIconAriaLabel inherited | string | — | Accessible label for the clickable left icon button. Required when
onLeftIconClick is set so screen-reader users hear a name for the
button (icons inside are aria-hidden). | |
max | number | — | Maximum allowed value. Clamped on step / Arrow / blur. | |
min | number | — | Minimum allowed value. Clamped on step / Arrow / blur. | |
mint | InputProps['mint'] | 'none' | Micro-interaction preset forwarded to the inner Input. Redeclared from InputProps so the inheritance is a documented contract rather than an accident of the Omit list. | |
name | string | — | Shared name for a hidden input for native form submission. | |
onLeftIconClick inherited | () => void | — | When provided, the left icon becomes a clickable button. | |
onRightIconClick inherited | () => void | — | When provided, the right icon becomes a clickable button. | |
onValueChange | (value: number | null) => void | — | Fires after the value changes (typing, stepper, Arrow, wheel, or clamp). | |
persistDebounceMs inherited | number | 300 | Debounce interval (ms) for storage writes. | |
persistKey inherited | string | — | Key for persisting the input value to storage. | |
persistNamespace inherited | string | — | Namespace (e.g. user id) to scope the persist key. | |
persistStorage inherited | localStoragesessionStorage | 'localStorage' | Storage backend for persistence. | |
persistVersion inherited | number | 1 | Version stamp included in the storage key. | |
precision | number | — | Fixed number of decimal places for display and rounding. When unset, the value is shown as typed and the step's own decimals drive rounding. | |
preset inherited | string | — | Apply a named preset registered via <BlocksProvider presets={{ Input: {...} }}>.
Prefer this over class overrides when the requested look falls outside the
semantic intent palette — presets keep hover/active/dark-mode logic coherent
and make the custom look reusable across the project. | |
readonly inherited | boolean | false | Readonly property for the NumberInput component | |
required inherited | boolean | false | Adds a required asterisk to the label and sets the native required attribute. | |
rightIcon | Snippet | — | A custom right-side adornment. Overrides the stepper — pair with hideStepper or provide your own controls. | |
rightIconAriaLabel inherited | string | — | Accessible label for the clickable right icon button. Required when
onRightIconClick is set so screen-reader users hear a name for the
button (icons inside are aria-hidden). | |
slotClasses inherited | Partial<Record<InputSlots, string>> | — | Per-slot class overrides merged with tv() styles. Slots: wrapper (root —
what class also targets) | container | base (the <input> element) |
label | message | iconContainer | iconButton | iconDecoration. | |
step | number | 1 | Increment applied by the stepper buttons, Arrow keys, and wheel. | |
unstyled inherited | boolean | — | Remove all default tv() classes — only user-provided classes apply. | |
value | number | null | — | Current numeric value. null when the field is empty. Supports bind:value. |
05 Types
Local type definitions used by this component.
Name | Kind | Category | Used by | Description | |
|---|---|---|---|---|---|
NumberInputProps | interface | props | 0 | — | |
InputProps | interface | props | 0 | — | |
InputVariants | type | variant | 0 | — | |
MintProp | type | helper | 2 | — | |
InputSlots | type | variant | 0 | Slot names derived from the tv() config above — single source of truth for slotClasses. | |
VariantProps | type | helper | 0 | — | |
MintName | type | helper | 0 | A mint name: a built-in (autocompleted), 'none' to disable, or any
consumer-registered name. (string & {}) keeps the registry open — a
custom name still type-checks, it just isn't suggested. A typo therefore
also still compiles (it resolves like an unregistered custom name and
warns at runtime); the union buys completion and docs, not validation. | |
MintConfig | interface | helper | 0 | — | |
SlotNames | type | helper | 0 | Extracts the slot-name union from a slotted tv() config function — the
companion to VariantProps. The slot-mode overload returns
(props?) => { [K in keyof S]: SlotFn }, so keyof ReturnType<T> is exactly
the set of slot names a component declares in tv({ slots: … }).
Use it to type a component's slotClasses prop from the single source of
truth (its *.variants.ts) instead of hand-maintaining a parallel union
that silently drifts when a slot is added or renamed: |
06 Installation
Import
import { NumberInput } from '@urbicon-ui/blocks';