Number Input
Steppers, drag-to-scrub, min, max, step and locale formatting.
Built on Icons.
Team plan
Drag a label sideways to scrub. Hold Shift for bigger steps.
Installation
npx shadcn@latest add https://neonkit.heyaadi.com/r/number-input.jsonUsage
import { NumberInput } from "@/components/ui/number-input"
<NumberInput
label="Price"
defaultValue={24}
min={0}
formatOptions={{ style: "currency", currency: "USD" }}
/>Examples
Formats and locales
formatOptions takes any Intl.NumberFormat options. Typing is read back leniently, with the locale's separators, percent signs and compact suffixes.
Scrubbing
A leading addon is a grip too, which suits dense inspectors without visible labels.
Sizes, error and disabled
API reference
Also accepts native input attributes, and the ref points at the input. className and style go to the field box.
| Prop | Type | Default |
|---|---|---|
value | numberControlled value. null is an empty field. | None |
defaultValue | numberInitial value when uncontrolled. | null |
onValueChange | (value: number | null) => voidCalled when a value is committed: on blur, Enter, stepping and scrubbing. Not on every keystroke. | None |
min | numberLowest value. Typed values are clamped to it, and Home jumps to it. | None |
max | numberHighest value. Typed values are clamped to it, and End jumps to it. | None |
step | numberAmount for the arrow keys, the steppers and scrubbing. Steps snap to multiples from min. | 1 |
largeStep | numberAmount for Shift+Arrow, PageUp, PageDown and Shift while scrubbing. | 10 |
formatOptions | Intl.NumberFormatOptionsHow the value displays: currency, percent (value 0.15 shows 15%), units, compact, fraction digits. | None |
locale | stringBCP 47 locale for formatting and parsing. | browser |
scrub | booleanDrag the label or leading addon sideways to change the value. | true |
leading | ReactNodeIcon or text before the value. A scrub grip when scrub is on. | None |
trailing | ReactNodeIcon or text after the value, before the steppers. | None |
name | stringSubmits the raw number (not the formatted text) with a surrounding form. | None |
label | ReactNodeVisible label. A scrub grip when scrub is on. | None |
description | ReactNodeHelper text under the field. | None |
error | ReactNodeRed invalid state and message. Replaces description. Pass true for the state without a message. | None |
disabled | booleanBlocks editing, stepping and scrubbing. | false |
readOnly | booleanShows the value without letting it change. | false |
color | NeonColorPalette name or hex. Falls back to --neon-accent, then cyan. | inherited |
size | "sm"Field height: 32, 40 or 48px. | "md" |
containerClassName | stringClasses for the outer block with the label, field and message. | None |
Accessibility
- required passes through to the input and shows the neon dot after the label. A form reset puts back the starting number.
- When the browser rejects the field on submit (required, type, pattern), it turns red until it's edited or the form resets. The browser shows its own message.
- The input is a spinbutton with aria-valuenow, aria-valuemin, aria-valuemax and aria-valuetext set to the formatted value.
- ArrowUp and ArrowDown step by step; with Shift, or PageUp and PageDown, by largeStep. Home and End jump to min and max when they're set.
- Enter commits what's typed and Escape drops it. Text that holds no number reverts to the last value; an empty field commits null.
- The stepper buttons repeat while held and speed up. They're out of the Tab order, since the arrow keys do the same job.
- Scrubbing captures the pointer, so the drag keeps working past the edge of the label. Shift moves by largeStep and Alt slows to one step per 10px.
- The tick animation is skipped under prefers-reduced-motion.