Number Field

A numeric input with stepper buttons, keyboard increments, scrubbing, and locale-aware formatting.

NumberFieldExample

Examples

Sizes

Use the size prop on NumberFieldGroup to switch between sm, default, and lg.

NumberFieldSizesExample

Controlled

Manage the value with React state via value and onValueChange. The value is null when the input is empty.

NumberFieldControlledExample
Value: 50

Formatting

Pass Intl.NumberFormatOptions to format for currency, percent, or unit display.

NumberFieldFormatExample

Scrub area

Wrap a label in NumberFieldScrubArea to let users drag horizontally to change the value. NumberFieldScrubAreaCursor renders a virtual cursor while scrubbing.

NumberFieldScrubExample

Installation

Copy the source code below into your project:

tsx
import { NumberField as BaseNumberField } from "@base-ui/react";
import { cn } from "@/lib/utils";
import { cva } from "class-variance-authority";
import type { VariantProps } from "class-variance-authority";
import { MinusIcon, PlusIcon } from "@phosphor-icons/react";

const NumberField = BaseNumberField.Root;

const numberFieldGroupVariants = cva(
  "flex w-full min-w-0 items-center overflow-visible rounded-lg bg-gray-950/5 text-foreground data-disabled:cursor-not-allowed data-disabled:opacity-50 dark:bg-gray-950/30",
  {
    defaultVariants: {
      size: "default",
    },
    variants: {
      size: {
        sm: "h-8 text-sm",
        default: "h-9 text-sm",
        lg: "h-10 text-base",
      },
    },
  },
);

function NumberFieldGroup({
  className,
  size,
  ...props
}: BaseNumberField.Group.Props & VariantProps<typeof numberFieldGroupVariants>) {
  return (
    <BaseNumberField.Group
      className={cn(numberFieldGroupVariants({ size }), className)}
      data-slot="number-field-group"
      {...props}
    />
  );
}

function NumberFieldInput({ className, ...props }: BaseNumberField.Input.Props) {
  return (
    <BaseNumberField.Input
      className={cn(
        "z-10 h-full min-w-0 flex-1 border border-input bg-transparent px-3 text-center tabular-nums transition-all duration-150 outline-none placeholder:text-muted-foreground not-data-disabled:hover:border-accent focus-visible:border-accent focus-visible:ring-[3px] focus-visible:ring-ring disabled:cursor-not-allowed",
        className,
      )}
      data-slot="number-field-input"
      {...props}
    />
  );
}

const numberFieldStepperClassName =
  "flex h-full shrink-0 bg-background cursor-default items-center justify-center self-stretch px-2.5 text-muted-foreground not-data-disabled:hover:bg-background-hover not-data-disabled:hover:text-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:size-4 border border-input";

function NumberFieldDecrement({ children, className, ...props }: BaseNumberField.Decrement.Props) {
  return (
    <BaseNumberField.Decrement
      className={cn(
        numberFieldStepperClassName,
        "rounded-l-lg rounded-r-none border-r-0",
        className,
      )}
      data-slot="number-field-decrement"
      aria-label="Decrease"
      {...props}
    >
      {children ?? <MinusIcon />}
    </BaseNumberField.Decrement>
  );
}

function NumberFieldIncrement({ children, className, ...props }: BaseNumberField.Increment.Props) {
  return (
    <BaseNumberField.Increment
      className={cn(
        numberFieldStepperClassName,
        "rounded-l-none rounded-r-lg border-l-0",
        className,
      )}
      data-slot="number-field-increment"
      aria-label="Increase"
      {...props}
    >
      {children ?? <PlusIcon />}
    </BaseNumberField.Increment>
  );
}

function NumberFieldScrubArea({ className, ...props }: BaseNumberField.ScrubArea.Props) {
  return (
    <BaseNumberField.ScrubArea
      className={cn("cursor-ew-resize select-none", className)}
      data-slot="number-field-scrub-area"
      {...props}
    />
  );
}

function NumberFieldScrubAreaCursor({
  className,
  ...props
}: BaseNumberField.ScrubAreaCursor.Props) {
  return (
    <BaseNumberField.ScrubAreaCursor
      className={cn("drop-shadow-sm", className)}
      data-slot="number-field-scrub-area-cursor"
      {...props}
    />
  );
}

export {
  NumberField,
  NumberFieldGroup,
  NumberFieldInput,
  NumberFieldDecrement,
  NumberFieldIncrement,
  NumberFieldScrubArea,
  NumberFieldScrubAreaCursor,
};

API Reference

NumberField

This component does not add any props on top of Base UI NumberField.Root. See the Base UI docs for the full API reference.

NumberFieldGroup

The NumberFieldGroup component extends the Base UI NumberField.Group props and adds the following:

PropTypeDefaultDescription
size"sm" | "default" | "lg""default"Controls the height and text size of the field.

NumberFieldInput

This component does not add any props on top of Base UI NumberField.Input. See the Base UI docs for the full API reference.

NumberFieldDecrement

This component does not add any props on top of Base UI NumberField.Decrement. See the Base UI docs for the full API reference.

NumberFieldIncrement

This component does not add any props on top of Base UI NumberField.Increment. See the Base UI docs for the full API reference.

NumberFieldScrubArea

This component does not add any props on top of Base UI NumberField.ScrubArea. See the Base UI docs for the full API reference.

NumberFieldScrubAreaCursor

This component does not add any props on top of Base UI NumberField.ScrubAreaCursor. See the Base UI docs for the full API reference.