Strip Accents and Diacritics
Removing accents from a string is the secret behind diacritic-insensitive search, slug normalization, and sort-key generation. The canonical answer is `normalize('NFD') + replace combining marks`, which works for Latin scripts, but locale-aware comparison and case-folding need richer tools. This snippet starts with the one-liner, adds a case-folded variant for matching, and ends with `Intl.Collator` for true locale-correct comparison.
859 views
4
function stripAccents(str) {
if (typeof str !== 'string') return '';
return str.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
}
console.log(stripAccents('café résumé')); // cafe resume
console.log(stripAccents('Naïve façade')); // Naive facade
console.log(stripAccents('München, Düsseldorf')); // Munchen, Dusseldorf
console.log(stripAccents('plain ASCII')); // plain ASCIIUnicode normalization form D (NFD) decomposes a single accented code point like 'é' (U+00E9) into a base letter 'e' (U+0065) followed by a combining acute accent (U+0301). Once the accent is its own code point, a regex strip of the combining-marks block (U+0300 through U+036F) removes it, leaving the base letter intact. This is the ASCII-folding trick used by every search engine and slug generator: cheap (one allocation), correct for Latin and Greek, and fully built into the language since ES2015. It does NOT handle scripts that have a single code point per character with no decomposition, like Arabic, Hebrew, or CJK, where the input passes through unchanged.
function foldForMatch(str) {
if (typeof str !== 'string') return '';
return str
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.toLowerCase();
}
function includesFolded(haystack, needle) {
return foldForMatch(haystack).includes(foldForMatch(needle));
}
console.log(includesFolded('Café au lait', 'cafe')); // true
console.log(includesFolded('Naïve approach', 'NAIVE')); // true
console.log(includesFolded('plain text', 'PLAIN')); // true
console.log(includesFolded('Crème brûlée', 'creme brulee')); // true
console.log(includesFolded('apple', 'banana')); // falseSearch-as-you-type fields, autocomplete, and table filters need to match 'cafe' against 'Café' regardless of case or accent. Folding the haystack and needle through the same NFD + strip + lowercase pipeline turns the comparison into a vanilla String.prototype.includes, which V8 specialises into a fast indexOf. Doing this on the haystack at search time is fine for short text; for large catalogs, precompute the folded form once at index time and search against that to keep latency flat. Note that this is intentionally locale-naive: Turkish dotted/dotless I and German ß both have locale-specific case rules that this version ignores.
// Intl.Collator gives you locale-correct, customizable comparisons,
// so you don't need to fold strings yourself when you only want to compare or sort.
const collator = new Intl.Collator('en', {
sensitivity: 'base', // ignore case AND accents
ignorePunctuation: true,
});
console.log(collator.compare('café', 'cafe')); // 0 (equal)
console.log(collator.compare('Naïve', 'naive')); // 0 (equal)
console.log(collator.compare('apple', 'banana')); // -1 (apple < banana)
// Sorting a list locale-correctly
const names = ['Émile', 'André', 'Bernard', 'Élise', 'Anne'];
console.log(names.slice().sort(collator.compare));
// ['André', 'Anne', 'Bernard', 'Élise', 'Émile']
// Different locale, different rules: Swedish treats å, ä, ö as separate letters
const sv = new Intl.Collator('sv', { sensitivity: 'base' });
console.log(sv.compare('a', 'å')); // -1 (different letters in Swedish)
const en = new Intl.Collator('en', { sensitivity: 'base' });
console.log(en.compare('a', 'å')); // 0 (same base letter in English)Stripping accents is the right move for indexing and slugs, but for user-facing sorting and equality you want Intl.Collator, which respects per-language rules without you hand-coding them. sensitivity: 'base' treats 'café' and 'cafe' as equal, while 'accent' keeps accent differences but ignores case, and 'variant' distinguishes everything. Pass the collator's compare method directly to Array.prototype.sort and you get locale-correct ordering for free, including correct handling of the Swedish å/ä/ö after z, the German ß, and Czech ch. Use the strip-accents helper for keys you store, but use a collator for anything the user sees.
