Skip to content
txbrendan/ui

Number Stepper

beta

Minus, the number, plus. For small counts like seats, guests, or minutes, and for live scores.

Preview

size

seats
seats
seats

score

Design
Support

format

min

states

seats
seats
seats

Install

  1. 1. Add the registry to components.json, once per app. Without it, the shadcn CLI looks up @txbrendan in a public list that txbrendan/ui does not control.

    npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"
  2. 2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.

    npx shadcn@latest add @txbrendan/theme
  3. 3. 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

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" />