---
title: Number Field
description: Number fields collect numeric input with increment/decrement steppers, locale formatting, and min/max constraints.
---

import { meta as NumberFieldMeta, Default } from '@/content/stories/number-field';
import Canvas from '@/components/component-docs/canvas';
import Demo from '@/components/component-docs/demo';

export const config = {
	dependencies: {
		components: [Canvas, Demo],
		scripts: [
			'../../../components/component-docs/demo.script.tsx',
			'../../../components/component-docs/canvas.script.tsx',
			'../../../components/component-docs/controls.script.tsx',
		],
	},
};


# Number Field

<p class="docs-lede">Number fields collect numeric input with increment/decrement steppers, locale formatting, and min/max constraints.</p>

## Try it

<Demo of={Default} meta={NumberFieldMeta} />

## Usage

Set `minValue`, `maxValue`, and `step` to constrain input. Compose with `RuiNumberFieldGroup` and stepper buttons for the full pattern.

```tsx
import {
  RuiNumberField,
  RuiNumberFieldGroup,
  RuiNumberFieldInput,
  RuiNumberFieldDecrementButton,
  RuiNumberFieldIncrementButton,
  RuiNumberFieldSteppers,
} from '@ecopages/radiant-ui/number-field';

<RuiNumberField value={3} minValue={0} maxValue={10} step={1}>
  <RuiNumberFieldGroup>
    <RuiNumberFieldInput />
    <RuiNumberFieldSteppers>
      <RuiNumberFieldDecrementButton />
      <RuiNumberFieldIncrementButton />
    </RuiNumberFieldSteppers>
  </RuiNumberFieldGroup>
</RuiNumberField>
```

## Custom markup

`<rui-number-field>` coordinates any light-DOM tree that matches its query contract. The `RuiNumberField` helpers stamp these targets; they are not required.

```tsx
import '@ecopages/radiant-ui/number-field';

<rui-number-field name="quantity" value={3} min-value={0} max-value={10}>
  <div class="rui-number-field__group" data-number-field-group>
    <input type="text" data-number-field-input data-rui-control data-rui-control-type="number" class="rui-number-field__input" />
    <button type="button" data-number-field-action="decrement" class="rui-number-field__stepper">−</button>
    <button type="button" data-number-field-action="increment" class="rui-number-field__stepper">+</button>
  </div>
</rui-number-field>
```

BEM classes are presentation-only. The host owns formatted input `value` and spinbutton `aria-*`. Set `name` on the host to submit the raw number with a native form (not the formatted display string).

## Step and commit behavior

<p>`commitBehavior` controls whether out-of-range typed values snap to bounds or reject on blur.</p>

## Disable scroll wheel

<p>Set `wheelDisabled` to prevent accidental value changes when users scroll over a focused number field.</p>

## Theming

Number field surfaces map to **semantic surface roles**, never Tailwind palette steps:

| Part | CSS roles |
| --- | --- |
| Group (`.rui-number-field__group`) | `border`, `background`, `rounded-control` |
| Input (`.rui-number-field__input`) | `on-background` text |
| Steppers (`.rui-number-field__steppers`) | `border` divider |
| Stepper (`.rui-number-field__stepper`) | `on-surface`, `surface` hover, `surface-container-low` active |
| Focus / invalid | `focus-ring` / `error` |

Geometry shares control tokens (`--size-control-sm`, `--space-control-*`, `--radius-control`, `--text-control`) with `RuiInput` and `RuiButton` so form rows align. Override at the theme layer, not in component CSS.

## API

`RuiNumberField` is a custom element (`<rui-number-field>`). Standalone it renders the default input + stepper row; compose the view helpers for full control.

### Attributes

| Attribute | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `number` | | Current value. |
| `default-value` | `number` | | Initial value when uncontrolled. |
| `min-value` | `number` | `-Infinity` | Lower bound; steppers and arrow keys snap to it. |
| `max-value` | `number` | `Infinity` | Upper bound; steppers and arrow keys snap to it. |
| `step` | `number` | `1` | Increment/decrement and snap interval. |
| `disabled` | `boolean` | `false` | Disables input and steppers. |
| `read-only` | `boolean` | `false` | Blocks editing; the value still displays formatted. |
| `label` | `string` | `''` | Accessible name when there is no visible `RuiLabel`. |
| `name` | `string` | `''` | Form field name on this host. Submits the raw number, not the formatted display. |
| `locale` | `string` | `''` | BCP 47 locale tag, or comma-separated fallback list. |
| `format-options` | `string` | `''` | JSON-serialized `Intl.NumberFormatOptions`. |
| `commit-behavior` | `snap` · `validate` | `snap` | Blur behavior after editing. |
| `increment-aria-label` | `string` | `Increment` | Accessible name for the increment stepper. |
| `decrement-aria-label` | `string` | `Decrement` | Accessible name for the decrement stepper. |
| `wheel-disabled` | `boolean` | `false` | Disables scroll-wheel value changes. |

### Light-DOM contract

| Target | Required | Host writes | Author owns |
| --- | --- | --- | --- |
| `[data-number-field-input]` | yes | `id`, `role`, `inputmode`, `data-disabled`, `data-readonly`, `aria-valuemin`, `aria-valuemax`, `aria-valuenow`, formatted `value` | text input; stamp `data-rui-control` for field wiring |
| `[data-number-field-action="decrement"]` | no | `disabled`, `aria-label` | decrement button |
| `[data-number-field-action="increment"]` | no | `disabled`, `aria-label` | increment button |
| `[data-number-field-group]` | no | — | presentation wrapper only |

Nested hosts: none.

### Events

| Event | Detail | Description |
| --- | --- | --- |
| `rui-change` | `{ value: number }` | Emitted when a value is committed (blur, stepper, or keyboard). |

### View helpers

| Component | Target stamped | Notes |
| --- | --- | --- |
| `RuiNumberField` | default group when `children` omitted | Includes input and steppers. |
| `RuiNumberFieldGroup` | `[data-number-field-group]` | Presentation only; not queried. |
| `RuiNumberFieldInput` | `[data-number-field-input]` | Always stamps `data-rui-control`. |
| `RuiNumberFieldIncrementButton` | `[data-number-field-action="increment"]` | Default label "Increment". |
| `RuiNumberFieldDecrementButton` | `[data-number-field-action="decrement"]` | Default label "Decrement". |
| `RuiNumberFieldSteppers` | stepper column wrapper | Presentation only. |

### CSS classes

Public BEM classes (documented via `@cssclass`):

| Class | Description |
| --- | --- |
| `.rui-number-field` | Root field wrapper. |
| `.rui-number-field__group` | Control-height bordered row. |
| `.rui-number-field__input` | Borderless text input. |
| `.rui-number-field__steppers` | Trailing stepper column. |
| `.rui-number-field__stepper` | Icon button cell (`+` / `−`). |

### Theme roles

| Part | CSS variables consumed |
| --- | --- |
| Group | `--border`, `--background`, `--radius-control`, `--duration-normal` |
| Input | `--on-background`, `--space-control-x`, `--space-control-y`, `--text-control` |
| Steppers | `--size-control-sm` |
| Stepper states | `--on-surface`, `--surface`, `--surface-container-low` |
| Focus / invalid | `--focus-ring` / `--error` |

## Accessibility

- Stepper buttons need accessible names; "Increment" and "Decrement" are provided by default.
- Announce min and max constraints in helper text when the valid range is not obvious.
- Disabled fields should explain why editing is unavailable.
