graphemely

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

example.js
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].lengthlength(str)
Letter a111
e with a combining acute accent221
Grinning face emoji211
Thumbs up with a skin tone421
Flag of Brazil (two regional indicators)421
Keycap digit one331
Family: man, woman, girl with zero width joiners851
Flag of England (tag sequence)1471

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

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

Terminal
npm install @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