/^/

Lesson 19 of 28

Unicode

Match letters, scripts, and emoji correctly across languages using the u flag and \p{...} Unicode property escapes.

8 min read

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 "é"
café naïve 北京 hello123
4 matches

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
café naïve 北京 hello123
4 matches

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);
// ["🍕", "☕"]
I love pizza 🍕 and coffee today!
2 matches
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
🍕
1 match

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}+/gu

This 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.

Quick check

Why might the pattern \w+ fail to fully match the word "café"?

What is the main difference between a Unicode code point and a UTF-16 code unit in JavaScript strings?