These libraries provide controlled input components for formatting user data during entry. react-number-format focuses on numeric values and currency. react-phone-number-input handles international phone numbers with country selection. react-input-mask and react-text-mask offer general-purpose string masking patterns, though maintenance status varies significantly between them.
When building forms, raw text inputs often lead to data quality issues. Users might enter phones without country codes or currency with wrong decimals. These four libraries solve that by formatting data as the user types. However, they differ in scope, API design, and long-term viability. Let's compare how they handle real-world input challenges.
How you define the rules for input varies wildly between these packages. This affects readability and maintenance.
react-input-mask uses a simple string pattern.
9 represents a digit.* represents any character.// react-input-mask: String pattern
import InputMask from 'react-input-mask';
<InputMask mask="99/99/9999" />
// User sees: __/__/____
react-text-mask uses an array of regex or functions.
// react-text-mask: Regex array
import MaskedInput from 'react-text-mask';
const dateMask = [/\d/, /\d/, '/', /\d/, /\d/, '/', /\d/, /\d/, /\d/, /\d/];
<MaskedInput mask={dateMask} />
react-number-format uses props for logic instead of masks.
// react-number-format: Logic props
import NumberFormat from 'react-number-format';
<NumberFormat prefix="$" decimalScale={2} fixedValue={true} />
// User sees: $0.00
react-phone-number-input hides the mask behind country logic.
// react-phone-number-input: Country based
import PhoneInput from 'react-phone-number-input';
<PhoneInput value={value} onChange={setValue} />
// User sees: +1 (___) ___-____ (depending on country)
All four libraries work with React's controlled component pattern, but they handle events differently.
react-input-mask passes standard onChange events.
// react-input-mask: Standard onChange
const [value, setValue] = useState('');
<InputMask
mask="999-999"
value={value}
onChange={(e) => setValue(e.target.value)}
/>
react-text-mask also uses standard events.
react-input-mask but with regex mask.// react-text-mask: Standard onChange
const [value, setValue] = useState('');
<MaskedInput
mask={dateMask}
value={value}
onChange={(e) => setValue(e.target.value)}
/>
react-number-format provides a custom callback.
onValueChange gives you the formatted and raw values.// react-number-format: Custom value callback
const [values, setValues] = useState({ value: '' });
<NumberFormat
value={values.value}
onValueChange={(values) => setValues(values)}
/>
// values.value is formatted, values.floatValue is raw number
react-phone-number-input manages value as E.164 string.
// react-phone-number-input: E.164 string
const [value, setValue] = useState('');
<PhoneInput
value={value}
onChange={setValue}
/>
// value is like "+14155552671"
This is the most critical factor for architectural decisions. Some packages are no longer safe for new projects.
react-input-mask is in maintenance mode.
// react-input-mask: Potential Strict Mode warning
// May require useEffect workarounds in React 18
<InputMask mask="999" />
react-text-mask is effectively abandoned.
// react-text-mask: Legacy warning
// Do not use in new greenfield projects
<MaskedInput mask={[/\d/]} />
react-number-format is actively maintained.
// react-number-format: Active maintenance
// Regularly updated for new React versions
<NumberFormat format="####" />
react-phone-number-input is actively maintained.
libphonenumber-js are kept current.// react-phone-number-input: Active maintenance
// Updated for latest country codes and formats
<PhoneInput value={value} onChange={setValue} />
You need to format 16 digits with spaces every 4 characters.
react-input-mask9999 9999 9999 9999 is easy to read.// react-input-mask: Credit Card
<InputMask mask="9999 9999 9999 9999" />
Users need to enter dollars with cents locked to 2 decimals.
react-number-format// react-number-format: Price
<NumberFormat prefix="$" decimalScale={2} fixedValue={true} />
Users from different countries need to enter phone numbers.
react-phone-number-input// react-phone-number-input: Contact
<PhoneInput value={value} onChange={setValue} />
Maintaining an old dashboard built 5 years ago.
react-text-mask// react-text-mask: Legacy
// Only use if already deeply integrated
<MaskedInput mask={[/\d/, /\d/]} />
| Package | Best For | Mask Style | Maintenance Status | React 18 Ready |
|---|---|---|---|---|
react-input-mask | General Strings | String Pattern (99/99) | ⚠️ Maintenance Mode | ⚠️ Mostly |
react-number-format | Numbers/Currency | Logic Props | ✅ Active | ✅ Yes |
react-phone-number-input | Phone Numbers | Country Logic | ✅ Active | ✅ Yes |
react-text-mask | Legacy Projects | Regex Array | ❌ Abandoned | ❌ No |
For new projects, avoid general maskers unless you have a specific need.
Use react-number-format for anything involving money or math. It prevents invalid data at the source.
Use react-phone-number-input for contact fields. Phone logic is too complex for regex masks.
Use react-input-mask only for simple static formats like dates or IDs, and test strictly with React 18.
Do not use react-text-mask in new code. It is stale and poses a risk to your stack.
Choosing the right tool here saves hours of validation logic later. Pick the specialized library over the general one whenever possible.
Choose react-input-mask for simple string patterns like dates or IDs where regex arrays feel too verbose. It uses a concise string syntax for masks. Verify React 18 compatibility before use, as strict mode can cause issues with older versions.
Choose react-number-format for any currency, percentage, or numeric input. It handles logic like decimals, prefixes, and suffixes better than regex masks. It is the safest choice for financial data entry.
Choose react-phone-number-input specifically for phone fields. It includes country logic, validation, and formatting that general maskers lack. It relies on libphonenumber-js for accuracy.
Avoid react-text-mask for new projects. It is unmaintained and lacks support for modern React features. Use only for legacy maintenance where migrating away is too costly.
Input masking component for React. Made with attention to UX. Compatible with IE8+.
npm install react-input-mask --save
Also you can use it without a module bundler
<!-- Load React first -->
<script src="https://unpkg.com/react/dist/react.min.js"></script>
<script src="https://unpkg.com/react-dom/dist/react-dom.min.js"></script>
<!-- Will be exported to window.ReactInputMask -->
<script src="https://unpkg.com/react-input-mask/dist/react-input-mask.min.js"></script>
mask : stringMask string. Default format characters are:
9: 0-9
a: A-Z, a-z
*: A-Z, a-z, 0-9
Any character can be escaped with a backslash. It will appear as a double backslash in JS strings. For example, a German phone mask with unremoveable prefix +49 will look like mask="+4\9 99 999 99" or mask={'+4\\9 99 999 99'}
maskChar : stringCharacter to cover unfilled parts of the mask. Default character is "_". If set to null or empty string, unfilled parts will be empty as in ordinary input.
formatChars : objectDefines format characters with characters as a keys and corresponding RegExp strings as a values. Default ones:
{
'9': '[0-9]',
'a': '[A-Za-z]',
'*': '[A-Za-z0-9]'
}
alwaysShowMask : booleanShow mask when input is empty and has no focus.
inputRef : functionUse inputRef instead of ref if you need input node to manage focus, selection, etc.
The following props are considered experimental because they are more prone to issues and are likely to be changed in the future. Use with caution.
beforeMaskedValueChange : functionIn case you need to implement more complex masking behavior, you can provide beforeMaskedValueChange function to change masked value and cursor position before it will be applied to the input. beforeMaskedValueChange receives following arguments:
value and selection fields. selection is null on input blur or when function is called before input mount. Example: { value: '12/1_/____', selection: { start: 4, end: 4 } }value and selection fields. selection is null on input focus or when function is called before input mount.null if user didn't enter anything (e.g. triggered by deletion or rerender due to props change).{
mask: '99/99/9999',
maskChar: '_',
alwaysShowMask: false,
formatChars: {
'9': '[0-9]',
'a': '[A-Za-z]',
'*': '[A-Za-z0-9]'
},
permanents: [2, 5] // permanents is an array of indexes of the non-editable characters in the mask
}
beforeMaskedValueChange must return an object with following fields:
selection in newState argument is null, it must be null too.Please note that beforeMaskedValueChange executes more often than onChange and must be pure.
children : functionNOTE: To make this feature more reliable, please tell about your use case in this issue
To use another component instead of regular <input /> pass render function as a children. Function receives props argument which contains props that aren't used by react-input-mask's internals. I.e. it passes down every prop except the following ones: onChange, onPaste, onMouseDown, onFocus, onBlur, value, disabled, readOnly. These properties, if used, should always be passed directly to react-input-mask instead of children and shouldn't be altered in chldren's function.
import React from 'react';
import InputMask from 'react-input-mask';
import MaterialInput from '@material-ui/core/Input';
// Will work fine
const Input = (props) => (
<InputMask mask="99/99/9999" value={props.value} onChange={props.onChange}>
{(inputProps) => <MaterialInput {...inputProps} type="tel" disableUnderline />}
</InputMask>
);
// Will throw an error because InputMask's and children's onChange aren't the same
const InvalidInput = (props) => (
<InputMask mask="99/99/9999" value={props.value}>
{(inputProps) => <MaterialInput {...inputProps} type="tel" disableUnderline onChange={props.onChange} />}
</InputMask>
);
import React from 'react';
import InputMask from 'react-input-mask';
class PhoneInput extends React.Component {
render() {
return <InputMask {...this.props} mask="+4\9 99 999 99" maskChar=" " />;
}
}
Mask for ZIP Code. Uses beforeMaskedValueChange to omit trailing minus if it wasn't entered by user:
import React from 'react';
import InputMask from 'react-input-mask';
class Input extends React.Component {
state = {
value: ''
}
onChange = (event) => {
this.setState({
value: event.target.value
});
}
beforeMaskedValueChange = (newState, oldState, userInput) => {
var { value } = newState;
var selection = newState.selection;
var cursorPosition = selection ? selection.start : null;
// keep minus if entered by user
if (value.endsWith('-') && userInput !== '-' && !this.state.value.endsWith('-')) {
if (cursorPosition === value.length) {
cursorPosition--;
selection = { start: cursorPosition, end: cursorPosition };
}
value = value.slice(0, -1);
}
return {
value,
selection
};
}
render() {
return <InputMask mask="99999-9999" maskChar={null} value={this.state.value} onChange={this.onChange} beforeMaskedValueChange={this.beforeMaskedValueChange} />;
}
}
Browser's autofill requires either empty value in input or value which exactly matches beginning of the autofilled value. I.e. autofilled value "+1 (555) 123-4567" will work with "+1" or "+1 (5", but won't work with "+1 (___) ___-____" or "1 (555)". There are several possible solutions:
maskChar to null and trim space after "+1" with beforeMaskedValueChange if no more digits are entered.Please note that it might lead to worse user experience (should I enter +1 if input is empty?). You should choose what's more important to your users — smooth typing experience or autofill. Phone and ZIP code inputs are very likely to be autofilled and it's a good idea to care about it, while security confirmation code in two-factor authorization shouldn't care about autofill at all.
Thanks to BrowserStack for the help with testing on real devices