react ~3 KB 0 deps v0.2.0 ↗ GitHub ↗

@mshafiqyajid/react-input-mask

Headless input mask hook + styled component. Supports custom mask patterns with digit (_), alpha (a), and alphanumeric (*) slots. Fixed literal characters are skipped automatically. Size + tone variants, label, hint, error, controlled or uncontrolled.

Playground #

Props
TSX
import { InputMaskStyled } from "@mshafiqyajid/react-input-mask/styled";
import "@mshafiqyajid/react-input-mask/styles.css";

<InputMaskStyled
  onChange={(val) => setValue(val)}
  mask="phone"
  label
/>

Install #

npm install @mshafiqyajid/react-input-mask

Quick start #

import { InputMaskStyled } from "@mshafiqyajid/react-input-mask/styled";
import "@mshafiqyajid/react-input-mask/styles.css";

const [phone, setPhone] = useState("");

<InputMaskStyled
  mask="(___) ___-____"
  label="Phone number"
  value={phone}
  onChange={(value, rawValue) => setPhone(value)}
/>

Mask format #

Define your pattern using these special characters:

CharacterAccepts
_Digit (0–9)
aLetter (a–z, A–Z)
*Alphanumeric (letter or digit)
Any other characterFixed literal — cannot be deleted, cursor skips over it
// Phone
<InputMaskStyled mask="(___) ___-____" />

// Date
<InputMaskStyled mask="__/__/____" />

// Time
<InputMaskStyled mask="__:__" />

// Credit card
<InputMaskStyled mask="____ ____ ____ ____" />

// Plate (2 letters + 4 digits)
<InputMaskStyled mask="aa-____" />

Custom mask character #

// Use • instead of _ for empty slots
<InputMaskStyled mask="__/__/____" maskChar="•" />

Lazy mode #

By default (lazy is true), mask placeholder characters are only shown up to the next slot to fill. The field looks clean until the user starts typing. Set lazy={false} to always show the full mask — useful for credit card fields that should show the entire pattern immediately.

// Always show the full mask pattern (eager mode)
<InputMaskStyled mask="____ ____ ____ ____" lazy={false} label="Card number" />

// Default: only show mask as far as the user has typed (lazy mode)
<InputMaskStyled mask="____ ____ ____ ____" label="Card number" />

showMask #

Set showMask={false} to hide the maskChar fill characters entirely. Only the typed characters are displayed — useful when you want mask validation without visual placeholders.

<InputMaskStyled mask="(___) ___-____" showMask={false} label="Phone" />

Custom format chars #

Override or extend the default format character map with formatChars. Keys are the characters used in the mask string; values are RegExp patterns that a typed character must match.

// Hex input: H accepts [0-9A-Fa-f]
<InputMaskStyled
  mask="HH:HH:HH"
  formatChars={{ H: /[0-9A-Fa-f]/ }}
  label="MAC segment"
/>

// Pin with uppercase letters only
<InputMaskStyled
  mask="UUUU"
  formatChars={{ U: /[A-Z]/ }}
  label="PIN"
/>

Auto-unmask #

When autoUnmask is true, the first argument passed to onChange is the raw value (no mask chars) instead of the formatted display value. The second argument remains rawValue as usual.

// onChange receives "5551234567" instead of "(555) 123-4567"
<InputMaskStyled
  mask="(___) ___-____"
  autoUnmask
  onChange={(val, raw) => console.log(val)} // val === raw === "5551234567"
/>

Prefix and suffix #

Use prefix and suffix to render non-editable decorative text inside the input wrapper — before or after the input field.

<InputMaskStyled mask="___-____" prefix="$" label="Amount" />
<InputMaskStyled mask="__/__/____" suffix="cal" label="Date" />

Validation #

Pass an error string to flip tone to danger and show the message below the field. Use hint for helper text.

<InputMaskStyled
  mask="__/__/____"
  label="Date of birth"
  value={value}
  onChange={(val, raw) => setValue(val)}
  error={raw.length > 0 && raw.length < 8 ? "Enter a complete date" : undefined}
  hint="Format: MM/DD/YYYY"
/>

Headless #

import { useInputMask } from "@mshafiqyajid/react-input-mask";

const { inputProps, value, rawValue, isComplete, isFocused } = useInputMask({
  mask: "(___) ___-____",
  onChange: (value, rawValue) => console.log(value, rawValue),
  onComplete: (value, rawValue) => submitForm(rawValue),
});

return <input {...inputProps} />;

API — InputMaskStyled #

PropTypeDefaultDescription
maskstringMask pattern string (required)
maskCharstring"_"Fill character shown in empty slots
valuestringControlled formatted value
defaultValuestring""Uncontrolled initial value
onChange(value: string, rawValue: string) => voidCalled on every change
onAccept(value: string, rawValue: string) => voidCalled when a character is accepted
onComplete(value: string, rawValue: string) => voidCalled when all slots are filled
onFocus(e: React.FocusEvent) => voidFocus event callback
onBlur(e: React.FocusEvent) => voidBlur event callback
allowedCharsRegExpOverride default per-slot character acceptance
formatCharsRecord<string, RegExp>Custom format character map; merged with defaults (_=digit, a=alpha, *=any)
lazybooleantrueWhen true, mask chars are only shown up to the next empty slot; when false, full mask is always visible
showMaskbooleantrueWhen false, hides the maskChar fill characters — only typed characters are shown
autoUnmaskbooleanfalseWhen true, the first onChange argument is the raw value without mask chars
prefixstringNon-editable text rendered before the input inside the wrapper
suffixstringNon-editable text rendered after the input inside the wrapper
size"sm" | "md" | "lg""md"Size variant
tone"neutral" | "primary" | "success" | "danger""neutral"Color tone
disabledbooleanfalseDisable the input
readOnlybooleanfalseRead-only mode
requiredbooleanfalseMark required, adds asterisk to label
invalidbooleanfalseApply invalid/error styling
labelstringLabel rendered above the field
hintstringHelper text below the field
errorstringError message — auto-applies danger tone + aria-invalid

API — useInputMask #

ReturnTypeDescription
inputPropsobjectSpread onto your <input>
valuestringFormatted display value (with mask chars)
rawValuestringUser-entered characters only, no mask chars
isCompletebooleanTrue when all slots are filled
isFocusedbooleanTrue when the input is focused
Edit this page on GitHub