ns-ui
Numeric spinbutton built as a ratchet and pawl: incrementing is free and clicky and a hold repeats and accelerates, but decrementing requires holding ~250ms while the pawl visibly swings 35 degrees clear before a hold starts stepping back, also accelerating.
Use when a quantity stepper for a CONSEQUENTIAL value (seats, licenses, allocations) where accidental decrement is costly: incrementing is a free, instant press (and a hold repeats, accelerating from 400ms toward 60ms per step), but decrementing needs a ~250ms pointer hold before the same accelerating repeat starts running backward — direction-dependent friction that makes 'down is deliberate' legible without a confirm dialog. This is a continuing, held control with per-step asymmetric resistance, not a single time-gated commit (see confirm-hold-ink) and not a value-indicating gauge (see stepper-needle, whose stepper is symmetric in both directions).
Install
npx shadcn add https://design.helpmarq.com /r/stepper-ratchet.jsonns-ui / stepper-ratchet — up is free, down needs a hold
Billed monthly at $24/seat. Removing a seat revokes access immediately, so it takes a deliberate hold.
Units reserved for this project. Tap + to grab more; hold − and the pawl has to swing clear before it lets go of one.
Press + to add — instant, no arm delay, and holding it repeats, accelerating from 400ms toward 60ms between steps. Press and hold − instead: the pawl rotates clear over a quarter second before it starts giving anything back, then repeats on that same accelerating schedule. Both work with mouse or touch, and each step pulses a quick vibration where the device supports it (a no-op on this Mac). Focus the number and use Arrow Up/Down: both directions step immediately, no hold required.
| Prop | Type | Default | Description |
|---|---|---|---|
| value? | number | — | controlled value; omit for uncontrolled |
| defaultValue? | number | — | — |
| min? | number | — | — |
| max? | number | — | — |
| step? | number | — | — |
| label? | string | — | accessible name for the spinbutton, and the on-screen caption |
| unit? | string | — | unit suffix rendered beside the value, e.g. "seats" |
| onValueChange? | (value: number) => void | — | — |
| disabled? | boolean | — | — |
| className? | string | — | — |