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.

Implicit anchoring of the pattern attribute The pattern value is implicitly wrapped with a start and end anchor, then tested against the entire input value, producing a match or a patternMismatch. pattern="[A-Z]{3}" your value ^(?:[A-Z]{3})$ browser anchors it full match → valid patternMismatch
The browser wraps your 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

  1. In DevTools, type an invalid value, then run $0.validity.patternMismatch on the focused field — it should be true.
  2. Confirm the implicit anchoring by testing a value that contains a match but isn’t a full match (e.g. ABCD against [A-Z]{3}): it must fail, proving the $ anchor is applied.
  3. 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().

← Back to HTML5 Input Types & Attributes