So far, most examples in this course have quietly assumed the text is plain ASCII English. Real-world text isn’t: names have accents, plenty of the world writes in scripts that aren’t Latin at all, and messages are full of emoji. This lesson covers how regex handles all of that correctly — and where the defaults quietly fall short.
Why \w isn’t enough
The familiar shorthand \w is defined as exactly [A-Za-z0-9_] — English letters, digits, and underscore. Nothing else. That means accented letters, Cyrillic, Chinese characters, and more simply don’t count as “word characters” to \w, no matter which flags you add.
"café".match(/\w+/g); // ["caf"] — stops right before "é"
Notice how this splits café and naïve apart at their accented letters, and skips 北京 entirely — none of those characters are in \w’s fixed ASCII set.
\p{...} Unicode property escapes
The fix is \p{...} — a Unicode property escape that matches characters by their Unicode category, not by a hardcoded ASCII list. \p{L} means “any kind of letter, in any script.” It requires the u (or v) flag to work at all — without one of those, \p{...} throws a syntax error rather than silently doing the wrong thing.
"café".match(/\p{L}+/gu); // ["café"] — the whole word, accent included
With \p{L}+, every word is matched as a complete unit — café, naïve, and even 北京 (Chinese letters) — while the digits in hello123 are correctly excluded, since digits aren’t letters.
Matching emoji with \p{Emoji}
Unicode property escapes aren’t limited to letters. \p{Emoji} matches characters classified as emoji:
"I love pizza 🍕 and coffee ☕ today!".match(/\p{Emoji}/gu);
// ["🍕", "☕"]
| Property | Matches |
|---|---|
\p{L} |
Any letter, in any script |
\p{N} |
Any number/digit character |
\p{Emoji} |
Emoji characters |
\p{Script=Han} |
Characters from a specific script, e.g. Chinese Han |
Code points vs. code units: the surrogate pair gotcha
JavaScript strings are stored internally as UTF-16, and most emoji live at code points above U+FFFF — outside the “Basic Multilingual Plane.” Those characters, like 🍕 (U+1F355), can’t fit in a single 16-bit unit, so JavaScript stores them as a pair of code units called a surrogate pair. That means "🍕".length is 2, not 1.
Without the u flag, . and ^…$ operate on raw code units, so they can split a single emoji character in half:
/^.$/.test("🍕"); // false — "🍕" is 2 code units, . only matches 1
/^.$/u.test("🍕"); // true — u flag treats the surrogate pair as one character
Compare the two demos above: the first (no flags) fails to match, because . only accounts for one UTF-16 code unit and the pizza emoji needs two. The second (with u) succeeds, because the u flag makes the whole engine “code-point aware.”
Watch out
Any pattern using \p{...} must include the u (or v) flag, or JavaScript throws a SyntaxError immediately — it won’t just silently ignore the property escape. If you see “Invalid regular expression: Invalid property name,” check your flags first.
Match any word, in any language
Write a pattern that matches whole words made only of letters — in any language — from the text "Hello Привет 你好 123", without matching the digits.
Show solution
/\p{L}+/guThis matches "Hello", "Привет", and "你好" as three separate full-word matches, while leaving "123" alone, since digits aren’t part of the L (Letter) category. Plain \w+ would only have caught "Hello" and part of "123", missing the Cyrillic and Chinese words entirely.
What’s next
You’ve now covered the core toolkit — from literal characters all the way to Unicode-aware matching. From here, the course moves into applying these building blocks to real, practical patterns you’ll reach for again and again.