<!-- Generated by scripts/gen-docs.mts from the JSDoc in registry/. Do not edit; run `pnpm run docs`. -->

# NumberField

A labeled number input on the Field contract: pass `label`, and `error` from your server action or client check. It parses what people type in their locale, shows the value with `format` (currency, percent, units), steps with its buttons and the arrow keys, and changes the value when the label is dragged sideways with a mouse. The steppers take taps over 44px on touch screens. The form submits the plain number.

```tsx
import { NumberField } from "@/components/ouro/number-field/number-field";
```

## Example

```tsx
<NumberField label="Servings" name="servings" defaultValue={4} min={1} max={24} />
<NumberField label="Price" name="price" min={0} format={{ style: "currency", currency: "USD" }} step={0.5} />
```

## Usage

It picks the phone keyboard itself: the keypad with a decimal separator when `min` is 0 or more and the field takes fractions (a fractional `step`, or a `format` with fraction digits, such as a currency), the digits-only keypad when `min` is 0 or more and it takes whole numbers, and the full keyboard when it takes negatives, since iOS's keypads have no minus key.

## Use instead when

- Use `TextField` with `inputMode="numeric"` instead for digit codes such as a PIN or a ZIP code, and `Slider` when an approximate value in a range is enough.

## Props

Type: `NumberFieldProps`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` |  | Layout only: margins, width, grid placement (`mt-4`, `col-span-2`). Appearance classes warn in development; style fields in your theme. |
| `decrementLabel` | `string` | `"Decrease"` | The name of the button that lowers the value, as visually hidden text and not an `aria-label`, so a test's label lookup for the field finds the field alone. Translate it with your app. |
| `defaultValue` | `number \| null` |  | The starting value, when the field keeps its own; `null` is empty. |
| `description` | `ReactNode` |  | Supporting text under the label, read after the label through `aria-describedby`. |
| `disabled` | `boolean` |  | Disables the field and its steppers, and fades it. |
| `error` | `ReadonlyErrorMessages` |  | What is wrong, from your server action or client check. The message (or several, as a list) shows under the field, which gets `aria-invalid`. A message is text or a React node (one with a link); an empty string or array means valid. |
| `format` | `Intl.NumberFormatOptions` |  | How the number shows, as `Intl.NumberFormat` options: `{ style: "currency", currency: "USD" }` shows `$4.50`, `{ style: "percent" }` shows `15%`, `{ maximumFractionDigits: 2 }` rounds. The form still submits the plain number. |
| `hideLabel` | `boolean` |  | Hides the label visually; screen readers still read it as the field's name. |
| `incrementLabel` | `string` | `"Increase"` | The name of the button that raises the value, as visually hidden text and not an `aria-label`. Translate it with your app. |
| `label` (required) | `string` |  | What the number is, shown above the field and read as its name. Required: every control needs a name. To hide it visually, keep it and add `hideLabel`. Drag it sideways with a mouse to change the value. |
| `largeStep` | `number` | `10` | The step for Page Up, Page Down and Shift with an arrow key. |
| `locale` | `Intl.LocalesArgument` | the browser's | The locale for `format` and for parsing what people type. |
| `max` | `number` |  | The highest value; the steppers stop there. |
| `min` | `number` |  | The lowest value; the steppers stop there. A minimum of 0 or more also gives phones a number keypad: iOS's keypads have no minus key, so a field that takes negatives (no `min`, or a negative one) gets the full keyboard. |
| `name` | `string` |  | The form field name the number submits under, as a plain number (`4.5`, not `$4.50`). |
| `onValueChange` | `((value: number \| null) => void)` |  | Called with the new number as it changes, or `null` when the field is emptied. |
| `onValueCommitted` | `((value: number \| null) => void)` |  | Called when the value settles: on blur after typing, when a stepper or scrub is released, and with each key press. Use it to save. |
| `placeholder` | `string` |  | An example shown while the field is empty. It is not a label; pass `label`. |
| `readOnly` | `boolean` |  | People can read and copy the value but not change it. |
| `ref` | `Ref<HTMLInputElement>` |  | The input element. |
| `required` | `boolean` |  | The field must be filled: it gets `aria-required`, and the label a marker. |
| `step` | `number` | `1` | The amount the steppers, the arrow keys and a scrub change the value by. |
| `value` | `number \| null` |  | The value, when you control it; `null` is empty. |

## Don't use

These are not props. Passing one fails the type check with the fix in the error, and in development a console message names the fix.

- `currency`: Not a prop. Currency is a format option: `<NumberField format={{ style: "currency", currency: "USD" }}>`.
- `errorMessage`: Not a prop. Ouro UI uses `error`: `<NumberField error="Required.">`.
- `formatOptions`: Not a prop. Ouro UI uses `format`: `<NumberField format={{ style: "percent" }}>`.
- `helperText`: Not a prop. Ouro UI uses `description`: `<NumberField description="...">`.
- `invalid`: Not a prop. `error` marks the field invalid: `<NumberField error="What is wrong">`.
- `isDisabled`: Not a prop. Ouro UI uses `disabled`: `<NumberField disabled>`.
- `isInvalid`: Not a prop. `error` marks the field invalid: `<NumberField error="What is wrong">`.
- `isReadOnly`: Not a prop. Ouro UI uses `readOnly`: `<NumberField readOnly>`.
- `isRequired`: Not a prop. Ouro UI uses `required`: `<NumberField required>`.
- `maxValue`: Not a prop. Ouro UI uses `max`: `<NumberField max={10}>`.
- `minValue`: Not a prop. Ouro UI uses `min`: `<NumberField min={1}>`.
- `onChange`: Not a prop. NumberField reports the number itself: `<NumberField onValueChange={(value) => …}>`.
- `precision`: Not a prop. Rounding is a format option: `<NumberField format={{ maximumFractionDigits: 2 }}>`.
- `type`: Not a prop. NumberField is always a number field; for text use `TextField`.

## `className` is for layout only

Pass margins, width and grid placement (`mt-4`, `w-full`, `col-span-2`). Your classes always win over Ouro UI's: NumberField merges `className` last with `cn` (tailwind-merge), so a class that conflicts with its own replaces it.[^layout-classes]

Appearance comes from props, not classes. In development, a class that sets appearance logs one warning per component instance, naming the prop to use instead. A class sets appearance when, after its variants (`hover:`, `sm:`) and any `!` are removed, it starts with `bg-`, `text-`, `border-`, `rounded-`, `shadow-`, `font-`, `ring-`, `from-`, `via-`, `to-`, `fill-` or `stroke-`, or is `border`, `rounded`, `shadow` or `ring`, or has a `dark:` variant. Text wrapping, overflow and table layout classes (`text-wrap`, `text-ellipsis`, `border-collapse`) are layout and do not warn.

Alignment classes (`text-center`) warn too: set alignment on a parent element.

## Types

### `ErrorMessage`

One error message: text, or a React element for a message that carries a link or other markup.

```ts
type ErrorMessage = string | ReactElement;
```

### `ReadonlyErrorMessages`

A read-only message view accepted by fields and Form; mutable input arrays remain valid.

```ts
type ReadonlyErrorMessages = ErrorMessage | readonly ErrorMessage[];
```

[^layout-classes]: test:registry/ouro/components/ouro/number-field/number-field.test.tsx::NumberField > lets app layout classes win on the root verify:testExists(registry/ouro/components/ouro/number-field/number-field.test.tsx::NumberField > lets app layout classes win on the root)
