Stepper

Atom
Increment, decrement, or type a number in a bounded range. A − button, an editable numeric center field, and a + button, controlled by value and clamped to min / max by step. This is the ± numeric control (the iOS UIStepper idiom); for a multi-step progress indicator see Steps.

Playground

Every control sets a real prop of StepperProps. The JSX below updates as you go.
Spacing
Props
value
number
2
label
string
description
string
Size
pick one
disabled
boolean
JSX · StepperProps
<Stepper value={2} label="Spacing" />

Variants

6 examples from src/atoms/stepper/stepper.md, each rendered live from its own JSX.
Quantity
With label
<Stepper label="Quantity" defaultValue={1} min={1} max={9} />
Quantity
Up to 10 per order.
With description
<Stepper label="Quantity" description="Up to 10 per order." defaultValue={1} min={1} max={10} />
Sizes
<Row alignCenter relaxed>
<Stepper small defaultValue={2} max={10} />
<Stepper defaultValue={2} max={10} />
<Stepper large defaultValue={2} max={10} />
</Row>
With min / max
<Row alignCenter relaxed>
<Stepper defaultValue={0} min={0} max={5} />
<Stepper defaultValue={5} min={0} max={5} />
</Row>
Stepped
<Stepper defaultValue={20} max={100} step={10} />
Disabled
<Stepper disabled defaultValue={4} max={10} />

Sizes

Spacing
small
Spacing
default
Spacing
large

States

Hover, focus and press are pinned so they can be compared side by side; the rest are real props.
Spacing
default
Spacing
hover
Spacing
focus
Spacing
disabled

Anatomy

Spacing
1
Decrement
2
Value
3
Increment
4
Label
Dimmed parts appear only in some states or variants.

Props

16 props on StepperProps, grouped as they are declared in source. Required props are marked *.
Prop
Type
Default
Description
Props
12 props
value
number
–
Controlled numeric value (clamped to [min, max] for display); omit for uncontrolled use.
defaultValue
number
–
Initial value for uncontrolled use (a bare <Stepper /> steps out of the box). Default min.
onChange
(next: number) => void
–
Fired with the next clamped value when buttons, adjustment keys, or direct entry change it (both modes).
testID
string
–
E2E hook forwarded to the group container.
min
number
0
Lower bound. Default 0. The − button disables when value <= min.
max
number
–
Upper bound. Default Number.MAX_SAFE_INTEGER. The + button disables when value >= max.
step
number
1
Increment/decrement amount for the ± buttons and adjustment keys. Default 1.
label
string
–
The control's persistent, component-owned label. When set it renders as a VISIBLE title ABOVE the ± control (mirroring Input's above-field placement, per-OS type from the skin) and becomes the group's programmatic name (wired via accessibilityLabel + aria-labelledby). When omitted, the control keeps the invisible "Number" accessible-name fallback and renders no visible label, so a bare <Stepper /> looks exactly as before.
description
ReactNode
–
Optional muted secondary line rendered under the label (parity with Input). Takes effect only alongside label.
required
boolean
false
Marks the control required: appends a destructive "*" to the label (decorative, never in the accessible name) and announces the control as required, through aria-required on the web's numeric field and with the word in its name ("Quantity, required") on iOS and Android. The star takes effect only alongside label.
suffix
ReactNode
–
A unit or note beside the control, 16 px after it on the same line ("× 4px", "items"): a string is set in the 400 13 px subtle ink, a node as it is. Unlike description, it reads as part of the value's line.
ref
React.Ref<RNTextInput>
–
Ref to the underlying React Native host. Supports object refs and callback refs; native methods follow the platform's behavior.
Size
pick one
2 props
small
boolean
false
large
boolean
false
State
2 props
disabled
boolean
false
State.
style
LayoutStyle
–
Composition within a parent only, never a restyle hook and never a width: the parent layout container provides the bounds.

Accessibility and keyboard

Keyboard
ArrowUp
Increments
ArrowDown
Decrements
Home
End
Minimum · maximum
Semantics
Roles
button
States
disabled
ARIA on web
aria-describedby
aria-disabled
aria-label
aria-labelledby
aria-required
Below the platform minimum, the touch area grows to 44 pt on iOS and 48 dp on Android without moving the layout.
From the docs
Pass label to title the control from above, description for a secondary line under it, and required to mark it: the label takes a decorative destructive star, and a screen reader hears the control as required, from aria-required on the web's numeric field and from its name ("Quantity, required") on iOS and Android.

Platform skins

The playground's props on each platform: same tree, native shapes, type and touch sizes.
Web
control 10 · field 10 · card 16 · Instrument Sans
iOS
capsule controls · field 8 · card 12 · SF Pro · 44 pt
Android
Material 3 stadium · field 4 · card 12 · Roboto · 48 dp

Usage

<Stepper defaultValue={3} max={10} />
On the web the control is one 40 px box (32 small, 48 large) at the control corner with square ± cells and the value in the mono face; on iOS the capsule [ − | + ] pill with the value to its left; on Android Material 3's outlined circular icon buttons around the value.

Tokens

Read from the playground render. Values follow the scheme and the ion hue.
--fd
--fm
--ft
--ln2
--sf2
--tx2

Related components