String.prototype.localeCompare()
The only correct way to sort text for people. Comparing strings with < orders them by UTF-16 code unit, which puts every capital before every lowercase letter and every accent after z.
Demo
The sign is the whole answer: negative means the first string sorts first. The fourth case is the one that matters — 'a'.localeCompare('B') is negative, because collation understands that a comes before b regardless of case. Compare that with the < operator, where 'a' < 'B' is FALSE, because the code unit for B is 66 and for a is 97. That single difference is why a plain sort() puts Zebra before apple.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| other | string | yes | The string to compare against. |
| locales | string | string[] | no (host default) | A BCP 47 language tag such as "de" or "sv". The ordering genuinely differs between languages. |
| options | object | no ({}) | Intl.Collator options — sensitivity, numeric, caseFirst, ignorePunctuation. sensitivity "base" makes the comparison ignore case and accents. |
Return value
number — Negative if this string sorts before the other, positive if after, 0 if they compare equal. The exact magnitude is unspecified — only the sign is meaningful.
Common patterns
names.sort((a, b) => a.localeCompare(b));
files.sort((a, b) => a.localeCompare(b, undefined, {numeric: true}));
const c = new Intl.Collator(); names.sort(c.compare);
Examples
Pitfalls
['zebra', 'äpfel', 'apple'].sort()
['zebra', 'äpfel', 'apple'].sort((a, b) => a.localeCompare(b))
if (a.localeCompare(b) === -1) { }
if (a.localeCompare(b) < 0) { }
items.sort((a, b) => a.localeCompare(b))
const c = new Intl.Collator(); items.sort(c.compare);
'ä'.localeCompare('z', 'sv')
'ä'.localeCompare('z', 'de')
When to use
- Sorting any list a person will read
- Case- and accent-insensitive comparison, via sensitivity options
- Natural sorting of names containing numbers
- Comparing text where the user locale should decide the order
- Sorting many thousands of items → Intl.Collator, reused
- You only need equality of identical bytes → === after normalize
- Sorting identifiers or keys → a plain comparison is fine and stable
- You need a stable order across locales → pin the locale explicitly
Notes
FAQ
Because that compares UTF-16 code units. Every capital letter has a lower number than every lowercase letter, so "Zebra" sorts before "apple", and accented characters sit above z entirely — a plain sort puts "äpfel" after "zebra". It is fast and correct for machine keys, and wrong for anything a human reads.
'a' < 'B'; // false — B is code unit 66 'a'.localeCompare('B') < 0; // true — a comes first ['zebra', 'äpfel'].sort(); // ['zebra', 'äpfel']