Number Stepper
betaMinus, the number, plus. For small counts like seats, guests, or minutes, and for live scores.
Preview
size
score
format
states
Install
1. Add the registry to
components.json, once per app. Without it, the shadcn CLI looks up@txbrendanin a public list that txbrendan/ui does not control.npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.
npx shadcn@latest add @txbrendan/theme3. Add the component.
npx shadcn@latest add @txbrendan/number-stepper
Use it for
- Small counts people nudge up or down: seats offered, guests per table, minutes per meeting.
- Give it a name: aria-label, like "Seats offered", or an id that a visible label's htmlFor points at.
- Set min and max. The buttons stop there, and Home and End jump there.
- Put the unit in suffix, like "seats". For a unit that can change, pass a UnitSwitch as the suffix.
- Set score for a live scoreboard: no box, round buttons, and a big number.
- The number rolls when it changes, up when it grows and down when it shrinks. With motion off, it changes at once.
Avoid it for
- Money and long numbers people type, like a price or a phone number. Use an input.
- Picking from a few named options. Use a radio group or a select.
- Ranges people drag across, like a volume. Use a slider.
Props
- value
The number. Use it with onValueChange.
number | null
- defaultValue
The number at first render. Defaults to min, or 0.
number
- onValueChange
Called with each new number, or null when the text is cleared.
(value: number | null) => void
- min
The lowest number. The minus button stops there.
number
- max
The highest number. The plus button stops there.
number
- step
How much the buttons and the arrow keys move it.
number · default 1
- largeStep
How much Page Up and Page Down, or Shift with an arrow, move it.
number · default 10
- size
Height and text size. The same heights as Input.
"sm" | "md" | "lg" · default "md"
- score
No box, round buttons, and a big number, for live scores.
boolean · default false
- format
How the number shows, like { style: "percent" }.
Intl.NumberFormatOptions
- suffix
A muted unit after the number, like "seats", or a UnitSwitch.
ReactNode
- disabled
Blocks the buttons and typing.
boolean · default false
- name
Form field name. The number is sent with the form.
string
- aria-label
Names the number. Required unless you pass aria-labelledby, or an id with a visible label.
string
- className
Classes for the box. Later classes win.
string
| Prop | Type | Default | What it does |
|---|---|---|---|
| value | number | null | - | The number. Use it with onValueChange. |
| defaultValue | number | - | The number at first render. Defaults to min, or 0. |
| onValueChange | (value: number | null) => void | - | Called with each new number, or null when the text is cleared. |
| min | number | - | The lowest number. The minus button stops there. |
| max | number | - | The highest number. The plus button stops there. |
| step | number | 1 | How much the buttons and the arrow keys move it. |
| largeStep | number | 10 | How much Page Up and Page Down, or Shift with an arrow, move it. |
| size | "sm" | "md" | "lg" | "md" | Height and text size. The same heights as Input. |
| score | boolean | false | No box, round buttons, and a big number, for live scores. |
| format | Intl.NumberFormatOptions | - | How the number shows, like { style: "percent" }. |
| suffix | ReactNode | - | A muted unit after the number, like "seats", or a UnitSwitch. |
| disabled | boolean | false | Blocks the buttons and typing. |
| name | string | - | Form field name. The number is sent with the form. |
| aria-label | string | - | Names the number. Required unless you pass aria-labelledby, or an id with a visible label. |
| className | string | - | Classes for the box. Later classes win. |
Accessibility
- The number is a text field, so people can type it as well as step it.
- Its type needs a name: aria-label, aria-labelledby, or an id for a label's htmlFor.
- The arrow keys step it, Shift with an arrow and Page Up and Page Down move 10, and Home and End jump to min and max.
- The buttons are named "Decrease" and "Increase", and they are not Tab stops, since the keys do the same. Holding one repeats.
- The suffix describes the field, so screen readers hear the unit after the number.
- The roll reads --motion-duration-base. When reduced motion sets it to 0, the number changes with no movement.
Examples
Seats, with min, max, and a unit
<NumberStepper aria-label="Seats offered" defaultValue={4} min={1} max={8} suffix="seats" />The big score look
<NumberStepper aria-label="Design score" score defaultValue={0} min={0} />Minutes, with a visible label
<label htmlFor="length">Meeting length</label>
<NumberStepper id="length" defaultValue={45} min={10} max={60} step={5} size="sm" />