# Number Field

Edit a numeric value with typing, stepping or scrubbing.

Quantity field

[Complete TSX file](/examples/number-field/default.tsx)

```tsx
"use client";

import {
  NumberField,
  NumberFieldDecrement,
  NumberFieldGroup,
  NumberFieldIncrement,
  NumberFieldInput,
  NumberFieldScrubArea,
} from "@/siteplane/components/number-field";

export default function Example() {
  return (
    <NumberField defaultValue={1}>
      <NumberFieldScrubArea label="Quantity" />
      <NumberFieldGroup>
        <NumberFieldDecrement aria-label="Decrease quantity" />
        <NumberFieldInput />
        <NumberFieldIncrement aria-label="Increase quantity" />
      </NumberFieldGroup>
    </NumberField>
  );
}
```

## Installation

Complete [one-time setup](/docs/installation/) first.

```bash
pnpm dlx shadcn@4.18.0 add @siteplane/number-field
```

With npm: `npx shadcn@4.18.0 add @siteplane/number-field`. [Registry JSON](/registry/number-field.json) lists source files and dependencies.

For this page’s examples, also install:

```bash
pnpm dlx shadcn@4.18.0 add @siteplane/button @siteplane/form @siteplane/input
```

Field is installed with Input.

## Usage

```tsx
import {
  NumberField,
  NumberFieldScrubArea,
  NumberFieldGroup,
  NumberFieldDecrement,
  NumberFieldInput,
  NumberFieldIncrement,
} from "@/siteplane/components/number-field";

<NumberField defaultValue={1}>
  <NumberFieldScrubArea label="Quantity" />
  <NumberFieldGroup>
    <NumberFieldDecrement aria-label="Decrease quantity" />
    <NumberFieldInput />
    <NumberFieldIncrement aria-label="Increase quantity" />
  </NumberFieldGroup>
</NumberField>;
```

## Composition

```text
NumberField
├── NumberFieldScrubArea
└── NumberFieldGroup
    ├── NumberFieldDecrement
    ├── NumberFieldInput
    └── NumberFieldIncrement
```

## Controlled range

Use a number or null for an empty field. Min, max and step constrain edits; the application stores the value.

Session timeout in 5-minute steps

[Complete TSX file](/examples/number-field/controlled.tsx)

```tsx
"use client";

import {
  NumberField,
  NumberFieldDecrement,
  NumberFieldGroup,
  NumberFieldIncrement,
  NumberFieldInput,
  NumberFieldScrubArea,
} from "@/siteplane/components/number-field";
import { useState } from "react";

export default function Example() {
  const [value, setValue] = useState<number | null>(30);

  return (
    <NumberField
      value={value}
      onValueChange={setValue}
      min={5}
      max={120}
      step={5}
    >
      <NumberFieldScrubArea label="Timeout (minutes)" />
      <NumberFieldGroup>
        <NumberFieldDecrement aria-label="Decrease timeout" />
        <NumberFieldInput />
        <NumberFieldIncrement aria-label="Increase timeout" />
      </NumberFieldGroup>
    </NumberField>
  );
}
```

## Formatted value

Locale and format apply to the editable value. The value remains numeric.

Budget in US dollars

[Complete TSX file](/examples/number-field/format.tsx)

```tsx
"use client";

import {
  NumberField,
  NumberFieldDecrement,
  NumberFieldGroup,
  NumberFieldIncrement,
  NumberFieldInput,
  NumberFieldScrubArea,
} from "@/siteplane/components/number-field";

export default function Example() {
  return (
    <NumberField
      defaultValue={12.5}
      min={0}
      format={{ style: "currency", currency: "USD" }}
      locale="en-US"
    >
      <NumberFieldScrubArea label="Budget" />
      <NumberFieldGroup>
        <NumberFieldDecrement aria-label="Decrease budget" />
        <NumberFieldInput />
        <NumberFieldIncrement aria-label="Increase budget" />
      </NumberFieldGroup>
    </NumberField>
  );
}
```

## Variants and disabled

`variant` sets the control surface. Disabled prevents stepping and editing.

Number field variants and disabled state

[Complete TSX file](/examples/number-field/appearance.tsx)

```tsx
"use client";

import {
  NumberField,
  NumberFieldDecrement,
  NumberFieldGroup,
  NumberFieldIncrement,
  NumberFieldInput,
  NumberFieldScrubArea,
} from "@/siteplane/components/number-field";

const fields: {
  disabled?: boolean;
  label: string;
  variant?: "default" | "elevated" | "inset";
}[] = [
  { label: "default", variant: "default" },
  { label: "elevated", variant: "elevated" },
  { label: "inset", variant: "inset" },
  { label: "disabled", disabled: true },
];

export default function Example() {
  return (
    <div className="grid gap-4">
      {fields.map(({ disabled, label, variant }) => (
        <NumberField
          defaultValue={2}
          disabled={disabled}
          key={label}
          variant={variant}
        >
          <NumberFieldScrubArea label={`Quantity, ${label}`} />
          <NumberFieldGroup>
            <NumberFieldDecrement aria-label="Decrease quantity" />
            <NumberFieldInput />
            <NumberFieldIncrement aria-label="Increase quantity" />
          </NumberFieldGroup>
        </NumberField>
      ))}
    </div>
  );
}
```

## Sizes

Omit `size` to inherit the provider's control size. `textSize` overrides the text size separately.

Number field sizes

[Complete TSX file](/examples/number-field/sizes.tsx)

```tsx
"use client";

import {
  NumberField,
  NumberFieldDecrement,
  NumberFieldGroup,
  NumberFieldIncrement,
  NumberFieldInput,
  NumberFieldScrubArea,
} from "@/siteplane/components/number-field";

const sizes = [
  { size: "sm", label: "small" },
  { size: "md", label: "medium" },
  { size: "lg", label: "large" },
  { size: "xl", label: "extra large" },
] as const;

export default function Example() {
  return (
    <div className="grid gap-4">
      {sizes.map(({ size, label }) => (
        <NumberField key={size} size={size} defaultValue={2}>
          <NumberFieldScrubArea label={`Quantity, ${label}`} />
          <NumberFieldGroup>
            <NumberFieldDecrement aria-label="Decrease quantity" />
            <NumberFieldInput />
            <NumberFieldIncrement aria-label="Increase quantity" />
          </NumberFieldGroup>
        </NumberField>
      ))}
    </div>
  );
}
```

## Emphasized value

Use `emphasized` when the number is the content, such as a quantity in a cart or the seats of a plan. The value turns semibold and one text step larger. In a form with other fields, keep the regular value.

Cart quantity

[Complete TSX file](/examples/number-field/emphasized.tsx)

```tsx
"use client";

import {
  NumberField,
  NumberFieldDecrement,
  NumberFieldGroup,
  NumberFieldIncrement,
  NumberFieldInput,
  NumberFieldScrubArea,
} from "@/siteplane/components/number-field";

export default function Example() {
  return (
    <NumberField emphasized defaultValue={2} min={1} max={10}>
      <NumberFieldScrubArea label="Quantity" />
      <NumberFieldGroup>
        <NumberFieldDecrement aria-label="Decrease quantity" />
        <NumberFieldInput />
        <NumberFieldIncrement aria-label="Increase quantity" />
      </NumberFieldGroup>
    </NumberField>
  );
}
```

## Form integration

Give the Field a name so Form submits the numeric value. `min` and `max` clamp the value, and FieldDescription explains the range. The field is compact by default; add `className="w-full"` to fill the form column.

Team seats in a form

[Complete TSX file](/examples/number-field/form.tsx)

```tsx
"use client";

import { Button } from "@/siteplane/components/button";
import { Field, FieldDescription } from "@/siteplane/components/field";
import { Form } from "@/siteplane/components/form";
import {
  NumberField,
  NumberFieldDecrement,
  NumberFieldGroup,
  NumberFieldIncrement,
  NumberFieldInput,
  NumberFieldScrubArea,
} from "@/siteplane/components/number-field";

export default function Example() {
  return (
    <Form
      className="grid w-full max-w-64 gap-4"
      onSubmit={(event) => event.preventDefault()}
    >
      <Field name="seats">
        <NumberField className="w-full" defaultValue={5} min={1} max={100}>
          <NumberFieldScrubArea label="Team seats" />
          <NumberFieldGroup>
            <NumberFieldDecrement aria-label="Remove a seat" />
            <NumberFieldInput />
            <NumberFieldIncrement aria-label="Add a seat" />
          </NumberFieldGroup>
        </NumberField>
        <FieldDescription>Choose between 1 and 100 seats.</FieldDescription>
      </Field>
      <Button type="submit">Update seats</Button>
    </Form>
  );
}
```

## API reference

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| NumberField.size | sm \| md \| lg \| xl | provider / lg | Shared dimensions. |
| NumberField.textSize | sm \| base | base for xl; otherwise CSS | Group text size. |
| NumberField.emphasized | boolean | false | Semibold value one text step larger than the field text: 16 px at sm, 18 px at md and lg, 20 px at xl. Overrides textSize. |
| NumberField.variant | default \| elevated \| inset | default | Group surface. |
| NumberField.id | string | generated | Input/label association. |
| NumberField.className | string | compact width | Layout and width. By default the field is two step buttons plus a 7rem value field wide (11rem at `md`); a width utility such as w-full or w-64 replaces that. |
| NumberFieldScrubArea.label | string | required | Visible associated label; scrubbing changes the number. |
| value / defaultValue | number \| null | uncontrolled | Controlled or initial numeric value. |
| onValueChange | (value, eventDetails) => void | unset | Numeric edit callback. |
| onValueCommitted | (value, eventDetails) => void | unset | Committed numeric edit callback. |
Root numeric range, formatting, locale, required, disabled, readOnly and form
props follow [Base UI Number
Field](https://base-ui.com/react/components/number-field). `NumberFieldGroup`
owns the shared surface; `NumberFieldInput` owns text editing;
Decrement/Increment use ghost icon Buttons by default and accept `render`, but
supply their own minus/plus children. ScrubArea must be inside NumberField and
supplies its own cursor. All styled parts accept string className.

`NumberFieldPrimitive` exports the foundation namespace. `NumberFieldContext` exports `{fieldId, size, textSize?, variant} | null`, default null. `CursorGrowIcon` accepts SVG props, defaults aria-hidden true and supplies the scrub cursor; it is not a control.

[Read the installed source](/source/number-field).

## Usage notes

Arrow keys adjust the focused number. The field is compact by default, sized for quantities and counts; set a width in `className` for longer values. A value longer than the field ends with an ellipsis until the field has focus; then it scrolls to the caret, as in any input. Keep a visible label and accessible names for step buttons. For invalid or server-rejected values, compose [Field](/docs/components/field/) help and errors; the numeric component does not persist or validate business rules.
