Skip to main content

HTML Pattern Attribute

Sometimes "not empty" and "the right type" are not enough. A product code must look like ABC-1234. A username must use only certain characters. A ZIP code must be five digits. The pattern attribute lets you describe the exact shape of the text with a regular expression, and the browser checks it for you.

Used well, pattern catches typos early. Used badly, it locks out real people with unusual but perfectly valid names. This lesson shows you how to write patterns, how to explain them to visitors, and when to leave them out.


What You Will Learn​

  • How to add pattern and how the browser reads it
  • Which controls support it
  • The anchoring rule that surprises many beginners
  • Writing a helpful title
  • Common, safe patterns
  • Why strict patterns for names and phones fail global visitors
  • Reading the patternMismatch flag
  • Why the server must check again

The Basics​

A regular expression (regex) is a short code that describes a shape of text. For example, [0-9]{5} means "exactly five digits."

index.html
<form action="/register" method="post">
<label for="code">Product code (like ABC-1234)</label>
<input
type="text"
id="code"
name="code"
pattern="[A-Z]{3}-[0-9]{4}"
title="Three capital letters, a hyphen, and four digits, like ABC-1234"
required
/>

<button type="submit">Check</button>
</form>

Type abc and press Check: the browser blocks the form and shows a message that includes your title text. Type ABC-1234 and it passes.

Where pattern Works​

ControlSupports pattern?
type="text", search, tel, url, email, passwordYes
type="number", date, time, range, colorNo (use min, max, and step)
type="checkbox", radio, file, hiddenNo
<textarea>No (use minlength and maxlength, or script)
<select>No

Three Rules to Remember​

1. Write the pattern only, with no slashes​

In JavaScript you might write /[0-9]{5}/. In HTML, leave out the slashes and flags:

<input pattern="[0-9]{5}" />    <!-- correct -->
<input pattern="/[0-9]{5}/" /> <!-- wrong: the slashes become part of the pattern -->

2. The pattern must match the whole value​

The browser anchors the pattern for you, as if it were wrapped in ^(?: and )$. So [0-9]{5} matches 90210, but not 902101 or x90210. You do not need to write ^ and $.

This is different from regex in JavaScript's .test(), which finds a match anywhere. If you want to allow extra text, say so with .*:

<!-- contains at least one non-space character -->
<input pattern=".*\S.*" />

3. It is skipped when the field is empty​

An empty value never fails pattern. Add required if you also need a value. (See Required.)

Try It: Pick a Rule​

Choose a rule, type values, and watch whether they match. Press Submit with a wrong value to see the browser's own message, including the title.

index.html
<label for="field">Product code</label>
<input
type="text"
id="field"
name="value"
pattern="[A-Z]{3}-[0-9]{4}"
title="Three capital letters, a hyphen, and four digits, like ABC-1234"
aria-describedby="hint"
/>
<button type="submit">Submit</button>
<p id="hint">Three capital letters, a hyphen, and four digits, like ABC-1234</p>
script.js
const field = document.getElementById("field");

field.addEventListener("input", () => {
console.log("patternMismatch:", field.validity.patternMismatch);
});

A Small Regex Cheat Sheet​

PieceMeaningExampleMatches
[abc]Any one of these characters[aeiou]e
[a-z]Any character in a range[0-9]7
\dA digit (0 to 9)\d\d42
\SAny character that is not a space\S+hello
.Any single charactera.cabc
?Zero or one of the previouscolou?rcolor, colour
+One or more[a-z]+hello
*Zero or more.*anything
{n}Exactly n[0-9]{4}2026
{n,m}Between n and m[a-z]{3,8}ada
|Orcat|dogdog
( )Group(ab)+abab
\p{L}Any letter in any language\p{L}+José, 李雷
Escape special characters

To match a literal ., +, ?, (, or - inside a character class, put a backslash in front: \., \+, \-. Modern browsers read pattern in a stricter mode, where an unescaped - in a class can make the whole pattern invalid. An invalid pattern is silently ignored, so the field would accept anything. Always test your pattern.

Always Write a title​

When a pattern fails, the browser says something like "Please match the requested format." That does not help anyone. The title text is added to that message, so use it to explain the rule in plain words.

<input
type="text"
name="username"
pattern="[a-z0-9_]{3,16}"
title="3 to 16 characters: lowercase letters, digits, or underscores"
/>

Better still, show the rule on the page before the visitor makes a mistake, and link it to the field:

<label for="username">Username</label>
<input type="text" id="username" name="username"
pattern="[a-z0-9_]{3,16}" aria-describedby="username-hint" />
<p id="username-hint">3 to 16 characters: lowercase letters, digits, or underscores.</p>

Many screen readers and phones do not show title text well, so the visible hint is the reliable one.

Handy Patterns​

UsePatternNotes
Product or order code[A-Z]{3}-[0-9]{4}Fixed shapes are ideal for pattern
Username[a-z0-9_]{3,16}Your own system, so you can choose the rules
5-digit US ZIP[0-9]{5}Only for US addresses (see below)
Hex colour#[0-9a-fA-F]{6}Or use type="color"
Digits only (like a PIN)[0-9]{4,6}Also add inputmode="numeric"
At least one non-space.*\S.*Catches a field of only spaces
Letters from any language[\p{L}\p{M}' .\-]+A generous name check (see below)

For PINs and codes that are digits but not quantities, use type="text" with inputmode="numeric" and pattern, not type="number". (See Number.)

Be Careful With Names, Phones, and Addresses​

The most common mistake with pattern is being too strict about real-world data. Look at what a typical "letters only" rule does:

http://127.0.0.1:5500/index.html
Name[A-Za-z ]+Letters from any language
Ada Lovelaceacceptedaccepted
José Garcíarejectedaccepted
Nguyễn Thị Minhrejectedaccepted
O'Brienrejectedaccepted
李雷rejectedaccepted
Åsa Öbergrejectedaccepted
Anne-Marierejectedaccepted

The strict rule [A-Za-z ]+ rejects José, Nguyễn, O'Brien, Anne-Marie, and every name written in a non-Latin script. These are real people, and many of them would give up on your form.

Safer approaches:

  • Do not validate names at all beyond "not empty." Names are far more varied than any rule.
  • If you must check, use a generous rule: letters from any language (\p{L}), marks (\p{M}), spaces, apostrophes, hyphens, and dots.
  • Phone numbers: formats differ by country, with +, spaces, brackets, and extensions. Use type="tel" with no pattern, or normalise on the server. (See Tel.)
  • Postal codes: they differ everywhere (digits, letters, spaces, or none at all). Choose the rule after the visitor picks a country, or skip the pattern.
  • Email addresses: use type="email". Writing your own email regex almost always rejects valid addresses.
A good test

Before you add a pattern for personal data, ask: "Could a real person in another country, with a valid answer, be blocked by this?" If yes, loosen it or drop it.

Reading and Using patternMismatch​

When a pattern fails, validity.patternMismatch is true. You can use it to write your own message.

const input = document.getElementById("code");

input.addEventListener("input", () => {
if (input.validity.patternMismatch) {
input.setCustomValidity("Use the format ABC-1234 (three capital letters, a hyphen, four digits).");
} else {
input.setCustomValidity("");
}
});

Remember to clear the message with an empty string, or the field stays invalid. For the full toolkit, see Form Validation.

Styling​

style.css
input:user-invalid {
border-color: #b91c1c;
}

input:user-valid {
border-color: #15803d;
}

Use :user-invalid, not :invalid, so the field does not turn red before the visitor has typed. And never use colour alone: show a text message too.

Accessibility at a Glance​

  • Show the rule in visible text next to the field, linked with aria-describedby.
  • Write a title, but do not depend on it.
  • Do not rely on the placeholder to show the format. It disappears when typing. A placeholder can show an example, but the rule belongs in a hint.
  • Give a clear error that says what to change, not "Invalid format."
  • Keep rules simple. Complicated rules ("one capital, one symbol, no repeats...") are hard to remember, so list them.
  • Do not reject spaces that visitors paste or type at the end of a value (such as a code copied from an email). Trim them for the visitor.

Security Notes​

  • pattern is not security. Anyone can remove the attribute or send a request by hand. Validate on the server with the same (or stricter) rule. (See Secure Form Design.)
  • Patterns are not a defence against injection. Allowing only "safe" characters can help, but the real defences are escaping output and parameterised queries on the server.
  • Beware of slow regular expressions. Some patterns with nested repeats, like (a+)+$, can take enormous time on certain input. This is called ReDoS. Keep patterns simple, and be extra careful with regex on the server, where it can freeze your application.
  • Do not trust a pattern for passwords. Complex composition rules do not make passwords safer. Length matters most. (See Password.)

Global Audience Notes​

  • Use \p{L} instead of A-Za-z if letters may come from any language.
  • Digits are not always 0-9. Some visitors, especially with Arabic or Indic keyboards, may type digits in other scripts. Consider converting them on the server.
  • Case: patterns are case-sensitive. If abc and ABC should both be fine, write both ranges, or lowercase the value on the server.
  • Do not force one format for dates and numbers. Use the proper input types so the browser handles local formats.
  • Length is counted in characters of the browser's string, which may count an emoji or a combined character as more than one. Keep length rules loose.
  • Write the hint in the visitor's language, with an example that makes sense for them.

Common Mistakes to Avoid​

  1. Relying on pattern for security. The server must validate, too.
  2. Writing the slashes (/[0-9]+/) from JavaScript.
  3. Adding ^ and $ and expecting partial matches. The whole value must always match.
  4. An invalid pattern (such as an unescaped -) that is silently ignored, so nothing is checked. Test it.
  5. Forgetting required. An empty value always passes.
  6. No title or visible hint, so the error is a mystery.
  7. Too-strict patterns for names, phones, and addresses.
  8. Using pattern on a textarea, select, or number input, where it does nothing.
  9. Writing your own email regex instead of type="email".
  10. Using type="number" for codes and then adding a pattern. Use type="text" with inputmode="numeric".
  11. Slow, nested patterns that invite ReDoS.
  12. Showing the format only as a placeholder.

Practice Time​

Try these in your editor:

  1. Write a pattern for a product code like ABC-1234, with a title.
  2. Type a value with an extra character at the end, and see that the whole value must match.
  3. Add required and compare an empty field with and without it.
  4. Write a username pattern of 3 to 16 lowercase letters, digits, or underscores.
  5. Read validity.patternMismatch in the console as you type.
  6. Replace the browser message with your own using setCustomValidity, and clear it correctly.
  7. Make a PIN field with inputmode="numeric" and pattern="[0-9]{4,6}".
  8. Write the "at least one non-space character" pattern and test a field of spaces.
  9. Test [A-Za-z ]+ against José, then switch to a \p{L} pattern.
  10. Bonus: write the server-side check for your product code in a language you know.

Quick Recap​

Click a question to check your answer.

1. What does the pattern attribute do?

It gives a regular expression that the whole value of a text-like input must match. If it does not match, the browser blocks the submit and shows an error.

2. Do you write slashes or ^ and $ in pattern?

No. You write only the pattern, such as [0-9]{5}. The browser anchors it automatically so the entire value must match.

3. Does an empty field fail a pattern?

No. Empty values skip the check. Add required if a value is mandatory.

4. Why should you write a title, and what is better than a title?

The title is added to the browser's error message to explain the rule. A visible hint linked with aria-describedby is better, because title text is not shown reliably on phones or to every assistive technology.

5. Why are strict patterns for names and phone numbers a bad idea?

Real names and numbers are far more varied than most patterns allow. A rule like [A-Za-z ]+ rejects accents, apostrophes, hyphens, and non-Latin scripts. Use no pattern, or a generous one such as letters from any language.

6. Is pattern enough for security?

No. Visitors can remove it or send requests directly. The server must validate every value again.

Key Takeaways​

  • pattern takes a regular expression, written with no slashes, that the whole value must match.
  • It works on text, search, tel, url, email, and password inputs, not on textarea, select, or number.
  • Empty values skip it, so combine it with required when needed.
  • Write a helpful title and a visible hint, and escape special characters. An invalid pattern is silently ignored.
  • Be generous with names, phones, and addresses. Use \p{L} for letters from any language.
  • Use validity.patternMismatch and setCustomValidity for your own messages, and style with :user-invalid.
  • It is a convenience, not security. The server must validate again.

Next, learn how to limit how long a value can be, in Minlength and Maxlength, or return to the Forms overview.