NumberInput
A number field with stepper buttons, keyboard steps, formatting, wheel and scrub.
Basic
Type a number, or step it with the buttons or ArrowUp and ArrowDown. Shift steps by 10, Alt by 0.1. Hold a button to repeat.
Code
<NumberInput value={state.quantity} attrs={{ "aria-label": "Quantity" }}
onValueChange={(v, d) => log(d.reason)}
onValueCommit={() => state.commits++} />Usage
import { NumberInput } from "@elements/ui/number-input";
<NumberInput value={cart.quantity} min={1} max={99} attrs={{ "aria-label": "Quantity" }} />
<NumberInput value={form.price} format={{ style: "currency", currency: "USD" }} step={0.5} />
<NumberInput value={layer.width} scrubLabel="Width" buttons="end" allowWheelScrub />Examples
Min and max
Blur clamps a typed value to the bounds and Home and End go to them. allowOutOfRange keeps the typed value and marks it invalid.
Code
<NumberInput value={state.bounded} min={0} max={10} />
<NumberInput value={state.loose} min={0} max={10} allowOutOfRange />Formats
format takes Intl.NumberFormat options: currency, percent, units. Typing parses in the locale.
Code
<NumberInput value={state.price} step={0.25} format={{ style: "currency", currency: "USD" }} />
<NumberInput value={state.rate} step={0.01} min={0} max={1} format={{ style: "percent" }} />
<NumberInput value={state.euro} locale="de-DE" format={{ style: "currency", currency: "EUR" }} />Snap to step
snapOnStep: a step from 12 with step 5 lands on 15, then 20.
Code
<NumberInput value={state.snapped} step={5} snapOnStep />Scrub and wheel
Drag a scrub label to change the value. allowWheelScrub steps with the wheel while the input is focused and hovered.
Code
<NumberInput value={state.width} scrubLabel="Width" teleportDistance={60} buttons="end" min={0} step={0.5} format={{ style: "unit", unit: "centimeter" }} />
<NumberInput value={state.height} scrubLabel="Height" scrubDirection="vertical" pixelSensitivity={4} buttons="none" />
<NumberInput value={state.wheel} allowWheelScrub buttons="end" />Sizes
Heights follow the control tokens.
Code
<NumberInput value={state.small} size="sm" />
<NumberInput value={state.large} size="lg" />Disabled and read-only
Neither steps. Read-only stays focusable.
Code
<NumberInput value={state.locked} disabled />
<NumberInput value={state.fixed} readonly />In a form
name submits the raw number. Reset restores the first value; required blocks an empty submit.
Code
<form onsubmit={(e) => submit(e)}>
<NumberInput value={state.qty} name="quantity" min={1} required />
<Button type="reset">Reset</Button>
<Button type="submit" variant="primary">Submit</Button>
</form>In a field
The field's label and description wire to the input.
Between 18 and 120.
Code
<Field label="Age" description="Between 18 and 120.">
<NumberInput value={state.age} min={18} max={120} />
</Field>Refusing a change
onValueChange gets details.cancel(): this one keeps even numbers only.
Code
<NumberInput value={state.even} step={2}
onValueChange={(v, d) => v !== null && v % 2 !== 0 && d.cancel()} />