Quantity Stepper Roll
Minus and plus stepper whose digits roll vertically like an odometer, with press squash, hold to repeat and a refusal shake at the limits.
About This Component
A quantity stepper whose number does not simply change: each digit that differs rolls vertically like an odometer, the outgoing character sliding out of the slot while the incoming one arrives from the other side. The direction follows the button, so counting down rolls the opposite way to counting up.
Read the full effect overview
A single self-contained component: one HTML file, one stylesheet, one script, vanilla JavaScript and GSAP core, no framework and no plugins. A native input type="number" stays in the DOM and holds the value, so the stepper submits with an ordinary form and reads correctly to a screen reader. Ships in three looks, vault, soft and corporate, switched with one data-variant attribute.
What's Included
- Per-digit vertical roll, direction follows plus or minus
- Only the digits that actually changed animate
- Press squash on the buttons, released with a back.out spring
- Hold a button to repeat, with a pause before the run starts
- Buttons disable at min and max, with a refusal shake on the attempt
- Native input type=number keeps the value for forms and screen readers
- Typing into the field clamps to range and rolls to the new number
- Three shipped variants: vault, soft, corporate
Perfect For
- Cart and checkout quantity controls
- Seat, ticket and guest pickers on booking forms
- Portion and serving counters on recipe or ordering screens
- Any numeric setting where the change should be legible, not instant
How It Works
Slot per character
The display is one overflow-hidden slot per character. On a change the new character is appended to the slot, set to yPercent 100 or -100 depending on direction and tweened to 0, while the outgoing span leaves the other way and removes itself onComplete. Slots whose character is unchanged are skipped, so 19 to 20 rolls two digits and 10 to 11 rolls one.
Squash and refusal
A press tweens the button to scaleY 0.86 and scaleX 1.08 and releases it with back.out(4). At a limit the button takes the disabled attribute and pointer-events none, and a press inside its rectangle runs a short elastic x shake instead, so the refusal is visible rather than silent.
The input owns the value
Every path, button, hold repeat and keyboard, writes into the native number input and dispatches change. The rolled digits are aria-hidden decoration over the top of it, so assistive technology and form submission both see one plain number.