7 NumberInput
L4stIdi0t edited this page 2026-10-09 22:01:06 +02:00

NumberInput

Numeric field with a unit suffix and real clamping — the thing a plain <input type="number"> does not give you.

Three deliberate behaviours:

  • Clamping happens on blur, Enter and stepping, never per keystroke, so typing "12" on the way to "120" is not rewritten to the minimum under the user's fingers.
  • onValueChange is live (every keystroke that parses); onValueCommit fires when the user settles — blur, Enter, arrow step. Same split as Slider, so expensive work hangs off the same channel in both.
  • The suffix is decoration: aria-hidden, pointer-events-none, and the input keeps its own accessible name. Put the unit in the label ("Width (px)") — a screen reader should not have to read it off a span.

Native ↑/↓ stepping is reimplemented here because the field is type="text": a number input rejects intermediate states ("1e", "-") by blanking value, which makes controlled editing lurch.

Forms · two-way: bind:value · submits in a form with name · keyboard contract

<script lang="ts">
	import { NumberInput } from '@l4st/driftwood';
</script>
Light Dark
number-input number-input in dark mode

Props

Prop Type Default What it does
value bind: number 0 Clamped and snapped to step on commit, never mid-keystroke.
min number Number.NEGATIVE_INFINITY Lower bound, applied on commit.
max number Number.POSITIVE_INFINITY Upper bound, applied on commit.
step number 1 Arrow-key and rounding increment. Shift+arrow moves ten of them.
suffix string — Unit shown inside the field, e.g. 'px'. Decorative — see above.
placeholder string — Shown while the field is empty.
disabled boolean false Blocks typing and stepping.
readonly boolean false Shows the value but refuses edits.
class string — Merged with the component's own classes, last — so yours win.
id string — Points a <Label for=…> at the input.
name string — Submits the current value with the form.
aria-label string — Accessible name, when no visible <Label for=…> points at this.
aria-labelledby string — Id of the element that names this, instead of aria-label.
aria-describedby string — Id of the hint or error text describing this field.
onValueChange (value: number) => void — Every keystroke that parses to a number in range.
onValueCommit (value: number) => void — The user settled: blur, Enter, or an arrow step.

Keyboard

Key Does
↑ / ↓ step, and commit
Shift + ↑ / ↓ step by ten
Enter clamp, commit and keep focus

Example

The markup behind the screenshot above, lifted from src/routes/shots/ — so it compiles, and it is exactly what produced the image.

<NumberInput bind:value={width} min={1} max={4096} suffix="px" aria-label="Width" />
<NumberInput value={2.5} step={0.5} min={0} suffix="mm" aria-label="Bleed" />

Forms · Field · Input · FileInput · FileList · Textarea · CodeEditor · Combobox · RadioGroup · InlineEdit · Label · Select · MultiSelect · DatePicker · TagsInput · Checkbox · Switch · Slider


← All components · Getting started · Theming · source