Pattern syntax

CharacterMatches
9Digit (09)
ZASCII letter (az, AZ)
AASCII alphanumeric (digit or letter)
Custom tokenIts local matcher and optional transform
{n} / {min,max}Repeats the token before it — see bounded quantifiers
\Escapes a token or another backslash
Anything elseLiteral — inserted as the user fills slots

Array masks: pass patterns shortest → longest by data capacity; the active one is picked from how many data characters are accepted, not a prefix or card network. Use resolveMask to choose a layout from content instead.

Bounded quantifiers#

A slot token may be followed by a bounded repeat count. {n} is exactly n occurrences; {min,max} is anywhere from min to max.

9{4}     exactly four digits
9{1,2}   one or two digits
Z{2,4}   two to four letters
A{1,8}   one to eight alphanumeric characters

{n} is shorthand — 9{4} and 9999 compile to the same mask. {min,max} adds a variable-width segment, which is what lets a date accept a one- or two-digit day and month without padding:

bind(date, '9{1,2}/9{1,2}/9{4}')
// 3/4/1986   3/12/1986   12/4/1986   12/12/1986

The separator is how the user picks a width. Typing "3/" commits the one-digit first segment: once a ranged segment reaches its minimum, typing the literal that follows it ends that segment, and the separator stays visible — it is input rather than decoration, so this holds with eager: false too. Typing "12" instead reaches the maximum and may reveal "/" eagerly, exactly as a fixed 99 segment does. Reaching the minimum alone never inserts anything: after "3" the value is still "3", because the next keystroke could be a second digit.

Any separator ends the segment, and the mask prints its own. A ranged segment is the one place a mask cannot work out its own boundary, so a person saying "this field is done" gets to say it with whichever divider is under their thumb — a keypad ., a -, a space — not only the one the pattern happens to spell.

bind(date, '9{1,2}/9{1,2}/9{4}')
// type "3.4.1986" → "3/4/1986"
// type "3-4-1986" → "3/4/1986"
// type "3 4 1986" → "3/4/1986"

Any Unicode punctuation, symbol, or space works, and each one behaves exactly as the mask's own separator does — same value, same caret. Letters, digits, and other scripts do not: a mistyped "a" in a date field is a typo, not a decision, so it stays the noise it always was. Neither does a character this mask's own alphabet accepts — a custom token matching "." makes "." content in that mask, never a boundary. The rule reaches exactly as far as the ambiguity it resolves: a segment only reads a separator this way once it is at or past its minimum and still short of its maximum, so a pattern with no {min,max} segment is completely unaffected — under '99/99/9999', "4." and "4/" alike give "4".

Closing a segment early retires the slots it did not use, so a finished value can be shorter than the pattern’s maximum: "3/4/1986" is complete at eight characters even though getMaxLength reports 10. Anything typed past that point is dropped rather than repacked — the boundaries the user set hold, and the character that no longer fits falls off the end, exactly as an extra digit does on a full fixed mask. maxlength alone is therefore not a completeness check for a ranged mask; inspect the value if you need one.

Mother Mask does not validate dates, or anything else semantic — it never inspects a value to decide that "34" cannot be a day and must mean 3/4. A quantifier is a width rule; explicit separators are how a user says a segment is shorter than its maximum.

Only bounded forms are syntax. *, +, ?, {n,}, {,n}, {0} and {2,1} are not, and neither is a repeat count above 1000; those brace sequences stay literal text. A quantifier is only read directly after an unescaped token, so '\9{1,2}' is the literal text 9{1,2}. getMaxLength and the maxlength bind sets use the compiled maximum (10 for '9{1,2}/9{1,2}/9{4}'), never the length of the pattern source, and array masks still select by compiled slot capacity.

Escaped literals#

bind(input, '\\A-999999') // '123456' → 'A-123456'
bind(input, '\\9-99')     // '12' → '9-12'
bind(input, '\\Z-99')     // '12' → 'Z-12'
bind(input, '\\\\99')      // a literal backslash, then two digits

Backslash escapes a built-in or custom token, or another backslash. Before any other character, or at the end of a pattern, it remains literal. JavaScript string literals need an extra backslash as shown above. Older patterns using backslash immediately before a token must double it to keep the backslash in the output.

Complete literal runs are formatting at their boundaries; escaped runs remain formatting after a segment shrinks. If a literal also matches the data alphabet, raw and formatted values can be ambiguous. Use a distinct separator, such as '\9-99', and avoid resolver literals that look like data but are absent from the fallback pattern.