react-intl-number-input

A React component for masked and formatted number input with Intl.NumberFormat locale support.

Interactive demo

Requirements

Install

npm install react-intl-number-input

Usage

import React, { useState } from 'react';
import IntlNumberInput from 'react-intl-number-input';

function App() {
  const [value, setValue] = useState(0);
  const [maskedValue, setMaskedValue] = useState('0.00');

  const handleChange = (event, nextValue, nextMaskedValue) => {
    setValue(nextValue);
    setMaskedValue(nextMaskedValue);
  };

  return (
    <div>
      <IntlNumberInput onChange={handleChange} />
      <p>value: {value}</p>
      <p>maskedValue: {maskedValue}</p>
    </div>
  );
}

TypeScript

import React, { useState } from 'react';
import IntlNumberInput, {
  IntlNumberInputProps,
} from 'react-intl-number-input';

function App() {
  const [value, setValue] = useState<number>(0);

  const handleChange: IntlNumberInputProps['onChange'] = (event, val, masked) => {
    setValue(val);
    console.log('Numeric:', val, 'Masked:', masked);
  };

  return <IntlNumberInput value={value} onChange={handleChange} locale="pt-BR" />;
}

React 18+:

import { createRoot } from 'react-dom/client';

const root = createRoot(document.getElementById('root'));
root.render(<App />);

React 16/17:

import ReactDOM from 'react-dom';

ReactDOM.render(<App />, document.getElementById('root'));

Properties

Name Type Default Description
value number | string 0 Controlled numeric value
locale string 'en-US' BCP 47 language tag (Intl locales)
prefix string '' Prefix shown in the masked value
suffix string '' Suffix shown in the masked value
precision number 2 Fraction digits (clamped to 0–20)
onChange function — (event, value, maskedValue) => void
onBlur function — (event, value, maskedValue) => void
disabled boolean false Disables the input
autoFocus boolean — Native input autofocus
minValue number — Minimum allowed value
maxValue number — Maximum allowed value
showStepButtons boolean false Renders built-in +/- step buttons (ignored when renderControls is set)
renderControls function — (props: ControlsRenderProps) => ReactNode — custom stepper UI
step number 1 Step increment in display units (e.g. 1 with precision={2} adds 0.01)
inputMode 'numeric' | 'decimal' auto Overrides mobile keyboard mode
className string — Input CSS class
style object — Input inline styles
id string — Input id
name string — Input name
placeholder string — Input placeholder
readOnly boolean — Read-only input
required boolean — Required input
tabIndex number — Input tab index

The component also accepts standard <input> attributes such as ref, onFocus, onKeyDown, aria-*, data-*, and autoComplete. Custom onChange and onBlur callbacks receive the clamped numeric value and the formatted masked string.

Accessibility (a11y)

The component is built with accessibility in mind:

Examples

Basic Usage

// maskedValue: 1,234,567.89
<IntlNumberInput />
// maskedValue: 12,345.6789
<IntlNumberInput precision={4} />
// maskedValue: $1,234,567.89
<IntlNumberInput prefix="$" />
// maskedValue: 1,234%
<IntlNumberInput suffix="%" precision={0} />
// maskedValue: 12.50% — type digits only; decimal is implied by precision
<IntlNumberInput suffix="%" precision={2} />
// maskedValue: R$ 1.234.567,89
<IntlNumberInput locale="pt-BR" prefix="R$ " precision={2} />
// With min/max and step buttons
<IntlNumberInput
  value={50}
  minValue={0}
  maxValue={100}
  step={5}
  precision={0}
  showStepButtons
  onChange={(event, value) => console.log(value)}
/>
// Custom controls (layout is up to you — wrap in a parent div as needed)
<div className="amount-field">
  <IntlNumberInput
    value={12.34}
    precision={2}
    step={1}
    minValue={0}
    maxValue={100}
    onChange={(event, value) => console.log(value)}
    renderControls={({ increment, decrement, setValue, value, formattedValue, min, max, disabled }) => (
      <div className="amount-controls">
        <button type="button" onClick={() => decrement()} disabled={disabled}>-</button>
        <span>{formattedValue}</span>
        <button type="button" onClick={() => increment()} disabled={disabled}>+</button>
        <button type="button" onClick={() => setValue(max ?? value)} disabled={disabled}>
          Max
        </button>
      </div>
    )}
  />
</div>

Programmatic Focus (ref)

import React, { useRef } from 'react';
import IntlNumberInput from 'react-intl-number-input';

function FocusExample() {
  const inputRef = useRef(null);

  return (
    <>
      <IntlNumberInput ref={inputRef} />
      <button onClick={() => inputRef.current?.focus()}>
        Focus Input
      </button>
    </>
  );
}

renderControls replaces showStepButtons when both are provided. The component renders the <input> and your controls as siblings (no wrapper), so you control layout in the parent.

How input works

Users type digits; the component applies locale formatting and optional prefix/suffix. For precision={2}, typing 1234 becomes 12.34. Negative values are supported when - is present in the input.

Contributing

See CONTRIBUTING.md for development setup, testing, and publishing instructions.

Local development requires Node.js 20+. The published package has no Node.js version constraint for consumers.

Changelog

See CHANGELOG.md for release history.