NumberField
A number, typed — with the two ends of its range and a pair of buttons that walk it.
Overview
A number, typed — with the two ends of its range and a pair of buttons that walk it.
First example
<NumberField min={1} max={99} defaultValue={1} onValueChange={setQuantity}><NumberField.Label>Quantité</NumberField.Label><FieldGroup><NumberField.Decrement accessibilityLabel="Retirer un article" /><NumberField.Field /><NumberField.Increment accessibilityLabel="Ajouter un article" /></FieldGroup></NumberField>
Anatomy
<NumberField>
<NumberField.Decrement />
<NumberField.Field />
<NumberField.Increment />
</NumberField>
Usage
<NumberField min={1} max={99} defaultValue={1} onValueChange={setQuantity}>
<NumberField.Label>Quantité</NumberField.Label>
<FieldGroup>
<NumberField.Decrement accessibilityLabel="Retirer un article" />
<NumberField.Field />
<NumberField.Increment accessibilityLabel="Ajouter un article" />
</FieldGroup>
</NumberField>
Root props
NumberFieldProps
The node's inherited React Native props apply too, and so do its style props — padding, margin, width and the rest.
| Prop | Type | Required | Description |
|---|---|---|---|
| value | number | null | undefined | No | The number shown. Present means controlled — the field follows it, and `onValueChange` is how it asks for a new one. `null` is an empty field. |
| defaultValue | number | null | undefined | No | Where an uncontrolled field starts. |
| onValueChange | ((value: number | null) => void) | undefined | No | Every edit, with the number the field now holds — or `null` while what is in the box is not one yet. **While the reader is typing it is the raw number**, bounds and all: `min={10}` and a reader on their way to `15` types a `1` first, and clamping that to ten would take the keyboard away from them. The bounds are applied when they leave the field, and on every press of a stepper. |
| min | number | undefined | No | The floor. Unset, the field runs down as far as the reader takes it. |
| max | number | undefined | No | The ceiling. |
| step | number | undefined | No | How far one press of a stepper moves the value. |
| formatOptions | NumberFormatOptions | undefined | No | How the value is written when the field is **not** being typed into — a currency, a unit, a fixed number of decimals. `Intl.NumberFormat`'s own options, unchanged. The moment the caret enters the box the value is rewritten plainly, without grouping, because a reader editing `1,234.50 €` should not have to type the comma or the euro sign back in. |
| locale | string | undefined | No | Which characters group and separate the number, in and out. |
| children | ReactNode | No | — |
Slots
NumberFieldDecrementProps
The node's inherited React Native props apply too, and so do its style props — padding, margin, width and the rest.
| Prop | Type | Required | Description |
|---|---|---|---|
| accessibilityLabel | string | undefined | No | What a screen reader says. There is no default, for the reason the close button's has none: a plus sign is not text, and the label beside it names the quantity rather than the action. |
| children | ReactNode | No | Replaces the drawn mark — an icon of your own. |
NumberFieldFieldProps
The node's inherited React Native props apply too, and so do its style props — padding, margin, width and the rest.
NumberFieldIncrementProps
The node's inherited React Native props apply too, and so do its style props — padding, margin, width and the rest.
| Prop | Type | Required | Description |
|---|---|---|---|
| accessibilityLabel | string | undefined | No | What a screen reader says. There is no default, for the reason the close button's has none: a plus sign is not text, and the label beside it names the quantity rather than the action. |
| children | ReactNode | No | Replaces the drawn mark — an icon of your own. |
Variants, sizes and colour
Accessibility
Migration from legacy
Implementation notes
It is a `TextField`
The root is the TextField's root, unchanged: the same recipe, the same four variants, the
same size, radius, color, labelPlacement, isInvalid and isDisabled.
NumberField.Label, .Description and .Error are the TextField's slots — the same
components, not wrappers. Only the field differs, by reading a number out of what is typed
into it. The MaskField's arrangement exactly.
The value is a number, and the box is a string
Two representations, and the caret is what swaps them.
| where the caret is | what the box holds |
|---|---|
| outside | the value written by Intl, formatOptions's way |
| inside | the same number written plainly, no grouping |
Out of the field, formatOptions is Intl.NumberFormat's own options, unchanged — a
currency, a unit, a fixed number of decimals cost nothing here.
Into it, everything that is not a digit, a sign or the decimal mark is dropped rather
than refused. The value the caret lands in is grouped, and rejecting its separators would
make the first keystroke on an existing value clear the box. A full stop passes as the
decimal mark wherever the locale is not using it to group, so 1.5 reads as one and a half
in fr-FR and 1.234 still reads as a thousand-odd in de-DE.
On the way in the value is rewritten plainly, so nobody has to type a euro sign back in.
null, never NaN: "not a number yet" is the state a half-typed field is in for as long
as it takes to type a minus sign, and NaN is a value that propagates instead of a state a
caller can test.
The bounds land when the reader leaves, not while they type
min={10}, and a reader on their way to 15 types a 1 first. Clamping that would take
the keyboard away from them.
So until the field is left, onValueChange reports what is actually in the box. The clamp
falls on blur, and on every press of a stepper.
The steppers
They are FieldGroup decorators, like TimeField.Period — that is the thing that lays a
control over a field and measures it, so the box stays the TextInput itself and no
wrapper borrows its border, its fill and its radius.
Decrement takes the leading edge and Increment the trailing one. The value sits
between them, which is the only arrangement in which the two read as one control rather
than as two marks that happen to be nearby. start and end, never left and right
(R13).
A button goes flat when the value has nowhere left to go, and stops taking presses there. That is asked of the result rather than of the bound: a value half a step short of the ceiling can still reach it, and a button dead at that point strands the reader.
With no children each draws its own mark — one bar, or two a quarter turn apart — so the
field works in a project that has installed no icon set. Pass an <Icon> to replace it.
From an empty field, a step starts at zero
Which is what puts the first press on min when the range starts above it: min={5} and a
step of one gives 5, not 6.
The keyboard is a guess, and a guess you can override
A fractional step, or a formatOptions that asks for decimals, opens the decimal pad;
anything else opens the number pad. Unlike the MaskField's, NumberField.Field does
not claim keyboardType — a field that has to take a minus sign needs a keyboard with
one on it, and that is the caller's call.
Related
NumberStepper— the same pair without a field to type into.Slider— a quantity chosen on a track.TextField— the root, the label, the hint and the error.