Why is the length of an emoji 2 in JavaScript?
Because String.prototype.length counts UTF-16 code units, not characters. Most emoji need two code units, and many are sequences of several code points. To count what a person sees, count grapheme clusters with length(str) from graphemely.
The fix, copy and paste
import { length } from '@kwy404/graphemely';
length('hello'); // 5
length('\u{1F600}'); // 1 (grinning face)
length('\u{1F44D}\u{1F3FD}'); // 1 (thumbs up, medium skin tone)
length('\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F467}'); // 1 (family of three)
length('e\u{301}'); // 1 (e with a combining accent)
Emoji in the code samples are written as JavaScript escapes, for example \u{1F468} for a man. In your own code you can type the emoji directly.
What .length actually measures
| Text (described) | .length | [...str].length | length(str) |
|---|---|---|---|
Letter a | 1 | 1 | 1 |
| e with a combining acute accent | 2 | 2 | 1 |
| Grinning face emoji | 2 | 1 | 1 |
| Thumbs up with a skin tone | 4 | 2 | 1 |
| Flag of Brazil (two regional indicators) | 4 | 2 | 1 |
| Keycap digit one | 3 | 3 | 1 |
| Family: man, woman, girl with zero width joiners | 8 | 5 | 1 |
| Flag of England (tag sequence) | 14 | 7 | 1 |
The spread operator and Array.from iterate by code point, which fixes surrogate pairs but still counts every part of a sequence. Only grapheme segmentation, as defined by Unicode UAX #29, matches what users see.
Where this bites in real code
- Character counters on text fields that disagree with what the user typed.
- Validation like "username must be at most 20 characters" that rejects short names with emoji or accents.
- Fixed width layouts and tables where padding is computed from
.length. SeepadStartandpadEndin graphemely.
Note that some limits are defined in bytes or code units on purpose, for example database column sizes. Use .length or new TextEncoder().encode(str).length for those, and length(str) for anything a human reads.
Install
npm install @kwy404/graphemely
pnpm add @kwy404/graphemely
yarn add @kwy404/graphemely
bun add @kwy404/graphemely
Works in Node 16 and newer, all modern browsers, Deno and Bun. ESM and CommonJS, TypeScript types included, zero dependencies, about 2 kB gzipped. Try your own text in the playground.
FAQ
JavaScript strings use UTF-16. Emoji live outside the Basic Multilingual Plane, so each one is stored as a surrogate pair of two code units, and .length counts code units.
It is three emoji (man, woman, girl) joined by two zero width joiners: 2 + 1 + 2 + 1 + 2 code units. It displays as one character, which is one grapheme cluster.
It counts code points, which is better than code units but still wrong for emoji sequences, flags, skin tones and combining accents.
Count grapheme clusters: length(str) from graphemely, or Intl.Segmenter with granularity grapheme.
Related guides
Truncate a string with emoji
Cut text to a length with an ellipsis, never half an emoji.
Reverse a string with Unicode
Reverse text without scrambling flags, accents or emoji.
Split a string into characters
Get the visible characters, not code units or code points.
Substring without breaking emoji
A Unicode safe substring and slice with negative indices.
Remove lone surrogates
Detect and repair the broken halves of emoji.