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
patternand 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
patternMismatchflag - 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."
- HTML Code
- Browser Output
<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
| Control | Supports pattern? |
|---|---|
type="text", search, tel, url, email, password | Yes |
type="number", date, time, range, color | No (use min, max, and step) |
type="checkbox", radio, file, hidden | No |
<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.
- HTML Code
- Browser Output
<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>
const field = document.getElementById("field");
field.addEventListener("input", () => {
console.log("patternMismatch:", field.validity.patternMismatch);
});
A Small Regex Cheat Sheet
| Piece | Meaning | Example | Matches |
|---|---|---|---|
[abc] | Any one of these characters | [aeiou] | e |
[a-z] | Any character in a range | [0-9] | 7 |
\d | A digit (0 to 9) | \d\d | 42 |
\S | Any character that is not a space | \S+ | hello |
. | Any single character | a.c | abc |
? | Zero or one of the previous | colou?r | color, 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 |
| | Or | cat|dog | dog |
( ) | Group | (ab)+ | abab |
\p{L} | Any letter in any language | \p{L}+ | José, 李雷 |
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
| Use | Pattern | Notes |
|---|---|---|
| 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:
| Name | [A-Za-z ]+ | Letters from any language |
|---|---|---|
| Ada Lovelace | accepted | accepted |
| José García | rejected | accepted |
| Nguyễn Thị Minh | rejected | accepted |
| O'Brien | rejected | accepted |
| 李雷 | rejected | accepted |
| Åsa Öberg | rejected | accepted |
| Anne-Marie | rejected | accepted |
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. Usetype="tel"with nopattern, 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.
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
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
patternis 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 ofA-Za-zif 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
abcandABCshould 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
- Relying on
patternfor security. The server must validate, too. - Writing the slashes (
/[0-9]+/) from JavaScript. - Adding
^and$and expecting partial matches. The whole value must always match. - An invalid pattern (such as an unescaped
-) that is silently ignored, so nothing is checked. Test it. - Forgetting
required. An empty value always passes. - No
titleor visible hint, so the error is a mystery. - Too-strict patterns for names, phones, and addresses.
- Using
patternon atextarea,select, ornumberinput, where it does nothing. - Writing your own email regex instead of
type="email". - Using
type="number"for codes and then adding a pattern. Usetype="text"withinputmode="numeric". - Slow, nested patterns that invite ReDoS.
- Showing the format only as a placeholder.
Practice Time
Try these in your editor:
- Write a pattern for a product code like
ABC-1234, with atitle. - Type a value with an extra character at the end, and see that the whole value must match.
- Add
requiredand compare an empty field with and without it. - Write a username pattern of 3 to 16 lowercase letters, digits, or underscores.
- Read
validity.patternMismatchin the console as you type. - Replace the browser message with your own using
setCustomValidity, and clear it correctly. - Make a PIN field with
inputmode="numeric"andpattern="[0-9]{4,6}". - Write the "at least one non-space character" pattern and test a field of spaces.
- Test
[A-Za-z ]+againstJosé, then switch to a\p{L}pattern. - 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
patterntakes a regular expression, written with no slashes, that the whole value must match.- It works on
text,search,tel,url,email, andpasswordinputs, not ontextarea,select, ornumber. - Empty values skip it, so combine it with
requiredwhen needed. - Write a helpful
titleand 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.patternMismatchandsetCustomValidityfor 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.