Pattern syntax
| Character | Matches |
|---|---|
9 | Digit (0–9) |
Z | ASCII letter (a–z, A–Z) |
A | ASCII alphanumeric (digit or letter) |
| Custom token | Its local matcher and optional transform |
{n} / {min,max} | Repeats the token before it — see bounded quantifiers |
\ | Escapes a token or another backslash |
| Anything else | Literal — 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/1986The 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 digitsBackslash 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.