Case Sensitivity with `localeCompare` in JavaScript
I recently ran into a bug using code like the following:
someArray.sort((a, b) => a.name.localeCompare(b.name))
At a glance this seems fine—localCompare
enjoys fantastic browser support, and as the name suggests, it isn’t English-centric. However, this is still the web we’re building for, and the familiar gotcha of inconsistent behavior amongst browsers is lurking here. This can lead to hard-to-debug inconsistencies and bugs.
Most browsers—including Chrome, Edge, and Firefox—and Node treat localeCompare
as case-insensitive by default, treating an uppercase “A” as the same as a lowercase “a”. Safari, however, does something different.
Safari uses a variant of case-sensitive sorting, but not in the usual ASCII order where “Z” sorts above “a”. Instead Safari sorts lowercase letters above their uppercase companions, but in alphabetical order otherwise, like “aAbBcC”.
In many cases this is fine—sorting based on the user’s locale is not only acceptable, it’s preferable. But that’s not always true, particularly in scenarios where data is expected to match the order from a server, such as when sorting properties before hashing for verification or as a checksum, or when hydrating server-side rendered React. In these cases, consistent behavior across environments is important and can be achieved by specifying a few additional options to localeCompare
:
a.name.localeCompare(b.name, "en", { sensitivity: "base" })
The sensitivity: "base"
option tells browsers to use the base character for comparison (MDN), effectively making the comparison case-insensitive. This results in consistent sorting behavior across Node and all common browsers (including IE11).