TypeScript input masking

TypeScript input masks for browser forms.

Bind a pattern to any input and keep edits, selections, and pasted values formatted without a framework dependency.

npm install mother-mask
Live preview bind()
bind(input, '(999) 999-9999')

Lightweight JavaScript input masking

mother-mask formats form fields as people type, paste, select, and replace text. Use it in plain browser apps or alongside any UI framework because the package only needs an HTMLInputElement.

Pattern masks

Add phone, date, CPF, CNPJ, CEP, SSN, ZIP+4, postal code, IBAN, VAT ID, license plate, credit card, and custom input masks with bind().

Decimal and currency masks

Format money, quantities, localized separators, prefixes, suffixes, and negative values with bindDecimal() and decimal helper exports.

Small browser package

Ships with zero runtime dependencies, TypeScript types, ESM, CJS, and UMD builds for npm installs or direct CDN usage.

Examples

Every field below is live.

CPF

bind(input, '999.999.999-99') 11 digits

CNPJ (alphanumeric)

bind(input, 'AA.AAA.AAA/AAAA-99') 12 alphanumeric + 2 digits

CEP

bind(input, '99999-999') 8 digits

Phone — array mask

bind(input, ['(99) 9999-9999', '(99) 99999-9999']) 10 or 11 digits — mask switches automatically

Date — segmented (default)

bind(input, '99/99/9999') select "12" and retype — day and year stay put

Date — flat (segmented: false)

bind(input, '99/99/9999', { segmented: false }) same edit here pulls the year forward

Time

bind(input, '99:99') simple HH:MM bind mask

License plate

bind(input, 'ZZZ-9999', { segmented: false }) letters then digits

Mercosul plate

bind(input, 'ZZZ-9Z99') letter/digit mixed pattern

Credit card — array mask

bind(input, ['9999 999999 99999', '9999 9999 9999 9999']) Amex (15) vs Visa/Mastercard (16)

Currency (USD-style)

bindDecimal(input, { prefix: '$' })

Currency (EUR-style)

bindDecimal(input, { separator: '.', decimalSeparator: ',', suffix: ' €' }) "." also opens the fraction

Whole numbers

bindDecimal(input, { decimalPlaces: 0, suffix: ' units' })

Negative values

bindDecimal(input, { prefix: '$', allowNegative: true }) type "-" anywhere to flip the sign

Masked value vs. raw value

bind(input, '999.999.999-99', (value) => { masked, raw })
masked
raw digits

Regional formats

A few common US, Canada, and Europe patterns — all just bind() calls.

US — Phone

bind(input, '(999) 999-9999') 10 digits

US — SSN

bind(input, '999-99-9999') 9 digits

US — ZIP+4

bind(input, '99999-9999') 5, or 9 with the +4 suffix

Canada — postal code

bind(input, 'Z9Z 9Z9') 6 alphanumeric characters

Canada — SIN

bind(input, '999 999 999') 9 digits

Europe (DE) — IBAN

bind(input, 'ZZ99 9999 9999 9999 9999 99') 2 letters + 20 digits

Europe (DE) — VAT ID

bind(input, 'DE999999999') 9 digits after the DE prefix

Europe (PL) — postal code

bind(input, '99-999') 5 digits

Pattern syntax

Character Matches
9 Digit (09)
Z Letter (az, AZ)
A Alphanumeric (digit or letter)
Anything else Literal — inserted as the user fills slots

Array masks: pass patterns shortest → longest; the active one is picked from how many data characters are typed (see phone and credit card above).

UMD / CDN

<script src="https://unpkg.com/mother-mask/dist/mother-mask.umd.js"></script>
<script>
  const dispose = MotherMask.bind(document.getElementById('cpf'), '999.999.999-99')
</script>

The global name is MotherMask.

API reference

bind (primary)

Signature bind(input, mask, options?)
Returns () => void — removes listeners and attributes so the input can be bound again
Third argument { onChange?: (value: string) => void }, or a legacy (value) => void callback

Other exports

Export Description
buildMask(value, mask, caret?, options?) Build a Mask instance (array mask resolved to one string first).
getMaxLength(mask) Maximum string length for the mask (longest pattern for array masks).
applyMask(value, mask, inputCaret?, options?) Low-level: apply a single mask string; returns { value, caret }.
process(value, mask, options?) Apply a mask pattern to a raw value, returning just the masked string.

bindDecimal

Signature bindDecimal(input, options?)
Returns () => void — removes listeners and attributes so the input can be bound again
Second argument DecimalMaskOptions & { onChange?: (value: string, numericValue: number) => void }, or a legacy (value, numericValue) => void callback

Other decimal exports

Export Description
applyDecimalMask(value, inputCaret?, options?) Low-level: format a raw/already-masked value; returns { value, caret }.
processDecimal(value, options?) Apply a decimal mask to a raw value, returning just the masked string.
unmaskDecimal(value, options?) Parse a raw or masked decimal string back into a JS number (0 if it has no digits).
formatDecimalValue(value, options?) Format a plain JS number into its masked display string — useful to pre-populate an input.

Types

type MaskPattern = string | string[]

interface MaskResult {
  readonly value: string
  readonly caret: number
}

interface ApplyMaskOptions {
  segmented?: boolean // hard field boundaries — on by default
}

interface BindOptions extends ApplyMaskOptions {
  onChange?: (value: string) => void
}

interface DecimalMaskOptions {
  decimalPlaces?: number      // default 2
  segmented?: boolean         // group into thousands — default true
  separator?: string          // thousands separator — default ','
  decimalSeparator?: string   // default '.'
  prefix?: string             // default ''
  suffix?: string             // default ''
  allowNegative?: boolean     // default false
}

interface BindDecimalOptions extends DecimalMaskOptions {
  onChange?: (value: string, numericValue: number) => void
}