# Number Field (/docs/number-field)





## API Reference [#api-reference]

<BaseUIReference href="https://base-ui.com/react/components/number-field" />

## Basic [#basic]

<Preview cssProperties="numberFieldPlaygroundCssProperties">
  <NumberFieldExample />

  <Preview.Code>
    {`
          import {
            Field,
            FieldLabel,
            NumberField,
          } from "moduix";
          import { useId } from "react";

          export function NumberFieldDemo() {
            const id = useId();

            return (
              <Field>
                <FieldLabel htmlFor={id}>Amount</FieldLabel>
                <NumberField id={id} defaultValue={100} />
              </Field>
            );
          }
        `}
  </Preview.Code>

  <Preview.CSSProperties>
    {(context) => <NumberFieldCssPropertiesPanel {...context} />}
  </Preview.CSSProperties>

  <Preview.CSSPlayground>
    {(context) => <NumberFieldCssPlaygroundPanel {...context} />}
  </Preview.CSSPlayground>
</Preview>

## Anatomy [#anatomy]

`NumberField` keeps numeric state on the root and renders the standard decrement, input, and
increment group automatically. Add `NumberFieldScrubArea` only when the label or another visible
part should support drag-to-change behavior.

```text
Field
├─ FieldLabel
└─ NumberField
   ├─ NumberFieldScrubArea
   │  ├─ scrub label
   │  └─ cursor (internal)
   └─ NumberFieldGroup
      ├─ NumberFieldDecrement
      ├─ NumberFieldInput
      └─ NumberFieldIncrement
```

```tsx
<Field>
  <FieldLabel htmlFor={id}>Amount</FieldLabel>
  <NumberField id={id} defaultValue={100} />
</Field>
```

| Part                   | Role                                                                                                        |
| ---------------------- | ----------------------------------------------------------------------------------------------------------- |
| `NumberField`          | Root state machine. Controls value, formatting, validation, min/max, step, and change handlers.             |
| `NumberFieldScrubArea` | Optional drag area for changing the value. It renders the scrub cursor internally by default.               |
| `NumberFieldGroup`     | Visual wrapper for the stepper buttons and input. Rendered automatically unless `withGroup={false}` is set. |
| `NumberFieldDecrement` | Button that decreases the value. It renders the default minus icon, or your custom icon as children.        |
| `NumberFieldInput`     | Editable numeric input. It receives formatted text and invalid, disabled, and readonly state attributes.    |
| `NumberFieldIncrement` | Button that increases the value. It renders the default plus icon, or your custom icon as children.         |

`NumberField` does not use portal-like service layers such as `portal`, `backdrop`, or `viewport`.
The standard group is rendered internally by default and can be styled with `classNames.group`,
`classNames.decrement`, `classNames.input`, and `classNames.increment`. The scrub cursor is an
internal service slot of `NumberFieldScrubArea`, so it is customized through `cursor`,
`withCursor`, and `classNames.cursor` instead of being added to the public composition.

## Composition [#composition]

Use `NumberField` for root behavior props such as `value`, `defaultValue`, `onValueChange`, `min`,
`max`, `step`, `required`, `disabled`, `readOnly`, and `format`.

Use the default group for standard number inputs. Set `decrementLabel` and `incrementLabel` to
localize the internal button labels, or set `withGroup={false}` and compose `NumberFieldGroup`,
`NumberFieldDecrement`, `NumberFieldInput`, and `NumberFieldIncrement` manually when the layout or
icons need full control.

Visible parts accept `className`: `NumberField`, `NumberFieldScrubArea`, `NumberFieldGroup`,
`NumberFieldDecrement`, `NumberFieldInput`, and `NumberFieldIncrement`. `NumberFieldScrubArea`
also accepts `cursor` for replacing the default scrub cursor icon, `withCursor={false}` for hiding
it, and `classNames.cursor` for styling the internal cursor wrapper.

## Examples [#examples]

### Controlled [#controlled]

Control the numeric value from React state when other UI needs to react to the current number.

<Preview>
  <ControlledNumberFieldExample />

  <Preview.Code>
    {`
          import {
            Field,
            FieldLabel,
            NumberField,
          } from "moduix";
          import { useState, useId } from "react";

          export function ControlledNumberFieldDemo() {
            const id = useId();
            const [value, setValue] = useState(24 as number | null);

            return (
              <div>
                <Field>
                  <FieldLabel htmlFor={id}>Controlled value</FieldLabel>
                  <NumberField id={id} value={value} onValueChange={setValue} />
                </Field>
                <span>Current value: {value ?? "empty"}</span>
              </div>
            );
          }
        `}
  </Preview.Code>
</Preview>

### Min, Max, and Step [#min-max-and-step]

Use `min`, `max`, and `step` to constrain button, keyboard, scrub, and enabled wheel-scrub interactions.

<Preview>
  <NumberFieldExample defaultValue="10" min="0" max="20" step="2" />

  <Preview.Code>
    {`
          import {
            Field,
            FieldLabel,
            NumberField,
          } from "moduix";
          import { useId } from "react";

          export function MinMaxStepNumberFieldDemo() {
            const id = useId();

            return (
              <Field>
                <FieldLabel htmlFor={id}>Quantity (0-20, step 2)</FieldLabel>
                <NumberField id={id} defaultValue={10} min={0} max={20} step={2} />
              </Field>
            );
          }
        `}
  </Preview.Code>
</Preview>

### Formatting [#formatting]

Use `format` for localized presentation while keeping the value numeric.

<Preview>
  <FormattedNumberFieldExample />

  <Preview.Code>
    {`
          import {
            Field,
            FieldLabel,
            NumberField,
          } from "moduix";
          import { useId } from "react";

          export function FormattedNumberFieldDemo() {
            const id = useId();

            return (
              <Field>
                <FieldLabel htmlFor={id}>Price</FieldLabel>
                <NumberField
                  id={id}
                  defaultValue={1250}
                  min={0}
                  step={50}
                  format={{ style: "currency", currency: "USD", maximumFractionDigits: 0 }}
                />
              </Field>
            );
          }
        `}
  </Preview.Code>
</Preview>

### Scrub Area [#scrub-area]

Add `NumberFieldScrubArea` when users should be able to drag the label area to change the value. The scrub cursor renders automatically and can be customized with the `cursor`, `withCursor`, and `classNames.cursor` props.

<Preview>
  <NumberFieldScrubAreaExample />

  <Preview.Code>
    {`
          import {
            NumberField,
            NumberFieldScrubArea,
            FieldLabel,
          } from "moduix";
          import { useId } from "react";

          export function ScrubAreaNumberFieldDemo() {
            const id = useId();

            return (
              <NumberField id={id} defaultValue={250}>
                <NumberFieldScrubArea>
                  <FieldLabel htmlFor={id}>Drag to scrub</FieldLabel>
                </NumberFieldScrubArea>
              </NumberField>
            );
          }
        `}
  </Preview.Code>
</Preview>

### Field Validation [#field-validation]

Wrap the control in `Field` to show native validation errors from `required`, `min`, and `max`.

<Preview>
  <NumberFieldValidationExample />

  <Preview.Code>
    {`
          import {
            Field,
            FieldLabel,
            NumberField,
            FieldError,
          } from "moduix";
          import { useId } from "react";

          export function NumberFieldValidationDemo() {
            const id = useId();

            return (
              <Field name="quantity" validationMode="onBlur">
                <FieldLabel htmlFor={id}>Items</FieldLabel>
                <NumberField id={id} min={1} max={10} required />
                <FieldError match="valueMissing">Please provide a number.</FieldError>
                <FieldError match="rangeUnderflow">Value should be at least 1.</FieldError>
                <FieldError match="rangeOverflow">Value should be at most 10.</FieldError>
              </Field>
            );
          }
        `}
  </Preview.Code>
</Preview>

### Custom Icons [#custom-icons]

Pass children to the stepper buttons to use icons from your application or icon library.

<Preview>
  <CustomIconsNumberFieldExample />

  <Preview.Code>
    {`
          import {
            Field,
            FieldLabel,
            NumberField,
            NumberFieldGroup,
            NumberFieldDecrement,
            ChevronDownIcon,
            NumberFieldInput,
            NumberFieldIncrement,
            ChevronUpIcon,
          } from "moduix";
          import { useId } from "react";
          import styles from "./number-field.module.css";

          export function CustomIconsNumberFieldDemo() {
            const id = useId();

            return (
              <Field>
                <FieldLabel htmlFor={id}>Floors</FieldLabel>
                <NumberField id={id} defaultValue={8} withGroup={false}>
                  <NumberFieldGroup>
                    <NumberFieldDecrement aria-label="Decrease value" className={styles.customButton}>
                      <ChevronDownIcon />
                    </NumberFieldDecrement>
                    <NumberFieldInput className={styles.customInput} />
                    <NumberFieldIncrement aria-label="Increase value" className={styles.customButton}>
                      <ChevronUpIcon />
                    </NumberFieldIncrement>
                  </NumberFieldGroup>
                </NumberField>
              </Field>
            );
          }
        `}
  </Preview.Code>

  <Preview.CSS>
    {`
          .customButton {
            --number-field-button-bg: var(--color-muted);
            --number-field-button-bg-hover: var(--color-accent);
            --number-field-icon-size: 1rem;
          }

          .customInput {
            --number-field-input-width: 7rem;
            --number-field-input-font-size: var(--text-lg);
          }
        `}
  </Preview.CSS>
</Preview>
