Primitives
NumberField
A number in a text field, with steps and optional bounds
1–99
Must be at least 1.
Usage
import { NumberField, Label, Stack } from "@gpuixui/primitives";
import { useState } from "react";
const [copies, setCopies] = useState(1);
<Stack direction="column" gap="xs">
<Label description="1–99">Copies</Label>
<NumberField label="Copies" value={copies} onValueChange={setCopies} min={1} max={99} />
</Stack>;API
| Prop | Type | Default | Consequence |
|---|---|---|---|
value | number | — | controlled value |
defaultValue | number | none | start value; the field shows empty without one |
onValueChange | (value: number) => void | — | the clamped number, after a valid edit or step |
onValueCommit | (value: number) => void | — | the value a gesture ended on: once per stepper press, once per scrub release, once per arrow or page key that moved it, and once when a typed edit ends on a readable value |
onChange | (value: number) => void | — | deprecated alias for onValueChange |
min / max | number | none | bound the value; absent means unbounded, and an inverted pair is the range it means |
step | number | 1 | arrow step |
precision | number | the value's own decimals | decimals painted on commit; it rounds and pads the text, and the value keeps its own. A stored value that is not a number (a record's null, text) is no precision at all, so the field paints the value's own decimals rather than rounding it to a whole number |
size | "sm" | "md" | "lg" | "md" | field height and padding |
disabled | boolean | false | read-only, out of the tab order, inert |
invalid | boolean | false | destructive border |
label | string | none | accessible name |
onSubmit | (event: EventPayload) => void | — | Enter: the draft commits first, then this reports |
description | string | none | announced help on the input (aria-description); a caller's permanent help wins over the hint |
placeholder | string | none | empty-field hint, painted in the placeholder role and announced as the field's description while the box is empty (the host's own hint is a fixed grey no theme reaches) |
autoFocus | boolean | false | focus on mount |
formatValue | (value: number) => string | fixed decimals | the display text, display-only; pair it with parseValue. An answer that says nothing ("" or only spaces — a locale table's missing row) is no answer, so the field paints the value it holds instead of an empty box |
parseValue | (text: string) => Option<number> | none | reads the text formatValue painted; tried first, then decimal |
className | string | — | a Tailwind class string, joined after the recipe so it wins conflicts |
style | StateStyle<FieldState> | — | resolved against the live state, merged last |
children | ReactNode | input + steppers | the parts to draw |
onClick / onKeyDown / onKeyUp / onFocus / onBlur | host handlers | none | composed with the field's own; a disabled field keeps them observable |
ref / aria-id / onLayout | host props | none | the native <input> is the addressed element |
testId | string | — | stable locator on the field's chrome |
NumberFieldScrubHandle
The label an inspector drags. Mount it beside a NumberField and give both the
same value and bounds: a horizontal drag moves one step per 4px, a held Shift
multiplies that by 10, and min/max clamp it. A press that does not travel is a
click, so the field beside it stays a text field.
| Prop | Type | Default | Consequence |
|---|---|---|---|
value | number | — | the value a drag starts from |
min / max | number | none | the bounds a drag clamps to |
step | number | 1 | the step one scrub count moves |
onValueChange | (value: number) => void | — | every frame of a drag, already clamped |
onValueCommit | (value: number) => void | — | the value the whole drag ended on |
disabled | boolean | false | takes no gesture, and paints no resize cursor |
children | ReactNode | — | the visible words a reader drags |
style / className / testId | host props | — | merged after the handle's own |
Edit this page on GitHubpackages/primitives/docs/number-field.mdx