Unicode and character properties
Code points against code units, the u and v flags, property escapes for scripts and categories, and normalising text before matching.
Code points and code units
A JavaScript string is a sequence of UTF-16 code units. A character outside the basic plane, such as an emoji or a rare CJK ideograph, occupies two units, so a pattern written without the u flag can match half a character.
const emoji = '\u{1F600}'; // one code point, two code units
emoji.length; // 2
/^.$/.test(emoji); // false without u
/^.$/u.test(emoji); // true, . is now one code point
/[\u{1F600}-\u{1F64F}]/u.test(emoji); // true, a code-point range
// \p{...} needs the u flag; without it, P is just a literal letter
/\p{Script=Greek}/u.test('alpha \u03b1'); // true
/\p{Script=Greek}/u.test('alpha a'); // false
// \p without u is a literal match on the letters p and {
/\p{Script=Greek}/.test('p{Script=Greek}'); // true, and almost certainly not what you meant[...]is the way to write a code point in a pattern literal, or use a surrogate pair escape for anything below the basic plane.- The
vflag is the successor tou: it adds set operations inside classes such as intersection and subtraction. It is not interchangeable withufor every pattern. Array.from(s)and the spread operator iterate code points, soArray.from(emoji).lengthis 1 whileemoji.lengthis 2.
Property escapes
| Escape | Matches | Example |
|---|---|---|
\p{L} | any letter | a, Z, the letter alpha in Greek |
\p{Nd} | a decimal digit in any script | 7, an Arabic-Indic digit |
\p{Lu} / \p{Ll} | uppercase / lowercase letter | A / a |
\p{P} | punctuation | a comma, an em dash |
\p{Script=Han} | characters of one script | a CJK ideograph |
\P{L} | the negation of any property | the capital P negates |
import re, unicodedata
# Python: the third-party regex module understands properties; re does not
# re does provide \w with Unicode semantics for str patterns, and re.ASCII disables it
re.findall(r'\w+', 'greek \u03b1\u03b2 beta') # ['greek', '\u03b1\u03b2', 'beta']
re.findall(r'\w+', 'greek \u03b1\u03b2 beta', re.ASCII) # ['greek', 'beta']
# a portable letter check without property escapes
LETTER = re.compile(r'[^\W\d_]', re.UNICODE) # a word char that is not a digit or underscore
# normalise before matching so that composed and decomposed forms agree
a = 'caf\u00e9' # e with an acute accent, one code point
b = 'cafe\u0301' # e followed by a combining accent
a == b # False
unicodedata.normalize('NFC', a) == unicodedata.normalize('NFC', b) # TrueIn JavaScript \w and \d stay ASCII-only even with the u flag, so a Unicode-aware letter class is written with \p{L} or with [^\W\d_] in Python.
Normalisation and case folding
// case-insensitive comparison of text that may be decomposed
const strip = s => s.normalize('NFD').replace(/\p{M}/gu, '').toLowerCase();
strip('Caf\u00e9'); // 'cafe'
strip('Cafe\u0301'); // 'cafe'
// the i flag is not the same as case folding: some characters do not fold simply
/\p{Ll}/u.test('a'); // true
/^\p{Lu}$/iu.test('a'); // true only because i is set
// match word characters in any script, then trim the result in code
'\u4f60\u597d world'.match(/\p{L}+/gu); // [ '\u4f60\u597d', 'world' ]💡
Normalise once, at the boundary where text enters your system, and store the normalised form alongside the original if the exact bytes matter. Matching different normalisation forms against each other is a bug that only appears for some users, on some keyboards, which makes it expensive to find later.
FAQ
Do I always need the u flag?
Use it for any pattern that may see non-ASCII text or that uses
\p{...} or \u{...}. The main cost is stricter syntax: a stray surrogate escape becomes an error instead of matching half a character.Why does \w not match letters with accents?
In JavaScript and in Python with
re.ASCII, \w is defined as ASCII alphanumerics plus underscore. Use \p{L} with the u flag, or [^\W\d_] in Python, for a Unicode-aware letter class.Related
Flags and matching modes Dialects and engines: PCRE, POSIX, RE2 and .NET
Last refreshed 2026-09-18.