JGuang

NumberInput

NumberInput · Mantine Core

Preview
npm install @mantine/core@9.7.1 @mantine/hooks@9.7.1

NumberInput

Install: npm install @mantine/core@9.7.1 @mantine/hooks@9.7.1

Version and compatibility

Pinned Mantine Core 9.7.1 requires React and React DOM ^19.2.0 and matching @mantine/hooks@9.7.1, from the installed npm package's peerDependencies. Do not force it into React 18 or another incompatible version. Explain upgrade impact and agree on a plan with the project owner; do not hide conflicts with --force/--legacy-peer-deps. The native Provider enables theme.respectReducedMotion and uses forceColorScheme for light/dark.

Use case

Edit numeric values with formatting and stepping.

Native props used by this example

These props are extracted from the local typechecked JSX and grouped by their owning native component. This is not an exhaustive prop list; see pinned source signatures and inheritance below.

Component propExplanation
NumberInput.disabledDisable user input/action; not equivalent to readOnly.
NumberInput.errorLocal error display; it does not run validation by itself.
NumberInput.readOnlyPrevent editing while preserving the read-only interaction.
NumberInput.labelVisible label or component-specific label content.
NumberInput.defaultValueInitial uncontrolled value, not a live controlled update.
NumberInput.minLower numeric boundary.

Interaction and state checks

Current scenarios: Default, Disabled, Invalid, Read only. Check actual behavior after selecting supported disabled/error/read-only scenarios; components without those native states do not receive invented universal props. Verify keyboard focus and exits, then connect changed values through the native callback signature.

Business integration boundary

onChange can return a number or string, including empty and intermediate numeric input; do not coerce every keystroke to Number. Read the generic number/bigint mode in the pinned type.

Host setup

Install pinned @mantine/core and @mantine/hooks, import @mantine/core/styles.css at the entry and use MantineProvider. The example's theme/variant are JGuang-authored host props, not universal native component APIs; forceColorScheme synchronizes the app theme. Native implementation source is a reading reference: use npm exports rather than copying internal engines. This batch covers Core, not dates/charts/forms extension packages. Detailed upstream prose remains English.

Runnable usage

import { useState } from 'react';
import * as M from '@mantine/core';
import '@mantine/core/styles.css';


function ComponentPreview({ variant = 'Default' }: { variant?: string }) {
const disabled = variant === "Disabled";
const invalid = variant === "Invalid";
const readOnly = variant === "Read only";
return <M.NumberInput disabled={disabled} error={invalid ? "Check the quantity / 请检查数量" : undefined} readOnly={readOnly} label="Quantity / 数量" defaultValue={5} min={0} />;
}
export default function Example({ variant = 'Default', theme = 'light' }: { variant?: string; theme?: 'light' | 'dark' }) {
return <M.MantineProvider forceColorScheme={theme} theme={{ respectReducedMotion: true }}><ComponentPreview variant={variant} /></M.MantineProvider>;
}

Preview states

Default, Disabled, Invalid, Read only

The example uses the native Mantine API. For supported input demos, change the local variant to Disabled, Invalid or Read only; these are example labels, not new component props. Keep the documented data, controlled values, stores and compound children when adapting the example.

Usage

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

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:

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 – ''
  • A standalone minus sign is represented as a 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.

BigInt values

NumberInput also supports bigint values. BigInt mode is inferred from value or defaultValue:

  • value/defaultValue can be bigint | string
  • onChange receives bigint | string
  • min, max, step, and startValue support bigint
  • BigInt mode is integer-only (allowDecimal/decimal formatting props do not enable decimal parsing)

string is still used as a fallback for intermediate states (for example '' or '-').

onChange vs onValueChange

NumberInput provides two callback props for handling value changes:

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

  • 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:

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".

Boundary callbacks

Use onMinReached and onMaxReached to call a function when the value hits the min or max boundary. These callbacks are triggered when the user attempts to increment beyond max or decrement below min using the controls or keyboard arrows.

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

function Demo() {
  return (
    <NumberInput
      min={0}
      max={100}
      onMinReached={() => console.log('Minimum value reached')}
      onMaxReached={() => console.log('Maximum value reached')}
    />
  );
}

Select all on focus

Set selectAllOnFocus to automatically select the entire input value when the field receives focus. This is useful when you expect users to replace the value rather than edit it:

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

function Demo() {
  return <NumberInput selectAllOnFocus defaultValue={100} />;
}

Prefix and suffix

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

Negative numbers

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

Decimal numbers

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

Decimal scale

The decimalScale controls how many decimal places are allowed:

Fixed decimal scale

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

Decimal separator

Set decimalSeparator to change the decimal separator character:

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.

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}:

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.

Increment/decrement on hold

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

Custom increment and decrement controls

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

Error state

Success state

Disabled state

Source-backed parameter reference

Implementation signatures and types are generated from the pinned local source. Optional props, native inherited types, union branches and never exclusions are preserved. A forwardRef implementation's ref is a React reference, not an additional business prop.

NumberInput(_props: NumberInputProps<T>)
export interface NumberInputHandlers {
  increment: () => void;
  decrement: () => void;
}

export type NumberInputNumericType = number | bigint;

export type NumberInputValue<T extends NumberInputNumericType = number> = T | string;

type NumberInputNumericValue<T extends NumberInputNumericType = number> = T;

export type NumberInputStylesNames = 'controls' | 'control' | __InputStylesNames;

export type NumberInputCssVariables = {
  controls: '--ni-chevron-size';
};

export interface NumberInputProps<T extends NumberInputNumericType = number>
  extends
    BoxProps,
    Omit<__BaseInputProps, 'pointer'>,
    StylesApiProps<NumberInputFactory>,
    ElementProps<
      'input',
      'size' | 'type' | 'onChange' | 'value' | 'defaultValue' | 'min' | 'max' | 'step'
    > {
  /** Controlled component value */
  value?: NumberInputValue<T>;

  /** Uncontrolled component default value */
  defaultValue?: NumberInputValue<T>;

  /** Called when value changes */
  onChange?: (value: NumberInputValue<T>) => void;

  /** Called when value changes with `react-number-format` payload */
  onValueChange?: OnValueChange;

  /** Determines whether leading zeros are allowed during input. If `false`, leading zeros are removed as you type (e.g., typing `007` results in `7`). Works in conjunction with `trimLeadingZeroesOnBlur`. @default true */
  allowLeadingZeros?: boolean;

  /** Determines whether negative numbers are allowed. If `false`, the input will not accept negative values, and the decrement button will stop at `0` (when `min` is not set). @default true */
  allowNegative?: boolean;

  /** Characters which when pressed result in a decimal separator. These characters will be replaced by the `decimalSeparator` in the input value. @default ['.', ','] */
  allowedDecimalSeparators?: string[];

  /** Limits the number of digits that can be entered after the decimal point @default Infinity */
  decimalScale?: number;

  /** Character used as a decimal separator. Generally used with `allowedDecimalSeparators` prop. @default '.' */
  decimalSeparator?: string;

  /** If `true`, automatically pads the decimal part with zeros to match `decimalScale` (e.g., with `decimalScale={2}`, typing `5.1` displays as `5.10`). Requires `decimalScale` to be set. @default false */
  fixedDecimalScale?: boolean;

  /** Prefix added before the input value */
  prefix?: string;

  /** Suffix added after the input value */
  suffix?: string;

  /** Defines the thousand grouping style. 'thousand' (1,000), 'lakh' (1,00,000), 'wan' (1,0000), 'none'. */
  thousandsGroupStyle?: 'thousand' | 'lakh' | 'wan' | 'none';

  /** A function to validate the input value. If this function returns `false`, the `onChange` will not be called and the input value will not change. */
  isAllowed?: (values: NumberFormatValues) => boolean;

  /** Advanced: Set to `true` if you're passing numeric strings (e.g., `"12345"`) and using formatting props like `prefix` or `suffix`. In most cases, you don't need this prop. See [react-number-format docs](https://www.npmjs.com/package/react-number-format) for details. @default false */
  valueIsNumericString?: boolean;

  /** Controls input `type` attribute @default 'text' */
  type?: 'text' | 'tel' | 'password';

  /** A character used to separate thousands */
  thousandSeparator?: string | boolean;

  /** Minimum possible value */
  min?: NumberInputNumericValue<T>;

  /** Maximum possible value */
  max?: NumberInputNumericValue<T>;

  /** Number by which value will be incremented/decremented with up/down controls and keyboard arrows @default 1 */
  step?: NumberInputNumericValue<T>;

  /** If set, the up/down controls are hidden @default false */
  hideControls?: boolean;

  /** Controls how values are clamped to the `min`/`max` range:
   * - `'blur'` (default): User can type any value, but it's clamped when the input loses focus
   * - `'strict'`: User cannot type values outside the range
   * - `'none'`: No clamping; `min`/`max` only apply to increment/decrement controls and arrow keys
   */
  clampBehavior?: 'strict' | 'blur' | 'none';

  /** If set, decimal values are allowed @default true */
  allowDecimal?: boolean;

  /** Increment/decrement handlers */
  handlersRef?: React.Ref<NumberInputHandlers | undefined>;

  /** Value used when incrementing/decrementing an empty input. If `min` is set and `startValue < min`, `min` is used instead. @default 0 */
  startValue?: NumberInputNumericValue<T>;

  /** Interval in milliseconds between value steps when increment/decrement button is held down. Can be a number or a function `(stepCount) => number` for dynamic intervals. Requires `stepHoldDelay` to be set. @default undefined */
  stepHoldInterval?: number | ((stepCount: number) => number);

  /** Initial delay in milliseconds before stepping the value. */
  stepHoldDelay?: number;

  /** If set, up/down keyboard events increment/decrement value @default true */
  withKeyboardEvents?: boolean;

  /** If set, leading zeros are removed on blur. For example, `00100` -> `100` @default true */
  trimLeadingZeroesOnBlur?: boolean;

  /** If set, all text is selected when the input receives focus @default false */
  selectAllOnFocus?: boolean;

  /** Called when the increment button or arrow up key is pressed and the value has reached the maximum */
  onMinReached?: () => void;

  /** Called when the decrement button or arrow down key is pressed and the value has reached the minimum */
  onMaxReached?: () => void;
}

export type NumberInputFactory = Factory<{
  props: NumberInputProps;
  ref: HTMLInputElement;
  stylesNames: NumberInputStylesNames;
  vars: NumberInputCssVariables;
  variant: InputVariant;
  signature: <T extends NumberInputNumericType = number>(
    props: NumberInputProps<T>
  ) => React.JSX.Element;
}>;

export type Mod = Record<string, any> | string;

export type BoxMod = Mod | Mod[] | BoxMod[];

export interface BoxProps extends MantineStyleProps {
  /** Class added to the root element, if applicable */
  className?: string | undefined;

  /** Inline style added to root component element, can subscribe to theme defined on MantineProvider */
  style?: MantineStyleProp;

  /** CSS variables defined on root component element */
  __vars?: CssVarsProp;

  /** `size` property passed down the HTML element */
  __size?: string;

  /** Breakpoint above which the component is hidden with `display: none` */
  hiddenFrom?: MantineBreakpoint;

  /** Breakpoint below which the component is hidden with `display: none` */
  visibleFrom?: MantineBreakpoint;

  /** Determines whether component should be hidden in light color scheme with `display: none` */
  lightHidden?: boolean;

  /** Determines whether component should be hidden in dark color scheme with `display: none` */
  darkHidden?: boolean;

  /** Element modifiers transformed into `data-` attributes, for example, `{ 'data-size': 'xl' }`, falsy values are removed */
  mod?: BoxMod;
}

export type ElementProps<
  ElementType extends React.ElementType,
  PropsToOmit extends string = never,
> = Omit<React.ComponentProps<ElementType>, 'style' | PropsToOmit>;

export interface CSSProperties extends React.CSSProperties {
  [key: string]: any;
}

type MantineStyle = CSSProperties | ((theme: MantineTheme) => CSSProperties);

export type MantineStyleProp = MantineStyle | MantineStyle[] | MantineStyleProp[] | undefined;

export type CssVariable = `--${string}`;

export type CssVariables<Variable extends string = CssVariable> = Partial<Record<Variable, string>>;

export type CssVars<Variable extends string = CssVariable> =
  | CssVariables<Variable>
  | ((theme: MantineTheme) => CssVariables<Variable>)
  | CssVars<Variable>[];

export type CssVarsProp<Variable extends string = CssVariable> =
  | CssVars<Variable>
  | CssVars<Variable>[];

type WrapperProps = React.ComponentProps<'div'> & DataAttributes;

export interface __BaseInputProps extends __InputWrapperProps, __InputProps {
  /** Props passed down to the root element */
  wrapperProps?: WrapperProps;
}

export type __InputStylesNames = InputStylesNames | InputWrapperStylesNames;

export type InputStylesNames = 'input' | 'wrapper' | 'section' | 'bottomSection';

export type InputVariant = 'default' | 'filled' | 'unstyled';

export interface __InputProps {
  /** Content section displayed on the left side of the input */
  leftSection?: React.ReactNode;

  /** Left section width, used to set `width` of the section and input `padding-left`, by default equals to the input height */
  leftSectionWidth?: React.CSSProperties['width'];

  /** Props passed down to the `leftSection` element */
  leftSectionProps?: React.ComponentProps<'div'>;

  /** Sets `pointer-events` styles on the `leftSection` element. Use `'all'` when section contains interactive elements (buttons, links). @default 'none' */
  leftSectionPointerEvents?: React.CSSProperties['pointerEvents'];

  /** Content section displayed on the right side of the input */
  rightSection?: React.ReactNode;

  /** Right section width, used to set `width` of the section and input `padding-right`, by default equals to the input height */
  rightSectionWidth?: React.CSSProperties['width'];

  /** Props passed down to the `rightSection` element */
  rightSectionProps?: React.ComponentProps<'div'>;

  /** Sets `pointer-events` styles on the `rightSection` element. Use `'all'` when section contains interactive elements (buttons, links). @default 'none' */
  rightSectionPointerEvents?: React.CSSProperties['pointerEvents'];

  /** Sets `required` attribute on the `input` element */
  required?: boolean;

  /** Key of `theme.radius` or any valid CSS value to set `border-radius`, numbers are converted to rem @default theme.defaultRadius */
  radius?: MantineRadius;

  /** Sets `disabled` attribute on the `input` element */
  disabled?: boolean;

  /** Controls input `height`, horizontal `padding`, and `font-size` @default 'sm' */
  size?: MantineSize | (string & {});

  /** Determines whether the input should have `cursor: pointer` style. Use when input acts as a button-like trigger (e.g., `component="button"` for Select/DatePicker). @default false */
  pointer?: boolean;

  /** Determines whether the input should have red border and red text color when the `error` prop is set @default true */
  withErrorStyles?: boolean;

  /** Determines whether the input should have green border when the `success` prop is set @default true */
  withSuccessStyles?: boolean;

  /** HTML `size` attribute for the input element (number of visible characters) */
  inputSize?: string;

  /** Section to be displayed when the input is `__clearable` and `rightSection` is not defined */
  __clearSection?: React.ReactNode;

  /** Determines whether the `__clearSection` should be displayed if it is passed to the component, has no effect if `rightSection` is defined */
  __clearable?: boolean;

  /** Determines how the clear button and rightSection are rendered @default 'both' */
  __clearSectionMode?: ClearSectionMode;

  /** Right section displayed when both `__clearSection` and `rightSection` are not defined */
  __defaultRightSection?: React.ReactNode;

  /** Displays loading indicator in the left or right section @default false */
  loading?: boolean;

  /** Position of the loading indicator @default 'right' */
  loadingPosition?: 'left' | 'right';

  /** Content section displayed at the bottom of the input, inside the border */
  __bottomSection?: React.ReactNode;

  /** Props passed down to the `__bottomSection` element */
  __bottomSectionProps?: React.ComponentProps<'div'>;
}

export type InputWrapperStylesNames =
  | 'root'
  | InputLabelStylesNames
  | InputDescriptionStylesNames
  | InputErrorStylesNames
  | InputSuccessStylesNames;

export interface __InputWrapperProps {
  /** Contents of `Input.Label` component. If not set, label is not displayed. */
  label?: React.ReactNode;

  /** Contents of `Input.Description` component. If not set, description is not displayed. */
  description?: React.ReactNode;

  /** Contents of `Input.Error` component. If not set, error is not displayed. */
  error?: React.ReactNode;

  /** Contents of `Input.Success` component. If not set, success is not displayed. */
  success?: React.ReactNode;

  /** Adds required attribute to the input and a red asterisk on the right side of label @default false */
  required?: boolean;

  /** If set, the required asterisk is displayed next to the label. Overrides `required` prop. Does not add required attribute to the input. @default false */
  withAsterisk?: boolean;

  /** Props passed down to the `Input.Label` component */
  labelProps?: InputLabelProps & DataAttributes & PlaceholderPolymorphicProps;

  /** Props passed down to the `Input.Description` component */
  descriptionProps?: InputDescriptionProps & DataAttributes & PlaceholderPolymorphicProps;

  /** Props passed down to the `Input.Error` component */
  errorProps?: InputErrorProps & DataAttributes & PlaceholderPolymorphicProps;

  /** Props passed down to the `Input.Success` component */
  successProps?: InputSuccessProps & DataAttributes & PlaceholderPolymorphicProps;

  /** Render function to wrap the input element. Useful for adding tooltips, popovers, or other wrappers around the input. @default React.Fragment */
  inputContainer?: (children: React.ReactNode) => React.ReactNode;

  /** Controls order and visibility of wrapper elements. Only elements included in this array will be rendered. @default ['label', 'description', 'input', 'error'] */
  inputWrapperOrder?: ('label' | 'input' | 'description' | 'error')[];
}

Native and external types

External types remain defined by React or native dependencies, not manually expanded into guessed props. The installed version's TypeScript definitions are authoritative.

Factory<{
  props: NumberInputProps;
  ref: HTMLInputElement;
  stylesNames: NumberInputStylesNames;
  vars: NumberInputCssVariables;
  variant: InputVariant;
  signature: <T extends NumberInputNumericType = number>(
    props: NumberInputProps<T>
  ) => React.JSX.Element;
}>
MantineStyleProps
React.CSSProperties
MantineTheme
Partial<Record<Variable, string>>
Record<Variable, string>
Variable
MantineBreakpoint
Record<string, any>
Omit<__BaseInputProps, 'pointer'>
React.ReactNode
InputLabelProps
DataAttributes
PlaceholderPolymorphicProps
InputDescriptionProps
InputErrorProps
InputSuccessProps
React.ComponentProps<'div'>
MantineRadius
MantineSize
ClearSectionMode
StylesApiProps<NumberInputFactory>
React.ElementType
Omit<React.ComponentProps<ElementType>, 'style' | PropsToOmit>
React.ComponentProps<ElementType>
ElementType
PropsToOmit
T
OnValueChange
NumberFormatValues
React.Ref<NumberInputHandlers | undefined>
HTMLInputElement
InputLabelStylesNames
InputDescriptionStylesNames
InputErrorStylesNames
InputSuccessStylesNames
React.JSX.Element