Number Field
Quantities, amounts and measurements where stepping helps: Base UI's NumberField, inside a Field. Codes that only look like numbers, such as postcodes and phone numbers, are Input; approximate values across a range use Slider.
import { NumberField } from '@fairgarden-private/design/components/NumberField'
The value uses tabular figures, so it never jitters while stepping. Show units on screen and in print: suffix follows the value with a no-break space, in the muted ink. At the minimum or maximum the matching step cell is disabled; out-of-range errors read "Enter 1–12", with an en dash.
Kinds and states
Kinds and states
stepper (default) joins square − and + cells to the input, divided by rules, with one --radius-1 outer radius; holding a cell takes the inverse pair. amount drops the steppers and sets the value in the display serif. readout puts circular − / + buttons around a large mono value, for dashboards. label with scrub makes the label a scrub area for fine pointers. Steppers suit ranges of up to about 20 steps.
import { Field, FieldError, FieldLabel } from '@fairgarden-private/design/components/Field'
import { NumberField } from '@fairgarden-private/design/components/NumberField'
import styles from './states.module.css'
/**
* The stepper (at its minimum, so − is disabled), the amount and the
* readout, then invalid, read-only and disabled fields.
*/
export function NumberFieldStates() {
return (
<div className={styles.stack}>
<Field>
<FieldLabel>Tickets</FieldLabel>
<NumberField min={1} max={12} defaultValue={1} suffix="adults" />
</Field>
<Field>
<FieldLabel>Donation</FieldLabel>
<NumberField kind="amount" prefix="$" suffix="USD" defaultValue={50} min={1} />
</Field>
<Field>
<NumberField label="Water" scrub kind="readout" defaultValue={2.5} step={0.5} suffix="litres" />
</Field>
<Field invalid>
<FieldLabel>Group Size</FieldLabel>
<NumberField min={1} max={12} defaultValue={14} allowOutOfRange />
<FieldError match>Enter 1–12.</FieldError>
</Field>
<Field>
<FieldLabel>Nights Booked</FieldLabel>
<NumberField readOnly defaultValue={3} />
</Field>
<Field disabled>
<FieldLabel>Guides</FieldLabel>
<NumberField defaultValue={2} />
</Field>
</div>
)
}
Primary and secondary
Primary and secondary
primary sets the group edge, dividers, glyphs and value. secondary is unused at rest and becomes the danger scale while the Field is invalid.
import { Field, FieldLabel } from '@fairgarden-private/design/components/Field'
import { NumberField } from '@fairgarden-private/design/components/NumberField'
import styles from './color.module.css'
/** `primary` recolors the group edge, dividers, glyphs, value and the held cell's inverse pair. */
export function NumberFieldColor() {
return (
<div className={styles.stack}>
<Field>
<FieldLabel>Primary Plum</FieldLabel>
<NumberField primary="plum" defaultValue={4} min={0} max={20} suffix="kg" />
</Field>
<Field>
<FieldLabel>Primary Slate</FieldLabel>
<NumberField primary="slate" kind="amount" prefix="€" defaultValue={25} />
</Field>
</div>
)
}
On grounds
On paper and forest
On forest the same roles resolve to the dark scale: light glyphs, the deep rule, and a light held cell.
paper
forest
import { Field, FieldLabel } from '@fairgarden-private/design/components/Field'
import { PresetGround } from '@/components/PresetGround'
import { NumberField } from '@fairgarden-private/design/components/NumberField'
import styles from './grounds.module.css'
const presets = ['paper', 'forest'] as const
/** The stepper on paper and forest: the held cell is a light fill with a dark glyph on the deep ground. */
export function NumberFieldGrounds() {
return (
<div className={styles.row}>
{presets.map((preset) => (
<PresetGround key={preset} preset={preset} className={styles.face}>
<p className={styles.name}>{preset}</p>
<Field>
<FieldLabel>Campers</FieldLabel>
<NumberField min={1} max={8} defaultValue={2} />
</Field>
<Field>
<FieldLabel>Distance</FieldLabel>
<NumberField kind="amount" defaultValue={12} suffix="km" />
</Field>
</PresetGround>
))}
</div>
)
}
API Reference
A Base UI NumberField inside a Field. At the minimum or maximum the
matching cell is disabled; out-of-range errors read “Enter 1–12″ with an
en dash [D32]. Use steppers for up to about 20 steps and tabular figures
throughout.
| Prop | Type | Description |
|---|---|---|
label | | A label rendered inside the root (a |
decrementLabel | | The decrement cell’s accessible name. Default “Decrease” (Base UI). |
incrementLabel | | The increment cell’s accessible name. Default “Increase” (Base UI). |
kind | |
|
placeholder | | The input’s placeholder, ending in “…". |
plate | | The group’s face becomes a nested |
prefix | | |
primary | | Primary Radix scale: group edge, dividers, glyphs and value. Never defaulted; omitted, it inherits the scope [D133]. |
scrub | | Makes |
secondary | | Secondary Radix scale. Unused at rest; the danger scale while invalid. |
suffix | | The unit, in --role-muted after the value with a no-break space (“kg”); on the readout, the caption under the value. Show units on screen and in print. |
className | | Class names for the root, added after the module’s own. |
numberField
type numberField = numberFieldNumberFieldProps
Props for NumberField: Base UI NumberField.Root props plus the kind, units and color axes.
type NumberFieldProps = (
{ kind?: 'stepper' | 'amount'; plate?: boolean } | { kind: 'readout'; plate?: false }
) & {
/** Class names for the root, added after the module's own. */
className?: string;
/** A prefix unit, in --role-muted ("$"). */
prefix?: React.ReactNode;
/**
* The unit, in --role-muted after the value with a no-break space
* ("kg"); on the readout, the caption under the value. Show units on
* screen and in print.
*/
suffix?: React.ReactNode;
/**
* A label rendered inside the root (a `FieldLabel`). Use it with `scrub`;
* otherwise a `FieldLabel` beside the NumberField in its `Field` works.
*/
label?: React.ReactNode;
/** Makes `label` a scrub area: drag it to change the value (fine pointers). */
scrub?: boolean;
/** The input's placeholder, ending in "…". */
placeholder?: string;
/** The decrement cell's accessible name. Default "Decrease" (Base UI). */
decrementLabel?: string;
/** The increment cell's accessible name. Default "Increase" (Base UI). */
incrementLabel?: string;
/**
* Primary Radix scale: group edge, dividers, glyphs and value. Never
* defaulted; omitted, it inherits the scope [D133].
*/
primary?:
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| null;
/** Secondary Radix scale. Unused at rest; the danger scale while invalid. */
secondary?:
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| 'amber'
| 'blue'
| 'cyan'
| 'grass'
| 'green'
| 'jade'
| 'lime'
| 'mint'
| 'orange'
| 'sky'
| 'teal'
| 'yellow'
| null;
}Specification: DESIGN-SYSTEM.md §10.4 (number field), with the stepper confirmed in D174.