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 v flag is the successor to u: it adds set operations inside classes such as intersection and subtraction. It is not interchangeable with u for every pattern.
  • Array.from(s) and the spread operator iterate code points, so Array.from(emoji).length is 1 while emoji.length is 2.

Property escapes

EscapeMatchesExample
\p{L}any lettera, Z, the letter alpha in Greek
\p{Nd}a decimal digit in any script7, an Arabic-Indic digit
\p{Lu} / \p{Ll}uppercase / lowercase letterA / a
\p{P}punctuationa comma, an em dash
\p{Script=Han}characters of one scripta CJK ideograph
\P{L}the negation of any propertythe 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)   # True

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

Flags and matching modes Dialects and engines: PCRE, POSIX, RE2 and .NET

Last refreshed 2026-09-18.