v3.2.0
NumericStepper

A value between a "−" and a "+" button — the fare the passenger offers, or a count.

3.0 3.0 -- 3.2.0

Overview

NumericStepper changes a value a step at a time with the "−" and "+" buttons; the Counter in the middle shows the value and opens direct entry. It holds the fare the passenger offers, or a count.

  • 1
    Standard
  • 2
    Empty
  • 3
    Green
  • 4
    Orange
  • 5
    Red
  • 6
    Skeleton

States

The six Fare State values, one block each. Press, focus and a disabled side button are interaction on each button, listed in the spec's States table — see Animation and Accessibility.

Standard

  • 1
    Decrease --surface-on-white — the fill, the Squircle's own.--text-and-icon-primary — the icon.Standard by default; the host sets each side button on its own.
  • 2
    Counter --surface-on-white — the fill; no tone.--text-and-icon-secondary — the chevron.
  • 3
    Title --text-and-icon-primary — the currency and the value.The default — an instance may set another TextAndIcon token.
  • 4
    Increase --text-and-icon-primary — the icon.Standard by default; the host sets each side button on its own.

Empty

  • 1
    Decrease --text-and-icon-disabled — the icon.Disabled by default; the host sets each side button on its own.
  • 2
    Placeholder --text-and-icon-secondary — "Tap to enter", in place of the title.No value yet; the Counter opens direct entry.
  • 3
    Increase --text-and-icon-disabled — the icon.Disabled by default; the host sets each side button on its own.

Green

  • 1
    Decrease --text-and-icon-primary — the icon.Standard by default; the host sets each side button on its own.
  • 2
    Glow --pastel-green1 — at 60 %, a radial gradient from the bottom centre to transparent.The Positive tone; the screen's own text says why.
  • 3
    Edge --border-success — a 1dp inside edge, radial from the bottom centre, gone by 32 % of the radius.
  • 4
    Increase --text-and-icon-disabled — the icon.Disabled by default; the host sets each side button on its own.

Orange

  • 1
    Decrease --text-and-icon-disabled — the icon.Disabled by default; the host sets each side button on its own.
  • 2
    Glow --pastel-orange1 — at 60 %, a radial gradient from the bottom centre to transparent.The Warning tone; the screen's own text says why.
  • 3
    Edge --border-warning — a 1dp inside edge, radial from the bottom centre, gone by 32 % of the radius.
  • 4
    Increase --text-and-icon-primary — the icon.Standard by default; the host sets each side button on its own.

Red

  • 1
    Decrease --text-and-icon-disabled — the icon.Disabled by default; the host sets each side button on its own.
  • 2
    Glow --pastel-red2 — at 60 %, a radial gradient from the bottom centre to transparent.The Error tone; the screen's own text says why.
  • 3
    Title --text-and-icon-error — the currency and the value.
  • 4
    Edge --border-error — a 1dp inside edge, radial from the bottom centre, gone by 32 % of the radius.
  • 5
    Increase --text-and-icon-disabled — the icon.Disabled by default; the host sets each side button on its own.

Skeleton

  • 1
    Side Buttons --skeleton-on-white — the Squircle Skeleton; no icons.--pattern-shimmer — the DS sweep.
  • 2
    Counter --skeleton-on-white — the fill; no text, no chevron.--pattern-shimmer — the same sweep; it runs in reverse in RTL.

Anatomy

Three buttons in a row: "−", the Counter, "+". The Counter carries the value and, optionally, a subtitle and a chevron.

  • 1
    Container A horizontal row that fills its host; no fill of its own. Required.
  • 2
    Decrease Squircle L, Type Icon — the "minus" icon.Required.
  • 3
    Counter --surface-on-white — the fill; a customButton that opens direct entry.Its row: the spacer, the text, the chevron. In Empty, "Tap to enter" replaces the title — see States. Required.
  • 4
    Currency Heading/Heading 3 — the text style, the same as the value.Before the value or after it; none for a count. Optional.
  • 5
    Value Heading/Heading 3 — the text style.--text-and-icon-primary — the colour, shared with the currency.Formatted by the product; one line. Required.
  • 6
    Increase Squircle L, Type Icon — the "add" icon.Required.
  • 7
    Spacer var(--sp-s24) — an empty box at the Counter's start, the chevron's width.It balances the chevron, so the text stays centred. Comes and goes with the chevron.
  • 8
    Subtitle TextRow in Expanded mode — one line, centred under the title.Body/Compact Body — the text style.--text-and-icon-secondary — the colour.Off by default. Optional.
  • 9
    Subtitle Icons var(--sp-s16) — an IconContainer in the TextRow's start or end slot, var(--sp-s4) from the text.Up to two on each side. A mono icon takes the subtitle colour; a colour icon — here wallet-color — keeps its own. Optional.
  • 10
    Chevron --text-and-icon-secondary — chevron-right-small in an IconContainer.On by default. Optional.

Layout

One size: a row of three s56 buttons that fills its host. The Counter takes the width the side buttons leave.

General

  • Container var(--sp-100-s8) — padding on all four sides; fixed.var(--sp-100-s4) — the gap between each side button and the Counter; fixed.Fills its host's width; hugs its height — 72 at 100 %.
  • SP Modes Figma binds the Counter's height and vertical padding to SP/130 % and everything else to SP/100 %, and the web takes the same families: the Counter grows to 72.8 at 130 % and holds there, the side buttons stay 56 — above 100 % the Counter stands taller than them.

Buttons

  • Counter var(--sp-130-s56) — the height, capped at 130%: it holds at 72.8 in the 150% mode; a two-line text centres in it.var(--sp-s24) — the spacer at the start and the chevron at the end; the spacer balances the chevron, so the text stays centred.var(--sp-s0) — the side padding and the gaps; the spacer and the chevron sit flush at the ends.var(--sp-130-s8) — top and bottom padding, capped at 130%.Fills the width the side buttons leave.
  • Side Buttons var(--sp-100-s56) — Squircle L, held at 100%: Figma binds the side buttons to SP/100%, over the Squircle L cap. It brings its states.var(--sp-100-s24) — the icon, fixed with the box.
  • Corner Radius var(--sp-100-s20) — all three buttons; fixed.
  • Without The Chevron The spacer goes with it — one Figma property toggles both; the text centres across the whole Counter.

Text

Prefix — the currency before the value
Suffix — the currency after the value
A count — no currency
Subtitle — the previous fare struck through
Subtitle — a TextRow with an icon in its end slot
  • Title Heading/Heading 3 — one line, centred in the Counter.var(--sp-s2) — the hair space between the currency and the value.The currency is a prefix or a suffix; a count has none. The value is formatted by the product.
  • Subtitle var(--sp-s0) — the gap to the title.var(--sp-s16) — the icons in the TextRow's start and end slots, up to two on each side.var(--sp-s4) — the TextRow's gap between the text and an icon.One line, centred under the title. Off by default — see Anatomy.
  • Overflow One line, never wrapped: an over-long value ends in an ellipsis on the web (iOS shrinks it). Figma stages no overflow.

RTL

  • 1
    LTR "−" on the left, "+" on the right; the chevron at the Counter's right end.
  • 2
    RTL The row mirrors: "+" on the left, "−" on the right; the chevron points left at the Counter's left end, the var(--sp-s24) spacer at its right end.The title keeps its order — the currency side is the Currency setting, not the direction.The DOM, Tab and reading order stay Decrease, Counter, Increase. The web takes the direction from dir="rtl".

Animation

Motion follows the motion system § 6.1 — pushHighlight, the press without scale. Two motions: the press, and the skeleton's shimmer.

Press

Rest
Pressed — the overlay on the Counter, frozen
Live — press "−", "+" or the Counter; steps of 5,000 between 110,000 and 130,000 (demo values)
  • General --component-push-highlight — press and release, 150 ms linear.var(--shadow-pressed) — the pressed overlay over the button's fill; it fades in and out, no scale.The same on all three buttons. A disabled side button does not react.
  • Value Change No motion: the new value replaces the old in the same frame — no count-up, no width tween. A change of Fare State swaps the tone, the placeholder and the side buttons' states in the same frame too.
  • Reduced Motion Handled at the token: motion.css zeroes every --component-* inside prefers-reduced-motion: reduce, so the overlay appears without a fade; no override of its own.

Skeleton

LTR
RTL — the sweep runs the other way
  • Shimmer var(--pattern-shimmer) — the DS sweep on every shape: the Squircle Skeleton on the side buttons, the same sweep on the Counter.It runs in reverse in RTL.
  • Skeleton → Content The host swaps the skeleton for the content; the stepper declares no transition of its own.
  • Reduced Motion var(--pattern-shimmer) — zeroed at the token, so the skeleton holds still.

Usage

NumericStepper sits where a person sets a value near a known one, a step at a time or by typing it in.

  • 1
    The Fare On The Ride Request The recommended fare to start from, the step buttons to move it, the Counter to type it.The tone comes with the screen's own words — "Recommended fare" — and those words are the Counter's description (aria-describedby), so a screen reader hears them too.
  • 2
    A Count The same stepper holds a count — passengers, seats: no currency, the step is one; the Counter still opens direct entry.
  • 3
    ✕ A Tone With No Words Red alone does not say what is wrong or what to do. Give the screen's reason in text next to the stepper — never the colour as the only signal.
  • 4
    ✕ A Value That Cannot Change Both side buttons dead and nothing to enter is not a stepper. Show the value as plain text.

Your fare

Recommended fare

✓ The fare on the ride request — the tone with the screen's words

Passengers

Including you

✓ A count — no currency, a step of one

Your fare

✕ A tone with no words — the colour alone says nothing

Ride price

✕ A value that cannot change — show plain text

Accessibility

Three buttons, each its own focus with its own announcement. The screen supplies the words: what the value is, and the value as it is spoken.

Three buttons, read in this order
At the limit — "+" disabled, the focus kept on it
Empty
Skeleton
  • 1
    Decrease "Decrease [Item], [value]" — a Button. "−" is read by its meaning, never as a symbol.Hint: TalkBack says "Double-tap to decrease" (from the onClick label); VoiceOver has none — the label says it.Web: aria-label + aria-describedby to a hidden span with the spoken value — never read on its own.
  • 2
    Counter "[Item], [value]" — a Button that opens direct entry, not read-only text. Hint: TalkBack "Double-tap to edit"; VoiceOver "Opens fare entry".A fare is read with its currency in words — "Fare, 120,000 Colombian pesos", never "C O L dollar 120 000"; a count — "Passengers, 3". The product's formatter supplies the spoken form.Direct entry is titled "[Item]"; on dismiss the focus returns to the Counter, whose name reads the committed value once.Web: the spoken text is a .visually-hidden span; the visible title, subtitle, spacer, chevron and icons are aria-hidden.
  • 3
    Increase "Increase [Item], [value]" — a Button; the hint as on Decrease.
  • 4
    At A Limit The step that lands on it says so — "130,000 Colombian pesos, maximum" (the product's word); a tap on the disabled button repeats it, so a press is never silent.TalkBack: enabled = false — "Disabled" after the role.VoiceOver: .notEnabled — "Dimmed"; the button stays in navigation.The Counter stays enabled — it still opens direct entry.Web — the trap: aria-disabled="true" + .squircle--disabled, the click only re-announces. Never disabled: "+" pressed up to the maximum would drop the focus to the page and the person loses their place.
  • 5
    Empty "[Item], Not set" — "Tap to enter" is a touch instruction and is not read. Both side buttons are disabled by default.
  • 6
    Skeleton Not announced, not focusable — TalkBack clearAndSetSemantics {}, VoiceOver accessibilityHidden(true) (.isLoading(true) alone does not hide it), web aria-hidden + inert, the buttons tabindex="-1" where inert is missing."Loading" is announced once by a separate role="status" node without aria-busy; aria-busy goes on the region being loaded.
  • After A Step The new value is announced right away; the focus stays on "−" or "+".TalkBack: the focused button's stateDescription follows the value and its change is read; the Counter is not a live region, so the value is read once. To verify on device.VoiceOver: an announcement notification; every element's accessibilityValue follows the value.Web: a polite live region inside the root — not the Counter; it holds only the latest value, so a held key does not queue steps. Try it on the live stepper in Animation.
  • Tones The colour is never announced and never the only signal. The screen's own message about the value is appended to the value — TalkBack in the Counter's stateDescription, VoiceOver in its accessibilityValue, web through aria-describedby (Usage, example 1).A step that changes the tone announces the new message with the new value.
  • Subtitle Its text joins the Counter's value, in words — "…, was 200,000 Colombian pesos". Its slot icons are decorative, unless the screen gives an icon that carries meaning a word.
  • Voice Control The name carries the visible number, so "Tap 120,000" works; iOS adds accessibilityInputLabels — "[Item]" and the visible value — to the Counter.
  • Keyboard And Focus Tab moves "−" → Counter → "+"; Enter and Space activate. No arrow keys — three buttons, not a spinbutton: the value is not edited in place.var(--sp-s2) — the focus ring in --text-and-icon-primary inside the button's edge, radius var(--sp-s20); :focus-visible only.
  • RTL The row mirrors; the order stays Decrease, Counter, Increase — in the DOM, in Tab and in reading. TalkBack follows the reading direction, so the button on the right — Decrease — comes first. The labels are semantic, not positional.
  • Text Scaling And Targets Heading/Heading 3 — the title, caps at 130 %.Heading/Heading 4 and Body/Compact Body — the placeholder and the subtitle, scale to 150 %, the DS maximum.var(--sp-100-s56) — the side buttons, s56 × s56 in every mode; the Counter, s56 high, grows to 72.8 at 130% — both above the 48 minimum on every platform.An over-long visible value ends in an ellipsis; the spoken value stays whole.
  • Reduced Motion The press overlay appears without a fade and the shimmer holds still — both zeroed at the token.
  • Contrast --text-and-icon-primary on --surface-on-white — ≈ 15.7:1 Day / ≈ 13.2:1 Night; above 11:1 over the Green and Orange glow; the focus ring the same.--text-and-icon-error — the Red value, ≈ 3.5:1 / ≈ 3.8:1, ≈ 3.1:1 / ≈ 3.5:1 over the strongest glow: large text, above the 3:1 floor.--text-and-icon-secondary — placeholder, subtitle, chevron, ≈ 4.6:1 / ≈ 6.1:1; the subtitle over the Red glow ≈ 4.1–4.4:1 Day, below 4.5:1 — the Figma colours stay, accepted (owner, 2026-11-12).--text-and-icon-disabled — ≈ 1.9:1, below the floor on purpose: a disabled control is exempt, and its state is announced, never shown by colour alone.