QuantityPicker
A stepper with clamping, per-button loading, a remove mode and a width that does not move.
A quantity stepper driven by seed-design's headless engine, so the
clamping, the step arithmetic, the per-button loading state and the removable mode are theirs
by construction.
The look is Attio's number field (Deals > New Deal > Deal value): a 32px, 9px-radius field on
the same ring Input uses, the value left-aligned, and a vertical pair of 12x10 chevron buttons
at the right edge, increase above decrease. size is kept for compatibility and ignored —
Attio's field has one height.
Installation
npm install @crisp-ui-kit/crispProps
Prop
Type
The width does not move
The value is a grid with a hidden copy of max stacked in the same cell as the number. Without
it the control gets wider at 10 and wider again at 100, and a column of them stops lining up.
The hidden copy runs through getValueText too, so a formatted value reserves the width of its
formatting, and it is aria-hidden so the value is announced once rather than twice.
Direction
dirmust be passed, not inherited. The engine writesdiron the root from its own prop, which defaults to"ltr", so a picker inside an RTL app renders left-to-right unless you passdir="rtl". Here it also decides the ORDER of the value and the stepper — the engine reverses its own children rather than relying on CSS — so an inherited direction would leave the stepper on the wrong side.
Usage
import { QuantityPicker } from "@crisp-ui-kit/crisp";
const [qty, setQty] = React.useState(1);
<QuantityPicker min={1} max={99} value={qty} onValueChange={setQty} />;Pass name to render a hidden input, so the value submits with a surrounding form.
Examples
Sizes (all render 32px)
Remove mode
With removable, pressing minus at the minimum calls onRemove instead of doing nothing, and
the decrease chevron becomes a bin so the button says what it now does. This is the cart-line shape: the
last decrement takes the line out.
removeAriaLabel is required when removable is set — the type enforces it, because that
label is the only accessible name the button has in this mode.
Loading
loading can be true for both buttons or { increment } / { decrement } for one. The
loading button shows a Spinner, reports aria-busy and refuses to step, while
its neighbour keeps working.