Skip to main content
The NumberInput component provides multiple ways to input numeric values: direct text entry, increment/decrement buttons, and interactive scrubbing.

Installation

npx shadcn@latest add @eo-n/number-input

Usage

import {
  NumberInput,
  NumberInputDecrement,
  NumberInputField,
  NumberInputGroup,
  NumberInputIncrement,
} from "@/components/ui/number-input";

export default function Example() {
  return (
    <NumberInput>
      <NumberInputGroup>
        <NumberInputDecrement />
        <NumberInputField />
        <NumberInputIncrement />
      </NumberInputGroup>
    </NumberInput>
  );
}

Examples

Default

<NumberInput defaultValue={0}>
  <NumberInputGroup>
    <NumberInputDecrement />
    <NumberInputField />
    <NumberInputIncrement />
  </NumberInputGroup>
</NumberInput>

With Label and Scrub Area

import {
  NumberInputScrubArea,
  NumberInputScrubAreaCursor,
} from "@/components/ui/number-input";
import { Label } from "@/components/ui/label";

<NumberInput defaultValue={25}>
  <NumberInputScrubArea>
    <Label>Age</Label>
  </NumberInputScrubArea>
  <NumberInputGroup>
    <NumberInputDecrement />
    <NumberInputField />
    <NumberInputIncrement />
  </NumberInputGroup>
</NumberInput>

Bounded Number Input (Min/Max)

<NumberInput defaultValue={50} min={0} max={100}>
  <NumberInputScrubArea>
    <Label>Volume (0-100)</Label>
  </NumberInputScrubArea>
  <NumberInputGroup>
    <NumberInputDecrement />
    <NumberInputField />
    <NumberInputIncrement />
  </NumberInputGroup>
</NumberInput>

With Custom Step

<NumberInput defaultValue={0} step={5}>
  <NumberInputGroup>
    <NumberInputDecrement />
    <NumberInputField />
    <NumberInputIncrement />
  </NumberInputGroup>
</NumberInput>

Decimal Values

<NumberInput defaultValue={0.0} step={0.1} min={0} max={1}>
  <NumberInputScrubArea>
    <Label>Opacity (0.0 - 1.0)</Label>
  </NumberInputScrubArea>
  <NumberInputGroup>
    <NumberInputDecrement />
    <NumberInputField />
    <NumberInputIncrement />
  </NumberInputGroup>
</NumberInput>

Disabled

<NumberInput defaultValue={42} disabled>
  <NumberInputGroup>
    <NumberInputDecrement />
    <NumberInputField />
    <NumberInputIncrement />
  </NumberInputGroup>
</NumberInput>

Scrub Direction

<NumberInput defaultValue={0}>
  <NumberInputScrubArea direction="horizontal">
    <Label>Horizontal Scrub</Label>
  </NumberInputScrubArea>
  <NumberInputGroup>
    <NumberInputDecrement />
    <NumberInputField />
    <NumberInputIncrement />
  </NumberInputGroup>
</NumberInput>

Controlled

import { useState } from "react";

function ControlledNumberInput() {
  const [value, setValue] = useState(0);

  return (
    <div className="space-y-4">
      <NumberInput value={value} onValueChange={setValue}>
        <NumberInputGroup>
          <NumberInputDecrement />
          <NumberInputField />
          <NumberInputIncrement />
        </NumberInputGroup>
      </NumberInput>
      <p className="text-sm">Value: {value}</p>
    </div>
  );
}

In a Form

function FormExample() {
  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    const formData = new FormData(e.target as HTMLFormElement);
    console.log(formData.get('quantity'));
  };

  return (
    <form onSubmit={handleSubmit} className="space-y-4">
      <div>
        <Label htmlFor="quantity">Quantity</Label>
        <NumberInput name="quantity" defaultValue={1} min={1}>
          <NumberInputGroup>
            <NumberInputDecrement />
            <NumberInputField id="quantity" />
            <NumberInputIncrement />
          </NumberInputGroup>
        </NumberInput>
      </div>
      <button type="submit">Submit</button>
    </form>
  );
}

API Reference

NumberInput

Extends all props from @base-ui/react NumberField.Root component.
value
number | null
The controlled value of the number input.
defaultValue
number | null
The default value when uncontrolled.
onValueChange
function
Callback fired when the value changes.
(value: number | null) => void
min
number
The minimum allowed value.
max
number
The maximum allowed value.
step
number
default:"1"
The increment/decrement step value.
disabled
boolean
Whether the number input is disabled.
required
boolean
Whether the number input is required in a form.
name
string
The name attribute for form submission.
allowWheelScrub
boolean
default:"false"
Allow changing value with mouse wheel.
className
string
Additional CSS classes to apply.

NumberInputScrubArea

Interactive area for scrubbing (dragging) to change values.
direction
string
default:"horizontal"
The scrub direction.Options: horizontal | vertical
pixelSensitivity
number
default:"2"
Pixels to drag per step increment.
className
string
Additional CSS classes to apply.

NumberInputGroup

Container for the decrement button, field, and increment button.
className
string
Additional CSS classes to apply.

NumberInputDecrement

Button to decrease the value.
className
string
Additional CSS classes to apply.

NumberInputField

The input field for direct text entry.
id
string
The id for associating with a label.
placeholder
string
Placeholder text for the input.
className
string
Additional CSS classes to apply.

NumberInputIncrement

Button to increase the value.
className
string
Additional CSS classes to apply.

TypeScript

import { NumberField as NumberInputPrimitive } from "@base-ui/react";

type NumberInputProps = React.ComponentProps<typeof NumberInputPrimitive.Root>;
type NumberInputFieldProps = React.ComponentProps<typeof NumberInputPrimitive.Input>;
type NumberInputScrubAreaProps = React.ComponentProps<typeof NumberInputPrimitive.ScrubArea>;

Styling Features

  • Tabular nums for aligned digits
  • Focus ring on keyboard focus
  • Hover states for buttons
  • Disabled state styling
  • Dark mode support
  • Interactive scrub cursor
  • Validation error states via aria-invalid
  • Shadow effects for depth

Accessibility

  • Fully keyboard accessible (Arrow keys, Page Up/Down)
  • Proper ARIA attributes
  • Focus visible indicators
  • Works with form labels
  • Screen reader support
  • Touch-friendly buttons
  • Input - For text input with number type
  • Slider - For visual range selection

Build docs developers (and LLMs) love