MASKED INPUT: Format-as-you-type on a plain .form-control
VARIANTS: UK phone | UK postcode | UPN | Custom mask | Invalid error | Disabled
COMPOSES: .form-control + .form-label + .form-text + .is-invalid/.invalid-feedback
Formatted identifiers take their canonical shape while the user types — group separators
are inserted automatically, letters uppercase where the format demands it, and messy
pasted values are stripped and reformatted. The caret is preserved through mid-value
edits, Backspace always deletes a real character (never bounces off a separator), and
partial input is never rejected (C05). Requires
data-controller="masked-input" with a declarative
data-masked-input-mask-value template (# digit,
A letter, * either; anything else is a literal).
Mask "##### ######": UK mobile grouping (5+6). Typing
07123456789 renders 07123 456789; pasting
"Tel: 07123-456-789" strips to digits and reformats. The optional hidden
data-masked-input-target="raw" input carries the unformatted digits
(07123456789) for systems that want the canonical value — the visible
input is the submitted field.
UK mobile number, e.g. 07123 456789. We'll format it as you type.
<div class="masked-input" data-controller="masked-input" data-masked-input-mask-value="##### ######"><input type="tel" class="form-control" autocomplete="tel" aria-describedby="…" data-masked-input-target="input" data-action="input->masked-input#input keydown->masked-input#keydown compositionend->masked-input#input"><input type="hidden" data-masked-input-target="raw"></div>
Format "uk-postcode": the one named algorithmic formatter —
UK postcodes are variable-geometry (outward part 2–4 characters, inward always 3)
and can't be expressed as a fixed template. Letters uppercase as typed
(sw1a1aa → SW1A1AA) and the canonical space appears once
the shape completes (SW1A 1AA) — mid-entry the space never hops around and
partial input is never fought.
For example SW1A 1AA. Used to check your catchment area.
<div class="masked-input" data-controller="masked-input" data-masked-input-format-value="uk-postcode"><input class="form-control" autocomplete="postal-code" …></div>
Mask "A############": one letter (uppercased on entry)
followed by 12 digits — no literals, so the formatted and raw values are identical.
A digit typed in the leading letter slot is discarded (character-class enforcement),
and characters beyond 13 are dropped.
A letter followed by 12 digits, e.g. A123456789012. You'll find it on your child's school report or by asking their current school.
<div class="masked-input" data-controller="masked-input" data-masked-input-mask-value="A############"><input class="form-control" …></div>
The mask is declarative: any fixed-shape code works by supplying a
template — nothing is hardcoded in the controller. Here a bank sort code
("##-##-##") shows non-space literals being inserted and deleted
transparently: Backspace after a dash deletes the digit before it in one keypress.
6 digits, e.g. 20-45-67. Shown on your bank card or statement.
<div class="masked-input" data-controller="masked-input" data-masked-input-mask-value="##-##-##"><input class="form-control" inputmode="numeric" …></div>
Error state: markup-only .is-invalid +
aria-invalid="true" on the input (reusing
.form-control.is-invalid — no bespoke error CSS), with
aria-describedby pointing at the .invalid-feedback text.
The controller never sets the error (validity is the form's decision at
submit); the first correcting keystroke clears it and hides the error text (C08).
Enter a full UK postcode, e.g. SW1A 1AA.
<input class="form-control is-invalid" aria-invalid="true" aria-describedby="postcode-error" …> … <p class="invalid-feedback" id="postcode-error" data-masked-input-target="error">Enter a full UK postcode, e.g. SW1A 1AA.</p>
Disabled: real [disabled] attribute — greys out via the
shared .form-control:disabled rule, no masked-input-specific disabled CSS.
A server-rendered value is formatted once on connect.
This number is verified — contact the school office to change it.
<input type="tel" class="form-control" value="07123456789" disabled data-masked-input-target="input" …>