react-number-format
react-number-format is a React input formatter with a caret engine that keeps editing usable while separators, prefixes, suffixes, decimal rules, or fixed digit patterns are applied. Version 5 splits the public UI into `NumericFormat` for quantities and currency-like input, `PatternFormat` for phone, card, and identifier shapes, and `NumberFormatBase` plus hooks for custom rules. It can also render formatted text, includes TypeScript declarations, and has no runtime dependencies. It formats and constrains keystrokes; it does not validate a currency, phone number, card number, or business rule by itself.
One of the better React choices when caret-correct numeric editing is the hard part, especially with controlled numeric-string state. Skip it for display-only formatting or semantic validation, and read the v5 value-shape and event rules before wiring it into forms.
Use it if
- You need thousands separators, decimal limits, prefixes, suffixes, and stable caret behavior while a React user types
- You need fixed digit patterns such as phone or card layouts with placeholders and mobile-keyboard hints
- You want formatted display and editable input to share the same rules and value object
- You have unusual formatting rules and can build them on NumberFormatBase, useNumericFormat, or usePatternFormat
- You only display numbers and never edit them: `Intl.NumberFormat` gives locale-aware output without a React input dependency or caret engine
- You expect automatic locale or currency semantics: separators, grouping style, decimal scale, prefix, and suffix are explicit props, and a `$` prefix neither selects a currency nor validates monetary precision
- You need arbitrary text masks: PatternFormat reserves placeholders for numeric characters, while react-imask covers broader typed and mixed-character masks
- You plan to store `floatValue` for high-precision money or very large identifiers: the quirks guide warns that the float can use exponential notation, while the unformatted string preserves digits without IEEE-754 rounding
- You rely on native `minLength` or `maxLength` to enforce formatted length: the documentation says these do not behave as expected because formatting occurs after input and recommends `isAllowed` instead
Setup reality
Install with `npm install react-number-format`; there are no runtime dependencies, stylesheet imports, native extensions, credentials, or config files. React and ReactDOM are peers, with version 5.4.5 declaring compatibility from React 0.14 through 19, and TypeScript declarations ship in `types/index.d.ts`. The first migration trap is old examples: version 5 removed the default `NumberFormat` component in favor of named `NumericFormat` and `PatternFormat` exports, renamed `isNumericString` to `valueIsNumericString`, and moved custom format functions to `NumberFormatBase`. The second trap is state shape. `onValueChange` returns `formattedValue`, an unformatted numeric-string `value`, and `floatValue`; choose one representation and feed the same kind back. For currency and identifiers, the numeric string is usually safest. When a controlled value is an unformatted string and the format contains digits, or NumericFormat uses `.` as the thousands separator, set `valueIsNumericString` so the library does not interpret value digits as formatting. `onValueChange` is not a native change handler and can run because a prop changed or because blur reformatted the value; inspect its second argument's `source` before treating every call as user input. Native `onChange`, `onBlur`, and `onFocus` still receive normal input events and do not receive the value object. NumericFormat does not infer the user's locale. You must choose decimalSeparator, thousandSeparator, grouping style, allowed alternative separators, decimal scale, and negative-number policy. `decimalScale` rounds a controlled value, and `fixedDecimalScale` pads trailing zeros. PatternFormat's `#` slots accept digits but do not prove that the completed string is a real card number or reachable phone number. On mobile, the docs recommend `type="tel"` for fixed patterns, but `type="text"` for decimal input so users can type the decimal separator. `customInput` takes a component reference, not an inline render function; that component must forward the value, events, and ref expected by the formatter. `isAllowed` rejects an edit before value change and is the documented replacement for length props, but it should be fast and should allow intermediate states such as an empty field or a lone minus sign when those are part of editing. Display mode renders a span by default and `renderText` replaces it. Accessibility remains your job: provide labels, descriptions, error text, and semantic validation, and do not assume visual masking explains required input to a screen-reader user.
Patterns
Build a controlled currency-style inputformat-currency-input
import { NumericFormat } from 'react-number-format';
<NumericFormat
value={amount}
valueIsNumericString
onValueChange={({ value }) => setAmount(value)}
thousandSeparator=","
decimalSeparator="."
decimalScale={2}
fixedDecimalScale
prefix="$"
/>Store the unformatted string for exact decimal digits. A prefix formats the field but does not identify or convert a currency.
Choose the correct value representationread-value-object
<NumericFormat
onValueChange={({ formattedValue, value, floatValue }, sourceInfo) => {
console.log({ formattedValue, value, floatValue, source: sourceInfo.source });
}}
/>`floatValue` can lose precision or use exponential notation; `value` is the safer state form for money and long identifiers.
Use comma decimals and dot groupingaccept-european-separators
<NumericFormat
value={amount}
valueIsNumericString
thousandSeparator="."
decimalSeparator=","
allowedDecimalSeparators={[',', '.']}
onValueChange={({ value }) => setAmount(value)}
/>`valueIsNumericString` is required here because the unformatted string is paired with `.` as the thousands separator.
Reject values outside a business limitlimit-value-range
<NumericFormat
value={quantity}
onValueChange={({ value }) => setQuantity(value)}
decimalScale={0}
allowNegative={false}
isAllowed={({ floatValue }) => floatValue == null || floatValue <= 1000}
/>Allow `floatValue == null` so the user can clear the field; isAllowed returning false blocks the edit and value callback.
Format a fixed phone-number patternformat-phone-pattern
import { PatternFormat } from 'react-number-format';
<PatternFormat
format="+1 (###) ###-####"
mask="_"
type="tel"
value={phoneDigits}
valueIsNumericString
onValueChange={({ value }) => setPhoneDigits(value)}
/>This restricts and formats digits but does not validate whether the number exists or whether its area code is valid.
Format card-number digitsmask-card-number
<PatternFormat
format="#### #### #### ####"
mask="_"
type="tel"
value={cardDigits}
valueIsNumericString
onValueChange={({ value }) => setCardDigits(value)}
/>The pattern neither runs a Luhn check nor handles every card length. Keep full card data out of ordinary application state and logs.
Render a noneditable formatted numberrender-formatted-text
<NumericFormat
value="1234567.5"
valueIsNumericString
displayType="text"
thousandSeparator
prefix="$"
decimalScale={2}
fixedDecimalScale
/>For display-only locale-aware currency, native Intl.NumberFormat is often a smaller and more semantic choice.
Format a design-system inputuse-custom-input
import { TextField } from '@mui/material';
<NumericFormat
customInput={TextField}
label="Budget"
value={budget}
valueIsNumericString
thousandSeparator
onValueChange={({ value }) => setBudget(value)}
/>Pass a component reference, not `customInput={() => <TextField />}`; the docs say an inline render component will not work.
Get the underlying input referencefocus-input-ref
const inputRef = useRef<HTMLInputElement | null>(null);
<NumericFormat
getInputRef={(element) => { inputRef.current = element; }}
thousandSeparator
/>
// later
inputRef.current?.focus();With a customInput, the referenced instance depends on that component's ref forwarding rather than always being a native input.
Ignore value callbacks caused by prop updatesdistinguish-user-changes
<NumericFormat
value={amount}
valueIsNumericString
onValueChange={({ value }, { source }) => {
if (source === 'event') setAmount(value);
}}
/>onValueChange can run for user events or prop changes, and it may run on blur; it is not equivalent to the native onChange event.
Reuse numeric formatting outside JSXformat-without-component
import { numericFormatter } from 'react-number-format';
const label = numericFormatter('1234567.5', {
thousandSeparator: ',',
decimalScale: 2,
fixedDecimalScale: true,
prefix: '$',
});The first argument is an unformatted numeric string. For ordinary locale display, compare this with Intl.NumberFormat.
Accept whole numbers onlyblock-decimal-input
<NumericFormat
value={count}
valueIsNumericString
decimalScale={0}
allowNegative={false}
onValueChange={({ value }) => setCount(value)}
/>The quirks guide recommends `decimalScale={0}` to block floating-point input; native maxLength is not a reliable formatted-value constraint.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| react-currency-input-field | npm | Choose it when the requirement is specifically a React currency and numeric field with a narrower API |
| react-imask | npm | Choose it for broader masks, typed values, dates, and mixed character patterns beyond numeric placeholders |
| cleave.js | npm | Choose it when the formatter must also serve non-React pages or several frontend frameworks |