Letras Diferentes
Unicode research · Unicode case-insensitive regex

Unicode-Aware Regex Case Matching: Simple Folds, /iu, Locale and Security

Explains JavaScript regular-expression ignoreCase semantics, Unicode simple case folding, Kelvin sign, sharp s, Turkish I, /u and /v flags and testing traps.

Technical referenceUnicode baseline: 18.0.0

The i flag is not a universal human-language case matcher

Regular expressions commonly use an i flag to request case-insensitive matching. In Unicode text, however, “ignore case” can refer to a simple one-code-point fold, a full fold that expands characters, a locale-sensitive mapping or collation-based equivalence. These are not interchangeable. A regex engine's case-insensitive matching semantics are part of its specification and may differ from str.casefold(), Intl.Collator and the application's data-identity rules.

ECMAScript applies Unicode-aware case folding to regular expressions when the appropriate Unicode mode flags are present. The details are especially visible for compatibility characters such as U+212A KELVIN SIGN and for characters like U+00DF LATIN SMALL LETTER SHARP S. Without Unicode mode, JavaScript uses a different canonicalization algorithm shaped by historical compatibility requirements.

Simple case folding versus full case folding

Unicode's CaseFolding.txt includes simple and full mappings. Simple mappings transform one scalar into at most one scalar. Full mappings can expand a single character into multiple scalars; for example ß case-folds to ss under default full folding. ECMAScript regular-expression ignore-case matching does not in general implement full one-to-many folding for this example. Thus testing /ß/iu against ss is not a reliable way to perform full Unicode caseless string equality.

CaseExpected core behaviorWhy
/k/iu versus KMatch in Unicode-aware modeKelvin sign simple folding
/k/i versus KNot the same Unicode-aware resultLegacy canonicalization restrictions
/ß/iu versus ssNo full-fold expansion matchSimple, not full multi-scalar folding
/σ/iu versus ςMatchGreek sigma simple folding
Turkish dotted/dotless INot an implicit locale-tailored foldECMAScript flags are not language parameters

Kelvin sign and the effect of Unicode mode

U+212A KELVIN SIGN (K) is a compatibility-looking symbol with a Unicode case-fold relationship to Latin k. In JavaScript, /[a-z]/iu and /k/iu can match K through Unicode-aware case handling. Legacy /[a-z]/i need not accept that same character, because its canonicalization algorithm excludes certain transformations from non-ASCII into ASCII.

console.log(/k/iu.test("K"));          // true
console.log(/[a-z]/iu.test("K"));      // true
console.log(/[a-z]/i.test("K"));       // false
console.log(/ß/iu.test("ss"));         // false
console.log(/σ/iu.test("ς"));          // true

This difference can matter for validators: adding a Unicode mode flag is not only a performance or parsing change, it can change which characters satisfy a seemingly simple ASCII-looking case-insensitive class. A security-sensitive input policy should use an explicit allowlist and normalization profile rather than relying on incidental regex canonicalization behavior.

Greek sigma and context-sensitive lowercasing

Greek has capital Σ, small σ and final ς. These variants can match under an appropriate Unicode-aware ignore-case regular expression, but producing correctly lowercased Greek prose uses a different algorithm that chooses final sigma from surrounding Cased and Case_Ignorable context. An i flag does not perform or expose that contextual lowercasing.

When the task is to make a displayed sentence lowercase, use the language-aware casing API; when the task is to find a caseless text match, choose the specified folding or collation relation. Confusing them creates bugs in search results, editing tools and identifier normalization.

Turkish I requires an explicit language strategy

The Turkish and Azerbaijani casing system distinguishes U+0049 I, U+0069 i, U+0130 İ and U+0131 ı. Default Unicode case folding and Turkish-specific mapping define different equivalence classes. A JavaScript regex does not acquire Turkish tailoring merely because the user has a Turkish keyboard or browser locale.

For UI search, Intl.Collator("tr", {usage:"search", sensitivity:"base"}) can provide a language-specific comparison relation, although it compares strings and does not replace substring matching. For stable identifiers, a separately defined caseless profile—potentially based on UTS #39 or a domain specification—should be implemented and used identically across registration and lookup.

console.log("I".toLocaleLowerCase("tr")); // ı
console.log("i".toLocaleUpperCase("tr")); // İ
const c = new Intl.Collator("tr", {usage:"search",sensitivity:"base"});
console.log(c.compare("I","ı")===0); // typically true in Turkish collation
// Neither statement changes the meaning of /i/iu.

Pre-folding entire strings is a separate comparison design

A common alternative is to normalize and full-case-fold both operands before comparing them. This works for a carefully specified whole-string equality relation and can handle one-to-many mappings such as ß to ss. It is not automatically equivalent to performing a regular expression against the original string: folding may change length, offsets and even matching boundaries. Search result highlighting then needs a map from folded text to original UTF-16 or grapheme ranges.

Python's str.casefold() implements full case folding in the runtime's Unicode data version, while JavaScript does not offer a built-in String.prototype.casefold(). The missing built-in is not a license to substitute toLowerCase() and claim full Unicode folding.

# Python illustrates full-fold whole-string equality.
assert "Straße".casefold() == "STRASSE".casefold()
assert "Σ".casefold() == "ς".casefold()
assert "İ".casefold() == "i\u0307"
# Full folding is not a transparent regex replacement against raw offsets.

Property escapes and case matching interact

Unicode property escapes such as \p{Letter} and \P{Lowercase_Letter} select property-defined sets rather than the ASCII-centric notion of words. Adding i can change how a Unicode-aware regex folds both characters and certain complement classes. The order of case folding and class complement differs between the modern v sets mode and some u constructs. Test the exact engine and flag combination rather than treating \P plus ignore case as intuitive Boolean negation.

These distinctions are especially important for identifier validation and text sanitization. A code path that accepts more Unicode letters after adding u can become inconsistent with a database that applies a narrower, case-insensitive collation. Normalize, validate and compare in a known order, and avoid silently switching algorithms at different layers.

Regex engine portability and test fixtures

JavaScript, Python's re, third-party Python Unicode regex libraries, ICU and PCRE use different regex grammars and casefold options. Some support full casefold matching, locale modes or special syntax not defined by ECMAScript. Copying a pattern from one environment to another without reviewing flag semantics can produce false-positive or false-negative matches.

Document the runtime's Unicode version, pattern, flags, normalization input and expected match span. Test representative supplementary mathematical characters too. A UTF-16 code-unit span from JavaScript and a code-point span from Python may refer to the same logical match but have different numeric offsets.

Spoofing and case equivalence are separate concerns

Mixed-script visually confusable strings can remain different even under full case folding. Greek Alpha and Latin A are not made canonically identical by a case-insensitive regex. UTS #39 provides additional confusable and identifier mechanisms. Likewise NFKC can fold mathematical styled letters into plain letters, which may be useful for a defined identifier profile but destructive for mathematical content or faithful copied Fancy Text.

For example the US Fancy Text tool produces encoded styled variants for display and copying. A regular expression search may have a different equivalence policy than the content editor's preservation policy. Never silently replace source code points simply because a regex engine reports a caseless match.

Regression matrix for implementations

Pattern / inputExpectedInvariant
/k/iu on KMatchUnicode simple folding
/[a-z]/i on KNo matchLegacy mode difference
/ß/iu on ssNo matchNo multi-scalar expansion
/σ/iu on ςMatchGreek sigma folding
Turkish I and ıUse locale/profile-specific equalityNo implicit language tailoring
Whole-string Straße / STRASSEEqual under full foldDifferent from JS regex matching
Folded substring highlightMap positions to original textLength may change

Testing case-insensitive matching with only ASCII examples misses these algorithmic distinctions. Use version-matched CaseFolding.txt to build complete tests and verify any regex-engine-specific tailoring explicitly.

Standards and implementation references

  1. ECMAScript — Canonicalize case matching
  2. UCD — CaseFolding.txt
  3. Unicode FAQ — Case mapping and folding
  4. MDN — RegExp.ignoreCase
  5. UTS #39 — Unicode Security Mechanisms

Unicode text processing must distinguish code points, Unicode properties, case transforms, grapheme boundaries and visual rendering.