Content

Quantity Picker
v0.2.4
Documentation Under Review

An accessible increment/decrement control built with CVA variants, four size options, danger state, and full keyboard support.

Prefer AI-assisted development?

This documentation is available via the Catalyst MCP Server. Learn how to connect →

Catalyst Quantity Picker is a compound component for selecting numeric values — increment, decrement, min/max constraints, danger thresholds, and four size variants included out of the box.

Preview

Installation

npm install @pmi/catalyst-quantity-picker
import {
  QuantityPickerRoot,
  QuantityPickerTrigger,
  QuantityPickerValue,
} from '@pmi/catalyst-quantity-picker';

Sizes

Four sizes are available via the size prop on QuantityPickerRoot.

Preview

Common Patterns

Danger State

Use danger on QuantityPickerRoot to highlight critical quantities or warning thresholds — the state is propagated to the triggers and value via data-danger.

Color alone does not convey the danger state to screen reader users or users who cannot perceive red (WCAG 2.2 SC 1.4.1, 1.3.1). Always pair it with a visible HelperText message and, while the message is shown, point aria-describedby on QuantityPickerValue at its id. If the danger state can change at runtime, render the message inside an always-present role="status" container so it is announced when it appears.

Preview
Error: Limit reached.

Disabled State

Use aria-disabled on a trigger when the value has reached its boundary (see ARIA), or pass disabled to both triggers to block all interaction.

Preview

API Reference

Quantity Picker is a compound component. The sections below cover the props specific to each part.

QuantityPickerRoot

The flex container that provides the size context to all child parts via data-size.

Prop

Type

QuantityPickerTrigger

The increment or decrement button. Always provide a descriptive aria-label — the trigger renders an icon only.

Prop

Type

QuantityPickerValue

Displays the current numeric value between the two triggers. It renders an <input role="spinbutton"> by default; use asChild to render a custom element.

Prop

Type

Accessibility

Quantity Picker is designed to meet WCAG 2.2 AA standards.

Keyboard Interactions

KeyAction
TabMoves focus to the value input (the Default example removes the buttons from the Tab order)
ArrowUpIncreases the value, up to max
ArrowDownDecreases the value, down to min
Home / EndSets the value to min or max
Shift + TabMoves focus in reverse
EnterActivates the focused trigger button
SpaceActivates the focused trigger button

ARIA

  • Provide a visible label identifying what the quantity represents (WCAG 2.2 SC 3.3.2) and associate it with the picker via role="group" + aria-labelledby on QuantityPickerRoot.
  • Each QuantityPickerTrigger is a <button> that contains an icon only — give it a contextual accessible name that includes the visible label (e.g. "Adults decrease quantity", "Adults increase quantity") (WCAG 2.2 SC 4.1.2). The examples use aria-labelledby combining the label id with a visually hidden (sr-only) action text inside the button, so the name stays in sync with the visible label.
  • When a single trigger reaches its boundary, set aria-disabled="true" on it rather than disabled: the button keeps focus, stays in the Tab order, is announced as unavailable (WCAG 2.2 SC 4.1.2), and QuantityPickerTrigger ignores its clicks. Use disabled only when both triggers must block all interaction.
  • QuantityPickerValue renders an <input role="spinbutton"> by default, following the APG quantity spinbutton. Pass value, min, max and onValueChange: it sets aria-valuenow, aria-valuemin and aria-valuemax, and sets aria-invalid while the value is outside the range. Associate the visible label with <label htmlFor> (WCAG 2.2 SC 4.1.2, 1.3.1).
  • Typing keeps digits only, an empty value resets to min on blur, and Arrow Up/Down and Home/End are handled for you; each change is reported through onValueChange. Keyboard changes on the focused input are announced natively through aria-valuenow.
  • When the typed value is outside the range, show a visible HelperText message inside an always-present role="status" container and, only while the message is shown, point aria-describedby on the input at it (WCAG 2.2 SC 3.3.1). danger on QuantityPickerRoot only changes the styling; the visible message is what keeps the error from relying on color alone (SC 1.4.1).
  • Value changes made with the buttons are not announced by the spinbutton, because focus stays on the button. Mirror the new value into a visually hidden <output> (an implicit polite status region) and empty it after about 2 seconds (WCAG 2.2 SC 4.1.3).
  • With asChild, the value has no spinbutton semantics. Add aria-live="polite" to the child if its changes must be announced.
  • Trigger borders and the focus indicator meet the WCAG 2.2 AA minimum contrast of 3:1 for non-text elements (SC 1.4.11). Disabled triggers are exempt from this requirement.
  • The danger state must not rely on color alone — pair it with a visible message (see Danger State) (SC 1.4.1).

Choosing the Right Component

Use Quantity Picker when users need to select a bounded integer value one step at a time. Reach for a different primitive when:

If you need…Use instead
A continuous range with a draggable handleSlider
Free-form numeric text entryText Field
A multi-step workflow progress controlProgress Stepper

On this page