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).

UK phone number

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>

UK postcode

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 (sw1a1aaSW1A1AA) 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>

UPN (Unique Pupil Number)

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>

Custom masks

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>

Invalid error

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

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" …>