HTML5 Pattern Attribute Regex Examples: Implementation & Debugging Guide
This recipe collects concrete, copy-ready pattern regex strings for the most common form fields — postal codes, phone numbers, usernames, emails, and more — together with the execution rules that make them behave identically across Chrome, Firefox, and Safari.
When to Use This Recipe
Use the pattern attribute when a field needs a format rule the built-in types do not enforce: a fixed-length numeric code, a username character set, a strict phone shape, or a tightened email rule. It runs synchronously in the browser, sets the patternMismatch flag on the field’s ValidityState, and requires no JavaScript. Reach for the Constraint Validation API Deep Dive instead when the rule depends on another field or on a remote check.
Throughout, the house pattern is <form novalidate> with a manual reportValidity() call: native constraints (including pattern) stay fully active, but the browser’s blocking popups are suppressed so you render accessible messaging yourself. For how pattern sits alongside the other constraint attributes and their flags, see HTML5 Input Types & Attributes.
pattern with ^(?: … )$ and tests it against the whole value — which is why explicit anchors are redundant.Execution Model: Implicit Anchoring
The native engine automatically wraps the pattern value with ^(?: and )$, so it always tests the entire value. This means you should omit ^ and $ from the attribute — they are redundant, and they become a trap when the same string is later reused in a JavaScript RegExp that has no implicit anchoring.
<!-- The browser tests this as ^(?:[A-Za-z]{3})$ — no anchors needed -->
<input type="text" pattern="[A-Za-z]{3}" title="Exactly three letters" required>
Always pair pattern with a descriptive title. The native tooltip uses title when validation fails, and it gives you ready text to mirror into an accessible message (screen readers do not reliably read title, so the ARIA mirror in the accessibility section is essential).
Production-Ready Patterns by Use Case
Each pattern below is written for the HTML attribute context (single backslashes, no anchors) and avoids constructs prone to catastrophic backtracking.
<!-- US ZIP, 5 digits or ZIP+4 -->
<input name="zip" pattern="\d{5}(-\d{4})?" title="ZIP code, e.g. 90210 or 90210-1234">
<!-- Username: letters, digits, underscore, 3–20 chars -->
<input name="username" pattern="[A-Za-z0-9_]{3,20}" title="3–20 letters, digits or underscores">
<!-- Slug: lowercase words separated by single hyphens -->
<input name="slug" pattern="[a-z0-9]+(?:-[a-z0-9]+)*" title="Lowercase words separated by hyphens">
<!-- Hex color -->
<input name="color" pattern="#[0-9A-Fa-f]{6}" title="6-digit hex color, e.g. #1a2b3c">
| Use case | Pattern | Notes |
|---|---|---|
| Email (strict TLD) | [a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,} |
Simplified RFC 5322. Prefer type="email" for the baseline; add this to require a real TLD. |
| International phone (E.164) | \+?[1-9]\d{1,14} |
Optional +, blocks a leading zero, caps at 15 digits per ITU-T E.164. |
| Strong password marker | (?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,20} |
Lookaheads require at least one letter and one digit; the lookaheads are zero-width so they don’t fight the implicit anchors. |
| UK postcode (loose) | [A-Za-z]{1,2}\d[A-Za-z\d]? ?\d[A-Za-z]{2} |
Allows the optional space; tighten per your data set. |
When you inject a pattern through JavaScript, the backslashes must be doubled in a template literal (\\d) because the string is parsed twice. In the raw HTML attribute, a single backslash (\d) is correct.
Bridging pattern to the Constraint Validation API
Under novalidate, you drive feedback with checkValidity() / reportValidity(). Read the patternMismatch flag to message specifically, and override the default with setCustomValidity() when the title text isn’t enough.
const input = document.querySelector<HTMLInputElement>('#custom-id')!;
input.addEventListener('input', () => {
if (input.validity.patternMismatch) {
input.setCustomValidity(`Format must match: ${input.title || input.pattern}`);
} else {
// CRITICAL: clear the custom flag or the field stays invalid forever.
input.setCustomValidity('');
}
});
Calling setCustomValidity() and then forgetting to reset it is the most common cause of a form that refuses to submit even after the value is corrected — the same pitfall covered in How to Use setCustomValidity Correctly.
Option Reference
Token in pattern |
Meaning | Attribute form |
|---|---|---|
\d |
a digit | single backslash |
{3,20} |
3 to 20 repetitions | as-is |
(?:…) |
non-capturing group | preferred over (…) |
(?=…) |
positive lookahead | zero-width, anchor-safe |
? |
optional (0 or 1) | as-is |
\p{L} |
any Unicode letter | needs Unicode-aware engine |
Verification Steps
- In DevTools, type an invalid value, then run
$0.validity.patternMismatchon the focused field — it should betrue. - Confirm the implicit anchoring by testing a value that contains a match but isn’t a full match (e.g.
ABCDagainst[A-Z]{3}): it must fail, proving the$anchor is applied. - Cross-check the same value against the JavaScript form of the regex with explicit anchors.
// Playwright: a partial match must be rejected by implicit anchoring
import { test, expect } from '@playwright/test';
test('pattern rejects a partial match', async ({ page }) => {
await page.goto('/form');
await page.fill('#code', 'ABCD'); // pattern="[A-Z]{3}"
await expect(page.locator('#code')).toHaveJSProperty('validity.patternMismatch', true);
});
Edge Cases & Failure Modes
Unicode property escapes aren’t universal. \p{L} works in Chromium-based engines but is unsupported in older Safari. Use an explicit range as a cross-browser fallback, or delegate to a JavaScript RegExp with the u flag and setCustomValidity().
<!-- Cross-browser safe: explicit ranges instead of \p{L} -->
<input type="text" pattern="[A-Za-zÀ-ÖØ-öø-ÿ \-]+" title="Letters, spaces and hyphens only">
HTML-special characters in the pattern. When generating a pattern programmatically, escape &, <, >, and " as entities so the attribute stays well-formed.
A non-empty title is not a substitute for ARIA. The native tooltip text is unreliable for assistive technology; mirror it as shown below.
Accessibility Notes
Synchronize aria-invalid with validity, associate a live error container via aria-describedby, and mirror the title text into that container so screen readers announce the format requirement — satisfying Error Identification (SC 3.3.1) and Error Suggestion (SC 3.3.3).
<input
type="text"
pattern="\d{4}"
title="Exactly four digits"
aria-describedby="pin-error"
aria-invalid="false"
>
<div id="pin-error" role="alert" aria-live="polite"></div>
const pin = document.querySelector<HTMLInputElement>('input[pattern="\\d{4}"]')!;
const pinError = document.getElementById('pin-error')!;
pin.addEventListener('input', () => {
const valid = pin.checkValidity();
pin.setAttribute('aria-invalid', String(!valid));
pinError.textContent = valid ? '' : (pin.title || 'Invalid format.');
});
Frequently Asked Questions
Do I need ^ and $ in a pattern attribute?
No. The browser implicitly wraps the value with ^(?: and )$, so the pattern is always tested against the entire string. Adding explicit anchors is harmless in the attribute but becomes a bug if you reuse the same string in a JavaScript RegExp, which has no implicit anchoring.
Why does my form stay invalid after the user fixes the input?
You almost certainly called setCustomValidity() with a message and never cleared it. A non-empty custom message keeps customError set regardless of the value. Always call setCustomValidity('') on the valid branch.
Can I use Unicode letters like \p{L} in a pattern?
In Chromium-based browsers, yes; in older Safari it may not be supported inside pattern. For maximum compatibility use explicit character ranges (for example [A-Za-zÀ-ÖØ-öø-ÿ]), or run a JavaScript RegExp with the u flag and report the result through setCustomValidity().
Related Guides
- HTML5 Input Types & Attributes — how
patternfits alongside the other constraint attributes. - Constraint Validation API Deep Dive — reading
patternMismatchand the rest ofValidityState. - How to Use setCustomValidity Correctly — setting and clearing custom messages safely.