# Number Input

URL: https://ark-ui.com/docs/components/number-input
Source: https://raw.githubusercontent.com/chakra-ui/ark/refs/heads/main/website/src/content/pages/components/number-input.mdx

A field that allows user input of numeric values.

---

<ComponentPreview id="NumberInput" />

## Anatomy

<Anatomy id="number-input" />

```tsx
<NumberInput.Root>
  <NumberInput.Label />
  <NumberInput.Scrubber />
  <NumberInput.Control>
    <NumberInput.Input />
    <NumberInput.IncrementTrigger />
    <NumberInput.DecrementTrigger />
  </NumberInput.Control>
</NumberInput.Root>
```

## Examples

**Example: basic**

```ripple
import { NumberInput } from 'ark-ripple/number-input';
import { ChevronUp, ChevronDown } from 'lucide-ripple';
import styles from 'styles/number-input.module.css';

export component Basic() {
  <NumberInput.Root class={styles.Root}>
    <NumberInput.Label class={styles.Label}>{'Label'}</NumberInput.Label>
    <NumberInput.Control class={styles.Control}>
      <NumberInput.Input class={styles.Input} />
      <div class={styles.TriggerGroup}>
        <NumberInput.IncrementTrigger class={styles.IncrementTrigger}>
          <ChevronUp />
        </NumberInput.IncrementTrigger>
        <NumberInput.DecrementTrigger class={styles.DecrementTrigger}>
          <ChevronDown />
        </NumberInput.DecrementTrigger>
      </div>
    </NumberInput.Control>
  </NumberInput.Root>
}

```

### Min and Max

Pass the `min` prop or `max` prop to set an upper and lower limit for the input. By default, the input will restrict the
value to stay within the specified range.

**Example: min-max**

```ripple
import { NumberInput } from 'ark-ripple/number-input';
import { ChevronUp, ChevronDown } from 'lucide-ripple';
import styles from 'styles/number-input.module.css';

export component MinMax() {
  <NumberInput.Root class={styles.Root} min={0} max={10}>
    <NumberInput.Label class={styles.Label}>{'Label'}</NumberInput.Label>
    <NumberInput.Control class={styles.Control}>
      <NumberInput.Input class={styles.Input} />
      <div class={styles.TriggerGroup}>
        <NumberInput.IncrementTrigger class={styles.IncrementTrigger}>
          <ChevronUp />
        </NumberInput.IncrementTrigger>
        <NumberInput.DecrementTrigger class={styles.DecrementTrigger}>
          <ChevronDown />
        </NumberInput.DecrementTrigger>
      </div>
    </NumberInput.Control>
  </NumberInput.Root>
}

```

> To allow values outside the min/max range, set `clampValueOnBlur` to `false`.

### Precision

In some cases, you might need the value to be rounded to specific decimal points. Set the `formatOptions` and provide
`Intl.NumberFormatOptions` such as `maximumFractionDigits` or `minimumFractionDigits`.

**Example: fraction-digits**

```ripple
import { NumberInput } from 'ark-ripple/number-input';
import { ChevronUp, ChevronDown } from 'lucide-ripple';
import styles from 'styles/number-input.module.css';

export component FractionDigits() {
  <NumberInput.Root
    class={styles.Root}
    formatOptions={{ minimumFractionDigits: 2, maximumFractionDigits: 3 }}
    defaultValue="1.00"
  >
    <NumberInput.Label class={styles.Label}>{'Label'}</NumberInput.Label>
    <NumberInput.Control class={styles.Control}>
      <NumberInput.Input class={styles.Input} />
      <div class={styles.TriggerGroup}>
        <NumberInput.IncrementTrigger class={styles.IncrementTrigger}>
          <ChevronUp />
        </NumberInput.IncrementTrigger>
        <NumberInput.DecrementTrigger class={styles.DecrementTrigger}>
          <ChevronDown />
        </NumberInput.DecrementTrigger>
      </div>
    </NumberInput.Control>
  </NumberInput.Root>
}

```

### Scrubbing

The NumberInput supports the scrubber interaction pattern. To use this pattern, render the `NumberInput.Scrubber`
component. It uses the Pointer lock API and tracks the pointer movement. It also renders a virtual cursor which mimics
the real cursor's pointer.

**Example: scrubber**

```ripple
import { NumberInput } from 'ark-ripple/number-input';
import { ChevronUp, ChevronDown, ArrowLeftRight } from 'lucide-ripple';
import styles from 'styles/number-input.module.css';

export component Scrubber() {
  <NumberInput.Root class={styles.Root} defaultValue="32">
    <NumberInput.Label class={styles.Label}>{'Label'}</NumberInput.Label>
    <NumberInput.Control class={styles.Control}>
      <NumberInput.Scrubber class={styles.Scrubber}>
        <ArrowLeftRight />
      </NumberInput.Scrubber>
      <NumberInput.Input class={styles.Input} data-has-scrubber />
      <div class={styles.TriggerGroup}>
        <NumberInput.IncrementTrigger class={styles.IncrementTrigger}>
          <ChevronUp />
        </NumberInput.IncrementTrigger>
        <NumberInput.DecrementTrigger class={styles.DecrementTrigger}>
          <ChevronDown />
        </NumberInput.DecrementTrigger>
      </div>
    </NumberInput.Control>
  </NumberInput.Root>
}

```

### Mouse Wheel

The NumberInput exposes a way to increment/decrement the value using the mouse wheel event. To activate this, set the
`allowMouseWheel` prop to `true`.

**Example: mouse-wheel**

```ripple
import { NumberInput } from 'ark-ripple/number-input';
import { ChevronUp, ChevronDown } from 'lucide-ripple';
import styles from 'styles/number-input.module.css';

export component MouseWheel() {
  <NumberInput.Root class={styles.Root} allowMouseWheel>
    <NumberInput.Label class={styles.Label}>{'Label'}</NumberInput.Label>
    <NumberInput.Control class={styles.Control}>
      <NumberInput.Input class={styles.Input} />
      <div class={styles.TriggerGroup}>
        <NumberInput.IncrementTrigger class={styles.IncrementTrigger}>
          <ChevronUp />
        </NumberInput.IncrementTrigger>
        <NumberInput.DecrementTrigger class={styles.DecrementTrigger}>
          <ChevronDown />
        </NumberInput.DecrementTrigger>
      </div>
    </NumberInput.Control>
  </NumberInput.Root>
}

```

### Formatting

To apply custom formatting to the input's value, set the `formatOptions` and provide `Intl.NumberFormatOptions` such as
`style` and `currency`.

**Example: formatting**

```ripple
import { NumberInput } from 'ark-ripple/number-input';
import { ChevronUp, ChevronDown } from 'lucide-ripple';
import styles from 'styles/number-input.module.css';

export component Formatting() {
  <NumberInput.Root
    class={styles.Root}
    formatOptions={{
      style: 'currency',
      currency: 'USD',
    }}
  >
    <NumberInput.Label class={styles.Label}>{'Label'}</NumberInput.Label>
    <NumberInput.Control class={styles.Control}>
      <NumberInput.Input class={styles.Input} />
      <div class={styles.TriggerGroup}>
        <NumberInput.IncrementTrigger class={styles.IncrementTrigger}>
          <ChevronUp />
        </NumberInput.IncrementTrigger>
        <NumberInput.DecrementTrigger class={styles.DecrementTrigger}>
          <ChevronDown />
        </NumberInput.DecrementTrigger>
      </div>
    </NumberInput.Control>
  </NumberInput.Root>
}

```

### Field

The `Field` component helps manage form-related state and accessibility attributes of a number input. It includes
handling ARIA labels, helper text, and error text to ensure proper accessibility.

**Example: with-field**

```ripple
import { Field } from 'ark-ripple/field';
import { NumberInput } from 'ark-ripple/number-input';
import { ChevronUp, ChevronDown } from 'lucide-ripple';
import field from 'styles/field.module.css';
import styles from 'styles/number-input.module.css';

export component WithField() {
  <Field.Root class={field.Root}>
    <NumberInput.Root class={styles.Root}>
      <NumberInput.Label class={styles.Label}>{'Label'}</NumberInput.Label>
      <NumberInput.Control class={styles.Control}>
        <NumberInput.Input class={styles.Input} />
        <div class={styles.TriggerGroup}>
          <NumberInput.IncrementTrigger class={styles.IncrementTrigger}>
            <ChevronUp />
          </NumberInput.IncrementTrigger>
          <NumberInput.DecrementTrigger class={styles.DecrementTrigger}>
            <ChevronDown />
          </NumberInput.DecrementTrigger>
        </div>
      </NumberInput.Control>
    </NumberInput.Root>
    <Field.HelperText class={field.HelperText}>{'Additional Info'}</Field.HelperText>
    <Field.ErrorText class={field.ErrorText}>{'Error Info'}</Field.ErrorText>
  </Field.Root>
}

```

### Root Provider

An alternative way to control the number input is to use the `RootProvider` component and the `useNumberInput` hook.
This way you can access the state and methods from outside the component.

**Example: root-provider**

```ripple
import { NumberInput, useNumberInput } from 'ark-ripple/number-input';
import { ChevronUp, ChevronDown } from 'lucide-ripple';
import styles from 'styles/number-input.module.css';

export component RootProvider() {
  const numberInput = useNumberInput();

  <div class="stack">
    <output>
      {'valueAsNumber: '}
      {@numberInput.valueAsNumber}
    </output>
    <NumberInput.RootProvider class={styles.Root} value={numberInput}>
      <NumberInput.Label class={styles.Label}>{'Label'}</NumberInput.Label>
      <NumberInput.Control class={styles.Control}>
        <NumberInput.Input class={styles.Input} />
        <div class={styles.TriggerGroup}>
          <NumberInput.IncrementTrigger class={styles.IncrementTrigger}>
            <ChevronUp />
          </NumberInput.IncrementTrigger>
          <NumberInput.DecrementTrigger class={styles.DecrementTrigger}>
            <ChevronDown />
          </NumberInput.DecrementTrigger>
        </div>
      </NumberInput.Control>
    </NumberInput.RootProvider>
  </div>
}

```

## Guides

### Scrubber

The `NumberInput.Scrubber` component provides an interactive scrub area that allows users to drag to change the input
value. It renders as a `<div>` element and displays a custom cursor element during scrubbing interactions.

This component utilizes the [Pointer Lock API](https://developer.mozilla.org/en-US/docs/Web/API/Pointer_Lock_API) for
smooth dragging interactions.

> **Note:** Browsers may show a notification when the Pointer Lock API is activated. The scrubber is automatically
> disabled in Safari to prevent layout shifts.

### Controlled

When controlling the NumberInput component, it's recommended to use string values instead of converting to numbers. This
is especially important when using `formatOptions` for currency or locale-specific formatting.

```tsx
const [value, setValue] = useState('0')

<NumberInput.Root value={value} onValueChange={(details) => setValue(details.value)}>
  {/* ... */}
</NumberInput.Root>
```

Converting values to numbers can cause issues with locale-specific formatting, particularly for currencies that use
different decimal and thousands separators (e.g., `1.523,30` vs `1,523.30`). By keeping values as strings, you preserve
the correct formatting and avoid parsing issues.

If you need to submit a numeric value in your form, use a hidden input that reads `valueAsNumber` from
`NumberInput.Context`:

```tsx
<NumberInput.Root value={value} onValueChange={(details) => setValue(details.value)}>
  <NumberInput.Input />
  <NumberInput.Context>
    {(context) => <input type="hidden" name="amount" value={context.valueAsNumber} />}
  </NumberInput.Context>
</NumberInput.Root>
```

## API Reference

### Props

**Component API Reference**

**Root Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `allowMouseWheel` | `boolean` | No | Whether to allow mouse wheel to change the value |
| `allowOverflow` | `boolean` | No | Whether to allow the value overflow the min/max range |
| `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. |
| `clampValueOnBlur` | `boolean` | No | Whether to clamp the value when the input loses focus (blur) |
| `defaultValue` | `string` | No | The initial value of the input when rendered.
Use when you don't need to control the value of the input. |
| `disabled` | `boolean` | No | Whether the number input is disabled. |
| `focusInputOnChange` | `boolean` | No | Whether to focus input when the value changes |
| `form` | `string` | No | The associate form of the input element. |
| `formatOptions` | `NumberFormatOptions` | No | The options to pass to the `Intl.NumberFormat` constructor |
| `id` | `string` | No | The unique identifier of the machine. |
| `ids` | `Partial<{
  root: string
  label: string
  input: string
  incrementTrigger: string
  decrementTrigger: string
  scrubber: string
}>` | No | The ids of the elements in the number input. Useful for composition. |
| `inputMode` | `InputMode` | No | Hints at the type of data that might be entered by the user. It also determines
the type of keyboard shown to the user on mobile devices |
| `invalid` | `boolean` | No | Whether the number input value is invalid. |
| `locale` | `string` | No | The current locale. Based on the BCP 47 definition. |
| `max` | `number` | No | The maximum value of the number input |
| `min` | `number` | No | The minimum value of the number input |
| `name` | `string` | No | The name attribute of the number input. Useful for form submission. |
| `onFocusChange` | `(details: FocusChangeDetails) => void` | No | Function invoked when the number input is focused |
| `onValueChange` | `(details: ValueChangeDetails) => void` | No | Function invoked when the value changes |
| `onValueCommit` | `(details: ValueChangeDetails) => void` | No | Function invoked when the value is committed (when the input is blurred or the Enter key is pressed) |
| `onValueInvalid` | `(details: ValueInvalidDetails) => void` | No | Function invoked when the value overflows or underflows the min/max range |
| `pattern` | `string` | No | The pattern used to check the <input> element's value against |
| `readOnly` | `boolean` | No | Whether the number input is readonly |
| `required` | `boolean` | No | Whether the number input is required |
| `spinOnPress` | `boolean` | No | Whether to spin the value when the increment/decrement button is pressed |
| `step` | `number` | No | The amount to increment or decrement the value by |
| `translations` | `IntlTranslations` | No | Specifies the localized strings that identifies the accessibility elements and their states |
| `value` | `string` | No | The controlled value of the input |


**Root Data Attributes:**

| Attribute | Value |
|-----------|-------|
| `[data-scope]` | number-input |
| `[data-part]` | root |
| `[data-disabled]` | Present when disabled |
| `[data-focus]` | Present when focused |
| `[data-invalid]` | Present when invalid |
| `[data-scrubbing]` |  |


**Control Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. |


**Control Data Attributes:**

| Attribute | Value |
|-----------|-------|
| `[data-scope]` | number-input |
| `[data-part]` | control |
| `[data-focus]` | Present when focused |
| `[data-disabled]` | Present when disabled |
| `[data-invalid]` | Present when invalid |
| `[data-scrubbing]` |  |


**DecrementTrigger Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. |


**DecrementTrigger Data Attributes:**

| Attribute | Value |
|-----------|-------|
| `[data-scope]` | number-input |
| `[data-part]` | decrement-trigger |
| `[data-disabled]` | Present when disabled |
| `[data-scrubbing]` |  |


**IncrementTrigger Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. |


**IncrementTrigger Data Attributes:**

| Attribute | Value |
|-----------|-------|
| `[data-scope]` | number-input |
| `[data-part]` | increment-trigger |
| `[data-disabled]` | Present when disabled |
| `[data-scrubbing]` |  |


**Input Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. |


**Input Data Attributes:**

| Attribute | Value |
|-----------|-------|
| `[data-scope]` | number-input |
| `[data-part]` | input |
| `[data-invalid]` | Present when invalid |
| `[data-disabled]` | Present when disabled |
| `[data-scrubbing]` |  |


**Label Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. |


**Label Data Attributes:**

| Attribute | Value |
|-----------|-------|
| `[data-scope]` | number-input |
| `[data-part]` | label |
| `[data-disabled]` | Present when disabled |
| `[data-focus]` | Present when focused |
| `[data-invalid]` | Present when invalid |
| `[data-required]` | Present when required |
| `[data-scrubbing]` |  |


**RootProvider Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `value` | `UseNumberInputReturn` | Yes |  |
| `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. |


**Scrubber Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. |


**Scrubber Data Attributes:**

| Attribute | Value |
|-----------|-------|
| `[data-scope]` | number-input |
| `[data-part]` | scrubber |
| `[data-disabled]` | Present when disabled |
| `[data-scrubbing]` |  |


**ValueText Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. |


**ValueText Data Attributes:**

| Attribute | Value |
|-----------|-------|
| `[data-scope]` | number-input |
| `[data-part]` | value-text |
| `[data-disabled]` | Present when disabled |
| `[data-invalid]` | Present when invalid |
| `[data-focus]` | Present when focused |
| `[data-scrubbing]` |  |


### Context

**API:**

| Property | Type | Description |
|----------|------|-------------|
| `focused` | `boolean` | Whether the input is focused. |
| `invalid` | `boolean` | Whether the input is invalid. |
| `empty` | `boolean` | Whether the input value is empty. |
| `value` | `string` | The formatted value of the input. |
| `valueAsNumber` | `number` | The value of the input as a number. |
| `setValue` | `(value: number) => void` | Function to set the value of the input. |
| `clearValue` | `VoidFunction` | Function to clear the value of the input. |
| `increment` | `VoidFunction` | Function to increment the value of the input by the step. |
| `decrement` | `VoidFunction` | Function to decrement the value of the input by the step. |
| `setToMax` | `VoidFunction` | Function to set the value of the input to the max. |
| `setToMin` | `VoidFunction` | Function to set the value of the input to the min. |
| `focus` | `VoidFunction` | Function to focus the input. |


## Accessibility

Complies with the [Spinbutton WAI-ARIA design pattern](https://www.w3.org/WAI/ARIA/apg/patterns/spinbutton/).

### Keyboard Support

<KeyBindingsTable id="number-input" />