react-input-mask vs react-number-format vs react-phone-number-input vs react-text-mask
Specialized Input Masking and Formatting Libraries for React
react-input-maskreact-number-formatreact-phone-number-inputreact-text-maskSimilar Packages:

Specialized Input Masking and Formatting Libraries for React

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.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
react-input-mask02,290-1398 years agoMIT
react-number-format04,084244 kB2266 days agoMIT
react-phone-number-input0-10.1 MB-a month agoMIT
react-text-mask08,22948.8 kB333-Unlicense

React Input Masking Libraries: Architecture, Maintenance, and Use Cases

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.

🎭 Mask Definition Style: Strings vs Regex vs Logic

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.
  • Easy to read for simple formats like dates or credit cards.
// 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.

  • More flexible but verbose.
  • Allows custom validation per character.
// 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.

  • You define prefixes, suffixes, and decimal scales.
  • No regex needed for numbers.
// 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.

  • You don't define the mask manually.
  • It changes based on the selected country.
// react-phone-number-input: Country based
import PhoneInput from 'react-phone-number-input';

<PhoneInput value={value} onChange={setValue} />
// User sees: +1 (___) ___-____ (depending on country)

🔄 Value Management: Controlled Components

All four libraries work with React's controlled component pattern, but they handle events differently.

react-input-mask passes standard onChange events.

  • You manage the state yourself.
  • Value is always a string.
// 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.

  • Similar to react-input-mask but with regex mask.
  • Value is a string.
// 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.
  • Crucial for saving clean data to the backend.
// 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.

  • onChange returns the full phone string with country code.
  • Simplifies validation.
// react-phone-number-input: E.164 string
const [value, setValue] = useState('');

<PhoneInput 
  value={value} 
  onChange={setValue} 
/>
// value is like "+14155552671"

🛠️ Maintenance & React Version Support

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.

  • Works with React 17 and mostly 18.
  • Can have issues with React 18 Strict Mode due to double rendering.
  • No major feature updates recently.
// react-input-mask: Potential Strict Mode warning
// May require useEffect workarounds in React 18
<InputMask mask="999" />

react-text-mask is effectively abandoned.

  • No updates in several years.
  • Does not support modern React patterns well.
  • High risk for security or compatibility bugs.
// react-text-mask: Legacy warning
// Do not use in new greenfield projects
<MaskedInput mask={[/\d/]} />

react-number-format is actively maintained.

  • Regular updates for React 18 and 19 compatibility.
  • Strong community support.
  • Safe for long-term projects.
// react-number-format: Active maintenance
// Regularly updated for new React versions
<NumberFormat format="####" />

react-phone-number-input is actively maintained.

  • Updates frequently to match phone number standards.
  • Dependencies like libphonenumber-js are kept current.
  • Safe for production use.
// react-phone-number-input: Active maintenance
// Updated for latest country codes and formats
<PhoneInput value={value} onChange={setValue} />

🌐 Real-World Scenarios

Scenario 1: Credit Card Entry

You need to format 16 digits with spaces every 4 characters.

  • Best choice: react-input-mask
  • Why? Simple string mask 9999 9999 9999 9999 is easy to read.
// react-input-mask: Credit Card
<InputMask mask="9999 9999 9999 9999" />

Scenario 2: Price Input

Users need to enter dollars with cents locked to 2 decimals.

  • Best choice: react-number-format
  • Why? Handles math logic, not just string patterns.
// react-number-format: Price
<NumberFormat prefix="$" decimalScale={2} fixedValue={true} />

Scenario 3: International Contact Form

Users from different countries need to enter phone numbers.

  • Best choice: react-phone-number-input
  • Why? Handles country codes and validation automatically.
// react-phone-number-input: Contact
<PhoneInput value={value} onChange={setValue} />

Scenario 4: Legacy Admin Panel

Maintaining an old dashboard built 5 years ago.

  • Best choice: react-text-mask
  • Why? If already used, keep it to avoid refactoring costs.
// react-text-mask: Legacy
// Only use if already deeply integrated
<MaskedInput mask={[/\d/, /\d/]} />

📊 Summary Table

PackageBest ForMask StyleMaintenance StatusReact 18 Ready
react-input-maskGeneral StringsString Pattern (99/99)⚠️ Maintenance Mode⚠️ Mostly
react-number-formatNumbers/CurrencyLogic Props✅ Active✅ Yes
react-phone-number-inputPhone NumbersCountry Logic✅ Active✅ Yes
react-text-maskLegacy ProjectsRegex Array❌ Abandoned❌ No

💡 Final Recommendation

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.

How to Choose: react-input-mask vs react-number-format vs react-phone-number-input vs react-text-mask

  • react-input-mask:

    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.

  • react-number-format:

    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.

  • react-phone-number-input:

    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.

  • react-text-mask:

    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.

README for react-input-mask

react-input-mask

Build Status npm version npm downloads

Input masking component for React. Made with attention to UX. Compatible with IE8+.

Demo

Table of Contents

Install

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>

Properties

mask : string

Mask 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 : string

Character 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 : object

Defines 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 : boolean

Show mask when input is empty and has no focus.

inputRef : function

Use inputRef instead of ref if you need input node to manage focus, selection, etc.

Experimental :fire:

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

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

  1. newState (object): New input state. Contains 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 } }
  2. oldState (object): Input state before change. Contains value and selection fields. selection is null on input focus or when function is called before input mount.
  3. userInput (string): Raw entered or pasted string. null if user didn't enter anything (e.g. triggered by deletion or rerender due to props change).
  4. maskOptions (object): Mask options. Example:
{
  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:

  1. value (string): New value.
  2. selection (object): New selection. If 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 : function

NOTE: 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>
);

Examples

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} />;
  }
}

Known Issues

Autofill

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:

  1. Set maskChar to null and trim space after "+1" with beforeMaskedValueChange if no more digits are entered.
  2. Apply mask only if value is not empty. In general, this is the most reliable solution because we can't be sure about formatting in autofilled value.
  3. Use less formatting in the mask.

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

Thanks to BrowserStack for the help with testing on real devices