Number Input

Form

Numeric input with increment and decrement controls.

Preview

Usage

example.jsx
import { NumberInput } from "@/components/ui/number-input";

export default function Example() {
  return <NumberInput />;
}

Source Code

Copy this file into components/ui/number-input.jsx in your project.

number-input.jsx
"use client";

import { forwardRef } from "react";
import { cn } from "@/lib/utils";

const NumberInput = forwardRef(({ className, value, onValueChange, min, max, step = 1, ...props }, ref) => {
  const handleIncrement = () => {
    const next = (Number(value) || 0) + step;
    if (max !== undefined && next > max) return;
    onValueChange?.(next);
  };

  const handleDecrement = () => {
    const next = (Number(value) || 0) - step;
    if (min !== undefined && next < min) return;
    onValueChange?.(next);
  };

  return (
    <div className={cn("flex items-center", className)}>
      <button type="button" onClick={handleDecrement} className="flex h-9 w-9 items-center justify-center rounded-l-md border border-input bg-background text-sm hover:bg-accent cursor-pointer disabled:opacity-50">−</button>
      <input
        ref={ref}
        type="number"
        value={value ?? ""}
        onChange={(e) => onValueChange?.(e.target.value === "" ? "" : Number(e.target.value))}
        min={min}
        max={max}
        step={step}
        className="flex h-9 w-full border-y border-input bg-transparent px-3 py-1 text-center text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
        {...props}
      />
      <button type="button" onClick={handleIncrement} className="flex h-9 w-9 items-center justify-center rounded-r-md border border-input bg-background text-sm hover:bg-accent cursor-pointer disabled:opacity-50">+</button>
    </div>
  );
});
NumberInput.displayName = "NumberInput";

export { NumberInput };

Quick Install

Make sure you have the cn() utility set up. It requires clsx and tailwind-merge.

npm install clsx tailwind-merge