QuantityStepper
A whole-number quantity, such as a product page's or a cart line's, in Input's box between a minus and a plus button. The input is a native number input; the buttons step it for the pointer and are disabled at the ends of the range.
Import
import { QuantityStepper } from '@nizivo/react/quantity-stepper';
// Once per app (Getting started), after
// @nizivo/tokens/tokens.css and @nizivo/react/css/base.css and @nizivo/react/css/input.css
import '@nizivo/react/css/quantity-stepper.css';Static or interactive
Client. It always ships its client leaf, which reads its Field through FieldScope, so a page with a QuantityStepper also loads FieldScope. The server HTML is a native number input that works before hydration, arrow keys included; the buttons work once the page has hydrated. How to choose.
In a client app (a Vite or React Router single-page app, or a 'use client' file) it is an ordinary React component: event handlers, refs and state work as usual. In a client app.
The buttons
The minus and plus buttons are for the pointer. They are out of the tab order, a press never moves
focus, and each is named for a screen reader's pointer modes: "Decrease quantity" and "Increase quantity"
(labels.decrement and labels.increment), with aria-controls pointing at the input. A keyboard
user steps with the arrow keys, which a screen reader announces as the input's value. Each button is
disabled at its end of the range, and both while the input is read-only or disabled.
A press steps the input as an arrow key would (stepUp() and stepDown(); an empty input starts at
min) and fires the native input and change events, so onChange, a form's own listeners and a
Field see an ordinary edit. decrementIcon and incrementIcon replace the internal minus and
plus glyphs.
Values
min is 0 and step is 1 unless set; max has no default. value and defaultValue are numbers,
and value={null} is an empty input. onValueChange(value, details) reports every change, typed or
pressed: the number, or null while the input is empty, with details.reason 'increment',
'decrement' or 'input'.
Quantities are whole numbers. A typed fraction fails the input's step, so inside a Field
messages.stepMismatch names the problem; a decimal amount, such as a price, is a NumberField's
job.
Width
The input is three characters wide, enough for 999. --nz-quantity-stepper-input-width sets
another width, for example 5ch.
Examples
Basic
A product page's quantity: 1 to 10, in a Field. The minus button is disabled at 1.
import type { JSX } from 'react';
import { Field } from '@nizivo/react/field';
import { QuantityStepper } from '@nizivo/react/quantity-stepper';
export function QuantityStepperExample(): JSX.Element {
return (
<Field name="quantity" label="Quantity" description="Up to 10 per order.">
<QuantityStepper min={1} max={10} defaultValue={1} />
</Field>
);
}
States
Sizes, a cart line at its stock limit, read-only, and disabled.
import type { JSX } from 'react';
import { Field } from '@nizivo/react/field';
import { QuantityStepper } from '@nizivo/react/quantity-stepper';
export function QuantityStepperStatesExample(): JSX.Element {
return (
<div style={{ display: 'grid', gap: 16, justifyItems: 'start' }}>
<QuantityStepper size="sm" aria-label="Small" defaultValue={2} />
<QuantityStepper aria-label="Medium" defaultValue={2} />
<QuantityStepper size="lg" aria-label="Large" defaultValue={2} />
<Field label="Quantity" description="Only 3 left in stock.">
<QuantityStepper min={1} max={3} defaultValue={3} />
</Field>
<QuantityStepper aria-label="Read-only" defaultValue={4} readOnly />
<Field label="Quantity" disabled>
<QuantityStepper defaultValue={1} />
</Field>
</div>
);
}
Props
Client component Renders data-slot="quantity-stepper" with the class nz-quantity-stepper. Props: QuantityStepperProps. Source: src/quantity-stepper/quantity-stepper.client.tsx.
| Name | Type | Default | Description |
|---|---|---|---|
decrementIcon | ReactNode | – | The minus button's mark. Defaults to the internal |
defaultValue | number | – | Uncontrolled starting value. |
incrementIcon | ReactNode | – | The plus button's mark. Defaults to the internal |
invalid | boolean | – | An error message is presented for this input. Inside a Field, the Field sets it. |
labels | Partial<QuantityStepperLabels> | – | Overrides the buttons' names, for example |
max | number | – | The highest quantity; the plus button is disabled there. No limit by default. |
min | number | 0 | The lowest quantity; the minus button is disabled there. |
onValueChange | (value: number | null, details: ChangeDetails<QuantityStepperValueReason>) => void | – | Called on every change, typed or pressed: the input's number, or |
render | RenderProp<QuantityStepperState> | – | Composition of the root, the bordered box. |
size | ControlSize"sm""md""lg" | 'md' | 32 / 40 / 48 px, as Input. |
step | number | 1 | A whole number. |
value | number | null | – | Controlled value; |
- Also takes every attribute of
<input>exceptchildren,defaultValue,max,min,prefix,size,step,type,value. (Omit<ComponentPropsWithRef<"input">, "type" | "size" | "prefix" | "children" | "min" | "max" | "step" | "value" | "defaultValue">)
render takes an element, or, in client code, a function of the props and this state (QuantityStepperState):
| Name | Type | Default | Description |
|---|---|---|---|
atMaxrequired | boolean | – | The value is at |
atMinrequired | boolean | – | The value is at |
disabledrequired | boolean | – | |
invalidrequired | boolean | – | An error message is presented (the Field's, or the |
readOnlyrequired | boolean | – | |
sizerequired | ControlSize"sm""md""lg" | – |
Other exports
QuantityStepperLabelstypeimport type { QuantityStepperLabels } from '@nizivo/react/quantity-stepper'QuantityStepper's own strings (§16.4): serialisable, English defaults.
Name Type Default Description decrementrequiredstring'Decrease quantity'The minus button's name.
incrementrequiredstring'Increase quantity'The plus button's name.
QuantityStepperPropstypeimport type { QuantityStepperProps } from '@nizivo/react/quantity-stepper'The props of
QuantityStepper, listed above.QuantityStepperStatetypeimport type { QuantityStepperState } from '@nizivo/react/quantity-stepper'The render state of
QuantityStepper, listed above.QuantityStepperValueReasontypeimport type { QuantityStepperValueReason } from '@nizivo/react/quantity-stepper''decrement'/'increment': a button was pressed.'input': typed, or an arrow key.type QuantityStepperValueReason = "decrement" | "increment" | "input"
Data attributes and slots
Style parts by class (.nz-<slot>) and state by data attribute. Attribute meanings come from contracts/vocabulary.json.
| Slot | Rendered by | Data attributes |
|---|---|---|
quantity-stepper.nz-quantity-stepper | QuantityStepperroot (takes render); interactive, focus: within |
|
quantity-stepper-control.nz-quantity-stepper-control | QuantityStepperinner element (CSS only) | none |
quantity-stepper-decrement.nz-quantity-stepper-decrement | QuantityStepperinner element (CSS only) | none |
quantity-stepper-icon.nz-quantity-stepper-icon | QuantityStepperinner element (CSS only) | none |
quantity-stepper-increment.nz-quantity-stepper-increment | QuantityStepperinner element (CSS only) | none |
CSS hooks
Set a hook on the component's class or any ancestor to restyle it without specificity games; leave it unset for the default shown.
| Hook | Default | Read as |
|---|---|---|
--nz-quantity-stepper-button-fg | --nz-icon-primary |
|
--nz-quantity-stepper-input-width | 3.5rem |
|
Layers: nz.components. Classes: .nz-input, .nz-quantity-stepper, .nz-quantity-stepper-button, .nz-quantity-stepper-control, .nz-quantity-stepper-icon.
Tokens this stylesheet reads (12)
--nz-bg-hover, --nz-bg-pressed, --nz-duration-fast, --nz-easing-standard, --nz-font-label-m, --nz-icon-primary, --nz-size-control-lg, --nz-size-control-md, --nz-size-control-sm, --nz-size-icon-sm, --nz-text-disabled, --nz-tracking-label-m
Accessibility
These parts are interactive. Each gets the focus outline and touch-action rule from @nizivo/react/css/base.css (the nz.base layer), so the floor survives a restyle:
quantity-stepper: draws the outline when the field inside it has focus.
Keyboard
| Keys | Action |
|---|---|
| Tab | Moves focus to the input. The buttons are skipped. |
| Arrow Up, Arrow Down | In the input, adds or takes away one step, within min and max (the browser's own number input). |
| Enter | In the input, submits its form. |
Source: a native <input type="number"> between two <button type="button" tabIndex={-1}> (quantity-stepper.client.tsx). The browser lane (test/browser/quantity-stepper.spec.tsx) checks the buttons by pointer, the arrow keys, Tab reaching only the input, the events a press fires, controlled use and a Field's message for a typed fraction.
Figma
Node ids from design/component-map.json, which nz gen writes from component.json. The links open the Nizivo Figma library, which may ask you to sign in.
- Canonical: Figma node
7367:377 - Ghost masters: none
ProgressRing
A circular progress indicator in three sizes, exposed as a progressbar or, for a measurement, as a meter.
RadioGroup
One choice from a short list of native radios, in a fieldset with a legend. The shell is server-rendered; one small client part passes the group's name and value to each radio.