NumberInput
A field for a number that hands back a number — never a string.
import { ... } from '@corndev/ui' onChange received: 8080 (number)
HTML
<label class="corn-label" for="port">Port</label>
<div class="corn-input-wrapper">
<input id="port" class="corn-input" data-size="md" type="text" inputmode="numeric" role="spinbutton" aria-valuemin="1" aria-valuemax="65535" value="8080" />
<span class="corn-input-section" data-position="right">
<span class="corn-number-input-controls">
<button type="button" class="corn-number-input-step" tabindex="-1" aria-label="Decrease">−</button>
<button type="button" class="corn-number-input-step" tabindex="-1" aria-label="Increase">+</button>
</span>
</span>
</div>
Classes
| Class |
|---|
.corn-input |
.corn-number-input-controls |
.corn-number-input-step |
.corn-input-wrapper |
Vanilla JS
Required for behaviour — adds the parsing that keeps the value a number, stepping with the arrow keys, Page Up/Down, Home/End and the buttons, and clamping on blur.
import { createNumberInput } from '@corndev/ui';
import '@corndev/ui/theme';
const el = document.querySelector('#target');
const instance = createNumberInput(el, { /* options */ });
Options
| Option | Type | Default | Description |
|---|---|---|---|
value | number | undefined | The value. Pass it to update() to drive the field from outside; undefined empties it. |
defaultValue | number | undefined | Initial value. |
onChange | (value: number|undefined) => void | undefined | Called with a number, or undefined when the field is emptied. Never a string, and not called for text still on its way to a number, like -. |
min | number | undefined | |
max | number | undefined | |
step | number | 1 | How far the arrow keys and the buttons move it. |
allowDecimal | boolean | true | |
allowNegative | boolean | undefined | Defaults to true unless min is 0 or more. |
hideControls | boolean | false | Leave out the − and + buttons; the arrow keys still step. |
size | 'sm'|'md'|'lg' | 'md' | |
disabled | boolean | false | |
error | boolean | false | |
fullWidth | boolean | false |
React
NumberInput — from @corndev/ui-react.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
value | number | undefined | The value (controlled). undefined is a value — an empty field — so passing the prop at all makes it controlled. |
defaultValue | number | undefined | Initial value (uncontrolled). |
onChange | (value: number|undefined) => void | undefined | Called with a number, or undefined when the field is emptied. Never a string, and not called for text still on its way to a number, like -. |
min | number | undefined | |
max | number | undefined | |
step | number | 1 | How far the arrow keys and the buttons move it. |
allowDecimal | boolean | true | |
allowNegative | boolean | undefined | Defaults to true unless min is 0 or more. |
hideControls | boolean | false | Leave out the − and + buttons; the arrow keys still step. |
size | 'sm'|'md'|'lg' | 'md' | |
disabled | boolean | false | |
error | boolean | false | |
fullWidth | boolean | false | |
leftSection | React.ReactNode | undefined | A unit or an icon before the number. |
className | string | undefined | On the wrapper, which draws the field. |
style | object | undefined | On the wrapper, so a width lands on the field. |
onChange receives a number, or undefined when the field is emptied — never a
string. That is what this component is for. A text field bound to a numeric value
hands back "1" for 1, and a form that writes that back has silently turned data
into text the moment someone clicked the field.
<NumberInput aria-label="Port" value={port} onChange={setPort} min={1} max={65535} />
Typing, and clamping
Text on its way to a number — -, 1. — may stand while you type it, and is not
reported until it is a number. Anything that can never be a number is refused.
The value is clamped on blur, not on every keystroke, so you can type 50 on
the way to 5 in a field whose max is 10 without it snapping under you.
It is a text input with the ARIA spinbutton role rather than type="number",
whose value is "" for exactly the partial entries above.
Stepping
| Key | |
|---|---|
| ↑ ↓ | Step by step |
| Page Up Page Down | Ten steps |
| Home End | min, max |
The − and + buttons step too, and are left out of the tab order because the keys
already do the job — two extra stops per number field make a form slow to cross.
hideControls removes them. Decimal steps don’t drift: 0.2 + 0.1 is 0.3.
Options
min,max,step(default1)allowDecimal(defaulttrue) andallowNegative(defaulttrue, unlessminis 0 or more)- Everything
Inputtakes —size,error,disabled,fullWidth,leftSectionfor a unit — and likeInputwith a section,classNameandstylego on the wrapper that draws the field.
In React, passing value at all makes it controlled. undefined is a real value — an
empty field — so a form that clears a controlled field back to undefined clears it.