Number Stepper
A quantity field whose digits roll the way you count, with a click per step, hold-to-accelerate, and a soft wall at the limits.
Features
- Rolls the digits up when you count up and down when you count down, with NumberFlow.
- Plays a click and a selection haptic on every step, throttled so a fast hold never stacks sounds.
- Repeats when held, speeds up over the first second and a half, and moves ten steps at a time after ten repeats.
- Nudges the value with a pop and a rigid haptic when it hits
minormax, and fades out the button that can't go further. - Takes typed and pasted values, then clamps and snaps them to
stepon commit, with a warning cue when it had to correct them. - Formats currency, percent, and units through
Intl.NumberFormat, and comes inoutline,pill, andghostvariants with a vertical orientation.
Installation
pnpm dlx shadcn@latest add @tentui/number-stepper
Usage
import { NumberStepper } from "@/components/number-stepper";<NumberStepper label="Guests" variant="pill" defaultValue={2} min={1} max={12} />Use value and onValueChange to control it. onValueChange fires on every
step; onValueCommit fires once a press is released, a typed value is
committed, or a key steps. Changes that come from the value prop stay silent,
and a new value from outside stops a hold in progress.
const [seats, setSeats] = useState(1);
<NumberStepper
label="Seats"
value={seats}
onValueChange={setSeats}
min={1}
max={8}
/>Pass format for currency, percent, or units. Percent values are fractions,
so typing 15 into the field below means 0.15.
<NumberStepper
label="Tip"
defaultValue={0.15}
max={0.3}
step={0.05}
format={{ style: "percent" }}
/>
<NumberStepper
label="Budget"
defaultValue={40}
step={5}
format={{ style: "currency", currency: "EUR" }}
locale="de-DE"
/>Set orientation="vertical" to put the increase button above the value, and
size to sm, md, or lg. Give it a name to submit the value with a
native form, and set wheel to let the scroll wheel change a focused field.
Accessibility
The input is a spinbutton with aria-valuenow, aria-valuemin,
aria-valuemax, and an aria-valuetext built from format. Name it with
label. The buttons read "Increase" and "Decrease" followed by the label, and
are left out of the tab order so Tab goes straight to the input.
Arrow keys move one step, Shift with an arrow or PageUp/PageDown move
largeStep steps, Home/End jump to the limits, Enter commits a typed
value, and Escape reverts it. Changes made with the buttons are announced
through a polite live region once the value settles, so a hold doesn't chatter.
With reduced motion enabled the digits change without rolling, and there is no
nudge or pulsing ring. Sound and haptics stay on because they confirm each step
rather than decorate movement; turn them off with sound={false} and
haptics={false}.