Why substring breaks emoji, and a Unicode safe substring
substring, substr and slice use UTF-16 code unit indices, so any index that falls inside an emoji cuts it. slice(str, start, end) from graphemely uses grapheme indices, so every cut is safe.
The fix, copy and paste
import { slice, at, splitAt } from '@kwy404/graphemely';
const s = 'I \u{2764}\u{FE0F} JS';
s.slice(0, 3); // 'I \u{2764}', the emoji presentation selector is lost
slice(s, 0, 3); // 'I \u{2764}\u{FE0F}'
slice('hello', -3); // 'llo', negative indices work like String.prototype.slice
at('ab\u{1F600}', -1); // '\u{1F600}', the whole emoji
splitAt('hello', 2); // ['he', 'llo']
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 goes wrong with code unit indices
const s = '\u{1F600}\u{1F600}\u{1F600}';
s.substring(0, 3);
// '\u{1F600}\uD83D', one and a half emoji
s.charAt(0);
// '\uD83D', half of an emoji
import { slice, at } from '@kwy404/graphemely';
slice(s, 0, 2);
// two whole emoji
at(s, 0);
// '\u{1F600}'
Indices often come from somewhere else (a cursor position, a search match, a database limit). If they were computed in code units, convert the logic to graphemes end to end, and use isWellFormed to assert that a result has no lone surrogates.
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
Its indices count UTF-16 code units. An emoji uses two or more code units, so an index can point to the middle of it.
They differ in how they treat negative and swapped arguments, but all three count UTF-16 code units, so all three can break emoji.
Use slice(str, start, end) from graphemely. It has the same contract as String.prototype.slice, including negative indices, but counts grapheme clusters.
Related guides
Truncate a string with emoji
Cut text to a length with an ellipsis, never half an emoji.
String length with emoji
Why an emoji has length 2, and how to count what users see.
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.
Remove lone surrogates
Detect and repair the broken halves of emoji.