Character classes & sets
Learn how JavaScript regex character classes match ASCII digits, words, spaces, Unicode properties, v-flag sets, and safe user input.
- 01Read the built-in shorthandsSay exactly which characters
\d,\w,\s, their capital opposites, and the dot match. - 02Choose sets or Unicode propertiesWrite ranges, flipped sets with
^, and properties such as\p{L},\p{N},\p{Script=Greek}, and\P{...}. - 03Build a regex from user text safelyEscape special characters with a helper or
RegExp.escape, and know where thevflag is stricter.
What a character class is
A regular expression, or regex, reads text one character at a time. Each part of the pattern stands for one spot in the text. A character class says which characters are allowed in one spot. For example:
\dallows one digit.[abc]allows onea, oneb, or onec.[^abc]allows one character that is nota,b, orc.\p{L}allows one letter from any language.
A character class is a part of a regex that matches exactly one character. It lists which characters are allowed. You can write that list in four ways:
- a short code, called a shorthand, such as
\d; - characters in square brackets, called a bracket set, such as
[a-z]; - a Unicode property, such as
\p{L}; - sets combined with the
vflag, such as[[a-z]--[aeiou]].
The previous lesson, Patterns & flags, showed how to write a regex, add flags, and use test, match, and replace. This lesson answers one question: which characters does one spot allow? To review text in JavaScript, see strings and Unicode & strings.
The table compares four tools that are easy to mix up. Read each row from left to right.
| You write | What it allows | When to use it |
|---|---|---|
\w | One basic English letter (A-Z, a-z), one digit (0-9), or _. | Use it for simple IDs and fixed formats. |
\p{L} | One letter from any language. It needs the u or v flag. | Use it for names and text in many languages. |
u flag | Reads one whole character at a time, even an emoji. It also turns on \p{...}. | You need it for \p{...} and for an emoji to count as one character. |
v flag | Everything u does, plus &&, --, sets inside sets, and string properties. | Use it when you combine sets or match \p{RGI_Emoji}. |
Built-in classes: \d, \w, \s, and dot
STEP THROUGHJavaScript has short codes for common sets. Each one matches one character. The first two know only the basic English characters. This basic set is called ASCII.
\dmatches one digit from0to9. It does not match digits from other writing systems, such as the Arabic digit three,٣.\wmatches one word character: a letter fromAtoZoratoz, a digit, or the underscore_. It does not match letters with accents, such asé.\smatches one space-like character: a space, a tab, a line break, or a rarer space such as the non-breaking space (a space that keeps two words on the same line).- The dot
.matches almost any one character. It skips line breaks.
A capital letter flips the meaning. \D matches one character that is not a digit. \W and \S work the same way.
Picture a guard at a door. The guard checks one guest at a time against a printed list. The guard does not look at the whole line of people. The guard looks only at the next guest.
- In real life: The guard holds one printed guest list
- In JavaScript: A character class is one list of allowed characters
- In real life: Each guest is either on the list or not
- In JavaScript: Each character either matches
\d,\w, or your set, or it does not - In real life: A different door can have a different list
- In JavaScript: Changing
\wto\p{L}changes which characters are allowed
The step-through below runs real checks. Pick a group of classes, then look for these results:
\dskips٣, but\p{N}(any number) matches it.\wmatches the underscore_, but noté.- The dot skips a line break, unless you add the
sflag. - An emoji such as
😄is stored as two pieces in a string. Without theuorvflag, the dot matches each piece, so you get two matches. Withu, you get one.
Pick a group of classes, guess which samples match, then step through the real checks.
script
["ASCII 9", "9"], ["Arabic ٣", "٣"], ["A", "A"], ["underscore", "_"], ["space", " "],]; const labels = (regex) => samples .filter(([, value]) => regex.test(value)) .map(([label]) => label) .join(", "); console.log(labels(/\d/u));console.log(labels(/\D/u));console.log(/\p{N}/u.test("٣"));\d takes only the digits 0 to 9, so it skips ٣. \p{N} takes a number from any writing system, so it takes both.Next, try each class on one sample string. Pick a class and see which characters it matches.
const sample = "A é β 9 ٣ _ -\u00A0.\n☕ 😀";const matches = sample.match(/\d/gu) ?? [];console.log(matches.join(" | "));Match list: 9
AskipsAspaceskipsspaceéskipséspaceskipsspaceβskipsβspaceskipsspace9matches9spaceskipsspace٣skips٣spaceskipsspace_skips_spaceskipsspace-skips-NBSPskipsNBSP.skips.\nskips\n☕skips☕spaceskipsspace😀skips😀One digit from 0 to 9 only. The match list comes from running the regex shown in the code.
\w is not a letter classThink of \w as basic English letters, digits, and _. With the i and u flags together, it also matches two rare symbols that look like K and s. It still does not match letters such as é or β. When you mean "letters from any language", use \p{L}.
Sets and ranges: [abc], [a-z0-9], and [^...]
SORT ITSquare brackets make your own set. The set still matches only one character.
[abc]matches onea, oneb, or onec.- A hyphen between two characters makes a range.
[A-Z]means any one capital letter fromAtoZ.[A-Z0-9]means one capital letter or one digit. - A caret
^right after[flips the set.[^A-Z0-9]means one character that is not a capital letter or a digit.
console.log("cab".match(/[abc]/g).join(""));console.log("B7!".match(/[A-Z0-9]/g).join(""));console.log("B7!".match(/[^A-Z0-9]/g).join(""));console.log("a-z".match(/[a\-z]/g).join(""));console.log("a-z]^".match(/[a-z\]\^-]/g).join("|"));console.log("a1!".match(/[^a-z0-9]/g).join(""));In plain words, line by line:
- Line 1:
[abc]matchesc,a, andb, so it printscab. - Line 2:
[A-Z0-9]matches the capitalBand the digit7. It printsB7. - Line 3: the caret flips the set. Only
!is not a capital letter or a digit, so it prints!. - Line 4:
\-is a hyphen with a backslash before it, so it means a real-, not a range. The set isa,-, orz, and it printsa-z. - Line 5: the set holds the range
a-z, a real]written\], a real^written\^, and a real-at the end. Every character matches, so it printsa|-|z|]|^. - Line 6:
[^a-z0-9]keeps only what is not a small letter or a digit. It prints!.
[A-Z0-9] allows one capital letter or one digit, so it finds B and 7. The ^ in [^A-Z0-9] flips the set, so it finds only !.To escape a character means to put a backslash \ before it, so it stands for itself. The rules are different inside and outside the brackets:
- Outside brackets, many characters have a special meaning:
.,+,?,*,(,),{,},|,^,$, and\. - Inside brackets, only a few need care:
](it closes the set),\,^at the very start, and-between two characters. - For a real hyphen, put
-first or last in the set, or write\-.
\d[a-z0-9]\p{L}\p{Script=Greek}[\p{L}--[aeiou]]\p{RGI_Emoji}
Sort each pattern by the kind of class it is. Each one matches a single character.
Unicode properties with \p{...} and \P{...}
STEP THROUGHUnicode is the big list of characters from every writing system. It gives each character labels, called properties, such as "letter", "number", or "Greek". A regex can match by property:
\p{...}(smallp) matches one character that has the property.\P{...}(capitalP) matches one character that does not have it.- Both need the
uorvflag.
Some useful properties:
\p{L}: any letter.\p{Lu}: any capital letter.\p{N}: any number.\p{Script=Greek}: any character from the Greek alphabet.\p{Emoji_Presentation}: characters that show as an emoji picture by default.
Unicode properties need the u or v flag. Pick a group, then step through to see which characters match.
script
console.log(text.match(/\p{L}/gu).join(" "));console.log(text.match(/\p{N}/gu).join(" "));console.log(text.match(/\P{L}/gu).filter((ch) => ch !== " ").join("|"));console.log(/^\p{L}$/u.test("e\u0301"), /^\p{M}$/u.test("\u0301"));A property only describes characters. It does not choose your rules for you. For example:
- For names from many countries, you may need letters, accent marks, spaces, apostrophes, and hyphens.
- For usernames, you may want only basic English letters and digits, on purpose.
Write the regex for the rule you chose. The last line of the lab shows why. e\u0301 is the letter e followed by a separate accent mark. On screen it looks like é, but it is two characters. So it is not one \p{L} letter. The accent alone matches \p{M} (any mark).
The v flag: set operations and string properties
INTERACTIVEThe v flag is a newer, stronger version of the u flag. It adds four things:
--takes one set away from another. This is called set difference.&&keeps only characters that are in both sets. This is called intersection.- A set can hold other sets in brackets, such as
[[a-z]--[aeiou]]. - Some properties match a whole emoji made of several characters as one match. These are called string properties.
To check whether a regex uses v, read its unicodeSets property. It is true or false, and you can only read it, not change it.
v flag, -- takes one set away from another. All letters minus a e i o u leaves lphbtβ.const text = "alphabet β123";const consonantsAndGreek = text.match(/[\p{L}--[aeiou]]/gv) ?? [];console.log(consonantsAndGreek.join(""));lphbtβ[\p{L}--[aeiou]] means every letter except a, e, i, o, and u. The Greek β stays, because it is a letter and not one of those vowels.
v flag. The string property example uses emoji, because emoji are what string properties match today.In the string property choice, the last line prints true, because new RegExp("", "v").unicodeSets is true for a regex made with v. Older browsers do not know the v flag. To check, make a tiny v regex inside a try block. If it throws an error, use a simpler u pattern instead.
The v flag gives new meaning to some symbols inside brackets, so it is stricter about escaping. For example, a real | inside a v set must be written \|. The next code throws an error on purpose. Read the error, then fix it to [a\|b].
| needs a backslash with v// In v mode, | is reserved inside a character class.new RegExp("[a|b]", "v");Escaping user input and RegExp.escape
SAFE DYNAMIC REGEXWhen a learner types a search term, that text is data. If you pass it straight to new RegExp(term), symbols such as +, [, and ? turn into regex rules. For example, a+b would mean "one or more a, then b". The fix is to escape the term first, then build the regex.
+ means "one or more", so /a+b/ also finds aaab. After escaping, \+ is a plain plus sign.function escapeForRegExp(input) { return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");} const userText = "a+b [test]";const escaped = escapeForRegExp(userText);const regex = new RegExp(escaped, "g"); console.log(escaped);console.log("find a+b [test] here".replace(regex, "<mark>$&</mark>"));In plain words, line by line:
- Lines 1 to 3 make the helper
escapeForRegExp. Line 2 finds every special character and puts a backslash before it. In the replacement,$&means "the character that was found". - Line 6 escapes the user text. Line 7 builds the regex from the escaped text.
- Line 9 prints the escaped text,
a\+b \[test\]. - Line 10 finds the real text
a+b [test]and wraps it in<mark>tags.
RegExp.escape is a newer built-in method from ES2025, the 2025 version of JavaScript. It does the same job and handles more unusual cases. MDN's browser support data lists it in Chrome 136+, Firefox 134+, Safari 18.2+, and Node.js 24+. This site's tests run on Node 22, which does not have it. So Node 22 code still needs the helper above, or a polyfill (code that adds a missing feature).
RegExp.escapeconst userText = "a+b [test]";const escaped = RegExp.escape(userText);const regex = new RegExp(escaped, "g"); console.log(escaped);console.log(regex.test("find a+b [test] here"));Line 5 prints \x61\+b\x20\[test\]. RegExp.escape also escapes the first letter and the space, as \x61 and \x20. That looks odd, but it keeps the text safe wherever you put it in a regex. Line 6 prints true.
Where character classes are used
Character classes show up when you check input, clean up text, search, and split text into pieces. First write the rule in plain words, such as "basic English username" or "digits from a phone field". Then pick the class that says exactly that.
Each row starts with a real task. Read across to see a pattern and why it fits.
| The task | A pattern to start with | Why it fits |
|---|---|---|
| Check a username | /^[A-Za-z0-9_-]+$/ or a version with \p{L} | First decide if your rule allows only basic English letters. Then pick the pattern. |
| Keep only phone digits | value.replace(/\D/g, "") | It removes everything that is not 0 to 9. Use \p{N} only if you accept digits from other writing systems. |
| Check a name from any country | /^[\p{L}\p{M}' -]+$/u | It allows letters, accent marks, apostrophes, spaces, and hyphens. Your app may still need more rules. |
| Highlight a search term | new RegExp(RegExp.escape(term), "gi") | The user's text is read as plain text, not as regex rules. |
- Usernames:
^[A-Za-z0-9_-]+$fits when you want only basic English letters, digits,_, and-. - Phone digits:
value.replace(/\D/g, "")removes everything that is not a digit from0to9. - Removing punctuation:
text.replace(/[^\p{L}\p{N}\s]/gu, "")keeps letters, numbers, and spaces. - Names from many countries: combine
\p{L}, accent marks, spaces, and punctuation, but only the ones your app accepts. - Search highlighting: escape the user's term, then build the regex.
Common misconceptions
- "
\dmeans any digit." It means only0to9. Use\p{N}for numbers from every writing system. - "
\wmeans any letter." It means basic English letters, digits, and_. It is not a good check for names from many countries. - "The dot matches everything." It skips line breaks unless you add the
sflag. Withoutuorv, it matches only half of an emoji such as😄. - "A bracket set matches a word." A set matches one character. The next lesson, on quantifiers, shows how to repeat it.
- "The
vflag is justuwith a new name." It adds--,&&, sets inside sets, string properties, and stricter escaping. - "Text from a text box is safe to put in a regex." Text can contain regex symbols. Escape it before you build a pattern.
The table compares three tools. Read each row to see when to use the tool and when not to.
| You write | Use it when | Don't use it when |
|---|---|---|
\w | Your rule allows only basic English letters, digits, and _. | You mean letters from many languages. |
\p{L} | You mean letters from any language, and you use u or v. | You need a strict format with only basic English letters. |
u | You need Unicode properties and whole emoji to work. | You need &&, --, sets inside sets, or string properties. Use v for those. |
Practice exercises
5 EXERCISES\d keepsType the exact first line this code prints.
const values = ["9", "٣"];
console.log(values.filter((value) => /\d/u.test(value)).join("|"));
console.log(/\p{N}/u.test("٣"));Only 9 matches \d. The second log prints true, because \p{N} matches the Arabic digit ٣.
This username regex uses \w, so it allows only basic English letters. It prints just maria. Which class should replace \w when the rule is "letters from many languages"?
const names = ["maria", "maria-é", "δοκιμή"];
const asciiOnly = /^[\w-]+$/u;
console.log(names.filter((name) => asciiOnly.test(name)).join(","));const international = /^[\p{L}\p{N}_-]+$/u;\p{L} matches letters such as é and Greek letters. Keep _ and - written out, because your rule allows them, not because they are letters.
Type the exact string the fixed filter prints.
const names = ["maria", "maria-é", "δοκιμή"];
const international = /^[\p{L}\p{N}_-]+$/u;
console.log(names.filter((name) => international.test(name)).join(","));All three names match, so the output is maria,maria-é,δοκιμή.
vPredict what this v-flag set difference prints.
const text = "a b c x y z";
console.log(text.match(/[[a-z]--[aeiou]]/gv).join(""));The pattern keeps small letters that are not vowels, so a b c x y z becomes bcxyz.
A user searches for a+b. Which ES2025 method should you call before you build the regex?
const term = "a+b";
const unsafe = new RegExp(term);
console.log(unsafe.test("aaab"));const regex = new RegExp(RegExp.escape(term), "g");RegExp.escape adds backslashes, so new RegExp reads the term as plain text.
Check your understanding
8 QUESTIONSQuestion 1 of 8What does
\dmatch in JavaScript?Choose an answer to see the explanation.
Question 2 of 8What does the word-class snippet print?
Read the code, then predictconst values = ["é", "_", "A"]; console.log(values.filter((value) => /\w/u.test(value)).join(","));Choose an answer to see the explanation.
Question 3 of 8What does this emoji example print?
Read the code, then predictconsole.log("😄".match(/./g).length, "😄".match(/./gu).length);Choose an answer to see the explanation.
Question 4 of 8What does this range code print?
Read the code, then predictconsole.log("aB7-".match(/[a-z0-9]/g).join(""));Choose an answer to see the explanation.
Question 5 of 8What does this Greek check print?
Read the code, then predictconsole.log("A β Ж".match(/\p{Script=Greek}/gu).join(""));Choose an answer to see the explanation.
Question 6 of 8What does this
v-flag set difference print?Read the code, then predictconst text = "a b c x y z"; console.log(text.match(/[[a-z]--[aeiou]]/gv).join(""));Choose an answer to see the explanation.
Question 7 of 8Why should a search highlighter escape the user's text before
new RegExp?Choose an answer to see the explanation.
Question 8 of 8Which statement about
RegExp.escapeis true?Choose an answer to see the explanation.
Key takeaways
\dand\wknow only basic English characters.\salso matches rarer spaces.- The dot skips line breaks unless you add
s. It needsuorvto match an emoji such as😄as one character. - A bracket set matches one character. Ranges and
^decide which characters are allowed. - Unicode properties such as
\p{L},\p{Lu},\p{N}, and\p{Script=Greek}needuorv. - The
vflag adds--,&&, sets inside sets, string properties, and stricter escaping. - Escape user text with a helper or
RegExp.escapebefore you build a regex from it.
Remember the one-liner.
First say in plain words which characters you mean. Then choose the regex that matches exactly those characters.
Up next: Quantifiers. They let the class you chose repeat, with +, *, ?, and {n,m}.