---
title: InputNumber
description: Numeric input with increment and decrement controls, step, and min/max constraints.
sidebar:
  order: 5
search:
  tags: [number, stepper, spinbutton, numeric]
---

# InputNumber

> Numeric input with increment and decrement controls, step, and min/max constraints.

## Import

```tsx
import { InputNumber } from 'moraine'
```

## Slot Structure

Numeric input with increment/decrement controls placed inline (horizontal) or stacked (vertical).

### Horizontal

```text
root
├── decrement (optional)
├── input
└── increment (optional)
```

### Vertical

```text
root
├── input
└── controls
    ├── increment (optional)
    └── decrement (optional)
```

## Examples

### Variants

Visual style variants for the input shell.

```tsx
function Variants() {
  const VARIANTS = ['outline', 'subtle', 'ghost', 'none'] as const

  return (
    <div class="flex flex-wrap gap-6 items-start">
      <For each={VARIANTS}>
        {(variant) => (
          <div class="space-y-1">
            <label class="text-xs text-muted-foreground block">{variant}</label>
            <InputNumber variant={variant} defaultValue={3} />
          </div>
        )}
      </For>
    </div>
  )
}
```

### Sizes

Size scale from `xs` to `xl` for input height and control rail sizing.

```tsx
function Sizes() {
  const SIZES: InputNumberSize[] = ['xs', 'sm', 'md', 'lg', 'xl']

  return (
    <div class="max-w-md space-y-3">
      <For each={SIZES}>
        {(size) => (
          <div class="space-y-1">
            <p class="text-xs text-muted-foreground font-mono">{size}</p>
            <InputNumber size={size} defaultValue={3} />
          </div>
        )}
      </For>
    </div>
  )
}
```

### Orientations

Horizontal (default) and vertical button layouts.

```tsx
function Orientations() {
  return (
    <div class="flex flex-wrap gap-6 items-start">
      <div class="space-y-1">
        <label class="text-xs text-muted-foreground block">Horizontal</label>
        <InputNumber defaultValue={5} minValue={0} maxValue={20} />
      </div>
      <div class="space-y-1">
        <label class="text-xs text-muted-foreground block">Vertical</label>
        <InputNumber orientation="vertical" defaultValue={5} minValue={0} maxValue={20} />
      </div>
    </div>
  )
}
```

### Controlled

Value bound to a signal with live readout.

```tsx
function Controlled() {
  const [controlledValue, setControlledValue] = createSignal(10)

  return (
    <div class="max-w-xs space-y-2">
      <InputNumber
        value={controlledValue()}
        onRawValueChange={(v) => {
          if (Number.isFinite(v)) {
            setControlledValue(v)
          }
        }}
        minValue={0}
        maxValue={99}
        variant="subtle"
      />
      <p class="text-xs text-muted-foreground">Current value: {controlledValue()}</p>
    </div>
  )
}
```

### Disabled

Non-interactive disabled state.

```tsx
function Disabled() {
  return <InputNumber defaultValue={7} disabled />
}
```

### Long Press

Press and hold increment or decrement to continuously step the value. Set `holdRepeat={false}` to disable the repeat behavior.

```tsx
function LongPress() {
  const [repeatValue, setRepeatValue] = createSignal(12)
  const [singleStepValue, setSingleStepValue] = createSignal(12)

  return (
    <div class="max-w-xs space-y-4">
      <div class="space-y-2">
        <InputNumber
          value={repeatValue()}
          onRawValueChange={(value) => {
            if (Number.isFinite(value)) {
              setRepeatValue(value)
            }
          }}
          minValue={0}
          maxValue={99}
          step={1}
          variant="subtle"
        />
        <p class="text-xs text-muted-foreground">
          Hold <span class="font-medium">+</span> or <span class="font-medium">−</span> to repeat.
          Current value: {repeatValue()}
        </p>
      </div>

      <div class="space-y-2">
        <InputNumber
          value={singleStepValue()}
          holdRepeat={false}
          onRawValueChange={(value) => {
            if (Number.isFinite(value)) {
              setSingleStepValue(value)
            }
          }}
          minValue={0}
          maxValue={99}
          step={1}
          variant="subtle"
        />
        <p class="text-xs text-muted-foreground">
          Set <code>holdRepeat=false</code> to keep press-and-hold at a single step. Current value:{' '}
          {singleStepValue()}
        </p>
      </div>
    </div>
  )
}
```

### Min / Max / Step

Constrained ranges and custom step increments.

```tsx
function MinMaxStep() {
  return (
    <div class="flex flex-wrap gap-6 items-start">
      <div class="space-y-1">
        <label class="text-xs text-muted-foreground block">Step 5 (0–100)</label>
        <InputNumber defaultValue={25} minValue={0} maxValue={100} step={5} />
      </div>
      <div class="space-y-1">
        <label class="text-xs text-muted-foreground block">Step 0.1 (0–1)</label>
        <InputNumber defaultValue={0.5} minValue={0} maxValue={1} step={0.1} />
      </div>
    </div>
  )
}
```

## API Reference

### Attributes

#### `root`

Number input wrapper that owns the input and step controls.

##### Data Attributes

| Attribute | Type | Description |
| --- | --- | --- |
| data-orientation | string \| undefined | Stores the rendered orientation. |
| data-size | string | Stores the resolved size variant. |
| data-variant | string | Stores the resolved visual variant. |

##### ARIA Attributes

| Attribute | Type | Description |
| --- | --- | --- |
| role | string | Defines the semantic role exposed to assistive technology. |

#### `input`

Native number input element.

##### ARIA Attributes

| Attribute | Type | Description |
| --- | --- | --- |
| aria-disabled | boolean \| string \| undefined | Indicates that the control is disabled. |
| aria-readonly | boolean \| string \| undefined | Indicates that the control value cannot be changed by the user. |
| aria-required | boolean \| string \| undefined | Indicates that user input is required. |
| aria-valuemax | boolean \| string \| undefined | Defines the maximum value for range-like controls. |
| aria-valuemin | boolean \| string \| undefined | Defines the minimum value for range-like controls. |
| aria-valuenow | boolean \| string \| undefined | Defines the current numeric value for range-like controls. |
| role | string | Defines the semantic role exposed to assistive technology. |

#### `increment`

Button that increases the current numeric value.

#### `decrement`

Button that decreases the current numeric value.

### Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| autofocus | boolean \| undefined | false | Whether to automatically focus the input on mount. |
| autofocusDelay | number \| undefined | 0 | Delay in milliseconds before focusing the input. |
| class | ClassValue | — | Class applied to the component root or trigger element. |
| classes | InputNumberT.Classes \| undefined | — | — |
| decrement | boolean \| undefined | true | Whether to show the decrement button. |
| decrementDisabled | boolean \| undefined | — | Whether the decrement button is disabled. |
| decrementIcon | IconT.Name | orientation === 'vertical' ? 'icon-chevron-down' : 'icon-minus | Icon for the decrement button. |
| defaultValue | string \| number \| undefined | — | Default displayed value for uncontrolled usage. |
| disabled | boolean \| undefined | false | Whether the input is disabled. |
| holdRepeat | boolean \| undefined | true | Whether press-and-hold should trigger repeated value changes. |
| id | string \| undefined | — | The ID of the input element. |
| increment | boolean \| undefined | true | Whether to show the increment button. |
| incrementDisabled | boolean \| undefined | — | Whether the increment button is disabled. |
| incrementIcon | IconT.Name | orientation === 'vertical' ? 'icon-chevron-up' : 'icon-plus | Icon for the increment button. |
| largeStep | number \| undefined | step * 10 | The step size used for PageUp/PageDown. |
| locale | string \| undefined | — | Locale for number formatting and parsing.<br>Uses browser default if not specified. |
| maxValue | number \| undefined | — | Maximum allowed numeric value. |
| minValue | number \| undefined | — | Minimum allowed numeric value. |
| name | string \| undefined | — | The name of the input element, used for form submission. |
| onBlur | JSX.FocusEventHandlerUnion<HTMLInputElement, FocusEvent> \| undefined | — | Callback when the input loses focus. |
| onChange | ((value: string) => void) \| undefined | — | Callback when the formatted string value changes. |
| onDecrementClick | JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent> \| undefined | — | Callback when the decrement button is clicked. |
| onFocus | JSX.FocusEventHandlerUnion<HTMLInputElement, FocusEvent> \| undefined | — | Callback when the input gains focus. |
| onIncrementClick | JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent> \| undefined | — | Callback when the increment button is clicked. |
| onRawValueChange | ((value: number) => void) \| undefined | — | Callback when the numeric value changes. |
| orientation | InputNumberOrientation \| undefined | horizontal | The orientation of the control buttons. |
| placeholder | string \| undefined | — | Placeholder text for the input. |
| rawValue | number \| undefined | — | Controlled numeric value. Takes precedence over `value`. |
| readOnly | boolean \| undefined | false | Whether the input is read-only. |
| ref | JSX.HTMLElementTags["div"] extends { ref?: infer Ref; } ? Ref : never \| undefined | — | — |
| repeatDelayMs | number \| undefined | 500 | Delay in milliseconds before repeated value changes start. |
| repeatIntervalMs | number \| undefined | 80 | Interval in milliseconds between repeated value changes. |
| repeatPointerTypes | "all" \| PointerType \| undefined | all | Pointer types that can trigger press-and-hold repeat. |
| repeatThrottleMs | number \| undefined | 0 | Minimum elapsed time in milliseconds between repeat triggers. |
| required | boolean \| undefined | false | Whether the input is required. |
| size | "xs" \| "sm" \| "md" \| "lg" \| "xl" \| undefined | — | — |
| step | number \| undefined | 1 | The increment/decrement step size. |
| style | JSX.CSSProperties \| undefined | — | — |
| styles | InputNumberT.Styles \| undefined | — | — |
| value | string \| number \| undefined | — | Controlled displayed value. |
| variant | "none" \| "outline" \| "ghost" \| "subtle" \| undefined | — | — |
| wheel | boolean \| undefined | false | Whether mouse wheel changes the value while the input is focused. |

### ARIA

Accessibility attributes and roles emitted by the component markup.

| Attribute | Type | Description |
| --- | --- | --- |
| aria-busy | boolean \| string \| undefined | Accessibility attribute forwarded by the rendered component. |
| aria-disabled | boolean \| string \| undefined | Indicates that the control is disabled. |
| aria-hidden | boolean \| string \| undefined | Hides decorative content from assistive technology. |
| aria-readonly | boolean \| string \| undefined | Indicates that the control value cannot be changed by the user. |
| aria-required | boolean \| string \| undefined | Indicates that user input is required. |
| aria-valuemax | boolean \| string \| undefined | Defines the maximum value for range-like controls. |
| aria-valuemin | boolean \| string \| undefined | Defines the minimum value for range-like controls. |
| aria-valuenow | boolean \| string \| undefined | Defines the current numeric value for range-like controls. |
| role | string | Defines the semantic role exposed to assistive technology. |

### Data Attributes

State and slot attributes exposed for styling hooks and selectors.

| Attribute | Type | Description |
| --- | --- | --- |
| data-disabled | string \| undefined | Present when the component or item is disabled. |
| data-loading | string \| undefined | Present when the component is loading. |
| data-orientation | string \| undefined | Stores the rendered orientation. |
| data-size | string | Stores the resolved size variant. |
| data-slot | string | Identifies the rendered slot for styling hooks and selectors. |
| data-variant | string | Stores the resolved visual variant. |
