NumberInput

Capture number from user

PackageIcon

Usage

NumberInput is based on react-number-format. It supports most of the props from the NumericFormat component in the original package.

NumberInput component supports Input and Input.Wrapper component features and all input element props. The NumberInput documentation does not include all features supported by the component – see the Input documentation to learn about all available features.

Input description

Variant
Size
Radius
import { NumberInput } from '@mantine/core';

function Demo() {
  return (
    <NumberInput
      label="Input label"
      description="Input description"
      placeholder="Input placeholder"
    />
  );
}

Loading state

Set loading prop to display a loading indicator. By default, the loader is displayed on the right side of the input. You can change the position with the loadingPosition prop to 'left' or 'right'. This is useful for async operations like API calls, searches, or validations:

import { NumberInput } from '@mantine/core';

function Demo() {
  return <NumberInput placeholder="Age" loading />;
}

Controlled

import { useState } from 'react';
import { NumberInput } from '@mantine/core';

function Demo() {
  const [value, setValue] = useState<string | number>('');
  return <NumberInput value={value} onChange={setValue} />;
}

Uncontrolled

NumberInput can be used with uncontrolled forms the same way as a native input[type="number"]. Set the name attribute to include number input value in FormData object on form submission. To control the initial value in uncontrolled forms, use the defaultValue prop.

Example usage of uncontrolled NumberInput with FormData:

import { NumberInput } from '@mantine/core';

function Demo() {
  return (
    <form
      onSubmit={(event) => {
        event.preventDefault();
        const formData = new FormData(event.currentTarget);
        console.log('Number input value:', formData.get('quantity'));
      }}
    >
      <NumberInput
        label="Enter quantity"
        name="quantity"
        defaultValue="1"
        min="1"
        max="100"
      />
      <button type="submit">Submit</button>
    </form>
  );
}

Value type

The value, defaultValue, and onChange props can be either string or number. In all cases when the NumberInput value can be represented as a number, the onChange function is called with a number (for example 55, 1.28, -100, etc.). But there are several cases when it is not possible to represent the value as a number:

  • Empty state is represented as an empty string – ''
  • Numbers that are larger than Number.MAX_SAFE_INTEGER - 1 or smaller than Number.MIN_SAFE_INTEGER + 1 are represented as strings – '90071992547409910'
  • Numbers with trailing decimal separators or trailing decimal zeros are represented as strings – 0., 0.0, -0.00, etc.

onChange vs onValueChange

NumberInput provides two callback props for handling value changes:

  • onChange: Receives a simplified value as number | string. This is the recommended callback for most use cases. The value is a number when possible, and a string in edge cases (empty input, very large numbers, trailing decimals).

  • onValueChange: Receives the full payload from react-number-format, which includes:

    • floatValue: The numeric value (or undefined)
    • formattedValue: The formatted string value (with prefix/suffix/separators)
    • value: The raw unformatted string value
    • Additional metadata about the change source

Use onValueChange when you need access to the formatted value or metadata about the change (e.g., whether it came from user typing, increment/decrement buttons, or programmatic changes). For simple form handling, onChange is sufficient.

import { NumberInput } from '@mantine/core';

function Demo() {
  return (
    <NumberInput
      prefix="$"
      thousandSeparator=","
      // onChange receives: 1234
      onChange={(value) => console.log('Simple value:', value)}
      // onValueChange receives: { floatValue: 1234, formattedValue: '$1,234', value: '1234' }
      onValueChange={(payload) => console.log('Full payload:', payload)}
    />
  );
}

min and max

Set the min and max props to limit the input value:

import { NumberInput } from '@mantine/core';

function Demo() {
  return (
    <NumberInput
      label="Enter value between 10 and 20"
      placeholder="Don't enter more than 20 and less than 10"
      min={10}
      max={20}
    />
  );
}

Clamp behavior

By default, the value is clamped when the input is blurred. If you set clampBehavior="strict", it will not be possible to enter a value outside of the min/max range. Note that this option may cause issues if you have tight min and max, for example min={10} and max={20}. If you need to disable value clamping entirely, set clampBehavior="none".

import { NumberInput } from '@mantine/core';

function Demo() {
  return (
    <NumberInput
      label="Strict clamping between 0 and 100"
      placeholder="Enter a number"
      clampBehavior="strict"
      min={0}
      max={100}
    />
  );
}

Prefix and suffix

Set the prefix and suffix props to add a given string to the start or end of the input value:

import { NumberInput } from '@mantine/core';

function Demo() {
  return (
    <>
      <NumberInput
        label="With prefix"
        placeholder="Dollars"
        prefix="$"
        defaultValue={100}
        mb="md"
      />
      <NumberInput
        label="With suffix"
        placeholder="Percents"
        suffix="%"
        defaultValue={100}
        mt="md"
      />
    </>
  );
}

Negative numbers

By default, negative numbers are allowed. Set allowNegative={false} to allow only positive numbers.

import { NumberInput } from '@mantine/core';

function Demo() {
  return (
    <NumberInput
      label="Negative number are not allowed"
      placeholder="Do not enter negative numbers"
      allowNegative={false}
    />
  );
}

Decimal numbers

By default, decimal numbers are allowed. Set allowDecimal={false} to allow only integers.

import { NumberInput } from '@mantine/core';

function Demo() {
  return (
    <NumberInput
      label="Decimals are not allowed"
      placeholder="Do not enter decimal numbers"
      allowDecimal={false}
    />
  );
}

Decimal scale

The decimalScale controls how many decimal places are allowed:

import { NumberInput } from '@mantine/core';

function Demo() {
  return (
    <NumberInput
      label="You can enter only 2 digits after decimal point"
      placeholder="Do not enter more than 2"
      decimalScale={2}
    />
  );
}

Fixed decimal scale

Set fixedDecimalScale to always display a fixed number of decimal places:

import { NumberInput } from '@mantine/core';

function Demo() {
  return (
    <NumberInput
      label="Always show 2 digits after decimal point"
      placeholder="Do not enter more that 2"
      decimalScale={2}
      fixedDecimalScale
      defaultValue={2.2}
    />
  );
}

Decimal separator

Set decimalSeparator to change the decimal separator character:

import { NumberInput } from '@mantine/core';

function Demo() {
  return (
    <NumberInput
      label="Custom decimal separator"
      placeholder="You can change it"
      decimalSeparator=","
      defaultValue={20.573}
    />
  );
}

Thousand separator

Set the thousandSeparator prop to separate thousands with a character. You can control the grouping logic with thousandsGroupStyle, which accepts: thousand, lakh, wan, none values.

import { NumberInput } from '@mantine/core';

function Demo() {
  return (
    <>
      <NumberInput
        label="Thousands are separated with a comma"
        placeholder="Thousands are separated with a comma"
        thousandSeparator=","
        defaultValue={1_000_000}
      />

      <NumberInput
        label="Thousands are separated with a space"
        placeholder="Thousands are separated with a space"
        thousandSeparator=" "
        defaultValue={1_000_000}
        mt="md"
      />
    </>
  );
}

Trim leading zeros on blur

By default, leading zeros are removed when the input loses focus (e.g., 00100 becomes 100). You can disable this behavior by setting trimLeadingZeroesOnBlur={false}:

import { NumberInput } from '@mantine/core';

function Demo() {
  return (
    <>
      <NumberInput
        label="Leading zeros removed on blur"
        placeholder="Type 00100 and click outside"
        trimLeadingZeroesOnBlur
        defaultValue="00100"
      />

      <NumberInput
        label="Leading zeros preserved"
        placeholder="Type 00100 and click outside"
        trimLeadingZeroesOnBlur={false}
        defaultValue="00100"
        mt="md"
      />
    </>
  );
}

Left and right sections

NumberInput supports leftSection and rightSection props. These sections are rendered with absolute positioning inside the input wrapper. You can use them to display icons, input controls, or any other elements.

You can use the following props to control sections styles and content:

  • rightSection / leftSection – React node to render on the corresponding side of input
  • rightSectionWidth/leftSectionWidth – controls the width of the right section and padding on the corresponding side of the input. By default, it is controlled by the component size prop.
  • rightSectionPointerEvents/leftSectionPointerEvents – controls the pointer-events property of the section. If you want to render a non-interactive element, set it to none to pass clicks through to the input.
import { NumberInput } from '@mantine/core';
import { CurrencyEthIcon } from '@phosphor-icons/react';

function Demo() {
  const icon = <CurrencyEthIcon size={20} />;
  return (
    <>
      <NumberInput leftSection={icon} label="With left section" placeholder="With left section" />
      <NumberInput
        rightSection={icon}
        label="With right section"
        placeholder="With right section"
        mt="md"
      />
    </>
  );
}

Increment/decrement controls

By default, the right section is occupied by increment and decrement buttons. To hide them, set the hideControls prop. You can also use the rightSection prop to render anything in the right section to replace the default controls.

import { NumberInput } from '@mantine/core';
import { ChartScatterIcon } from '@phosphor-icons/react';

function Demo() {
  return (
    <>
      <NumberInput label="Hide controls" placeholder="Hide controls" hideControls />
      <NumberInput
        label="Custom right section"
        placeholder="Custom right section"
        mt="md"
        rightSection={<ChartScatterIcon />}
        rightSectionPointerEvents="none"
      />
    </>
  );
}

Increment/decrement on hold

Set the stepHoldDelay and stepHoldInterval props to define behavior when increment/decrement controls are clicked and held:

Step value when clicking and holding increment/decrement buttons

Steps get faster over time when holding the control button

import { NumberInput } from '@mantine/core';

function Demo() {
  return (
    <>
      <NumberInput
        label="Step on hold"
        description="Step value when clicking and holding increment/decrement buttons"
        stepHoldDelay={500}
        stepHoldInterval={100}
      />

      <NumberInput
        label="Step the value with interval function"
        description="Steps get faster over time when holding the control button"
        stepHoldDelay={500}
        stepHoldInterval={(t) => Math.max(1000 / t ** 2, 25)}
      />
    </>
  );
}

Custom increment and decrement controls

You can get a ref with increment and decrement functions to create custom controls:

import { useRef } from 'react';
import { NumberInput, Group, Button, NumberInputHandlers } from '@mantine/core';

function Demo() {
  const handlersRef = useRef<NumberInputHandlers>(null);
  return (
    <>
      <NumberInput
        label="Click buttons to change value"
        placeholder="Click the buttons"
        handlersRef={handlersRef}
        step={2}
        min={10}
        max={20}
        defaultValue={15}
      />

      <Group mt="md" justify="center">
        <Button onClick={() => handlersRef.current?.decrement()} variant="default">
          Decrement by 2
        </Button>

        <Button onClick={() => handlersRef.current?.increment()} variant="default">
          Increment by 2
        </Button>
      </Group>
    </>
  );
}

Error state

Invalid name

import { NumberInput } from '@mantine/core';

function Demo() {
  return (
    <>
      <NumberInput label="Boolean error" placeholder="Boolean error" error />
      <NumberInput
        mt="md"
        label="With error message"
        placeholder="With error message"
        error="Invalid name"
      />
    </>
  );
}

Disabled state

import { NumberInput } from '@mantine/core';

function Demo() {
  return <NumberInput disabled label="Disabled input" placeholder="Disabled input" />;
}

Styles API

NumberInput supports the Styles API; you can add styles to any inner element of the component with the classNames prop. Follow the Styles API documentation to learn more.

Description

Error

Component Styles API

Hover over selectors to highlight corresponding elements

/*
 * Hover over selectors to apply outline styles
 *
 */

Get element ref

import { useRef } from 'react';
import { NumberInput } from '@mantine/core';

function Demo() {
  const ref = useRef<HTMLInputElement>(null);
  return <NumberInput ref={ref} />;
}

Accessibility

If NumberInput is used without the label prop, it will not be announced properly by screen readers:

import { NumberInput } from '@mantine/core';

// Inaccessible input – screen reader will not announce it properly
function Demo() {
  return <NumberInput />;
}

Set aria-label to make the input accessible. In this case the label will not be visible, but screen readers will announce it:

import { NumberInput } from '@mantine/core';

// Accessible input – it has aria-label
function Demo() {
  return <NumberInput aria-label="My input" />;
}

If the label prop is set, the input will be accessible and it is not required to set aria-label:

import { NumberInput } from '@mantine/core';

// Accessible input – it has associated label element
function Demo() {
  return <NumberInput label="My input" />;
}