Table of contents
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 |
|---|---|
![]() |
![]() |
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
driftwood
Layout
Actions
Forms
- Field
- Input
- FileInput
- FileList
- Textarea
- CodeEditor
- Combobox
- RadioGroup
- NumberInput
- InlineEdit
- Label
- Select
- MultiSelect
- DatePicker
- TagsInput
- Checkbox
- Switch
- Slider
Feedback
Charts
Overlays
Navigation
Generated from the source by pnpm wiki · repository · v0.24.0

