How Count Text is built
Count Text counts pasted text several ways at once — graphemes, Unicode code points, UTF-16 code units, words, lines, and UTF-8/UTF-16 byte counts. This post is about why those numbers disagree with each other, and what that disagreement is actually showing.
Tech used
Intl.Segmenter for grapheme and word counts
"length" on a JavaScript string counts UTF-16 code units, not characters — a distinction that’s invisible for plain ASCII and very visible the moment emoji or combining characters enter the picture. Intl.Segmenter (Baseline 2024, native, no dependency) with granularity: 'grapheme' counts what a person would call “one character” — a grapheme cluster — regardless of how many code points or UTF-16 units it takes to represent it underneath. The same API with granularity: 'word' gives an approximate word count without a hand-rolled whitespace-splitting regex. Byte counts come from TextEncoder().encode(text).length for UTF-8, and text.length * 2 for UTF-16 (JavaScript strings are already UTF-16 internally, so no encoding step is needed there).
Implementation & operational notes
A family emoji is the clearest demonstration that these numbers aren’t redundant. 👨👩👧👦 (family: man, woman, girl, boy) is built from four base emoji joined by three zero-width joiner characters — seven Unicode code points in total, several of which are surrogate pairs in UTF-16 — and a person looking at it sees exactly one character. Every metric the tool reports disagrees on this input: 1 grapheme, 7 code points, more UTF-16 code units than that (each surrogate pair contributes 2), and a UTF-8 byte count larger still. None of those numbers is “the” character count — they’re each answering a different question (what do you see / how many Unicode code points / how does JavaScript store it / how many bytes does it take on the wire), and showing all of them side by side is the point: a single “character count” would have to silently pick one of those questions and hide that a choice was made.
Keeping the tool to counting, deliberately. The earlier legacy counter tool grew multiple named counters, undo history, and drag-reordering before it was retired — feature growth that never served the core task of “how much text is this.” This tool counts and stops there: no saved history, no multi-tab state, just a live count of whatever is currently pasted. A toggle for whether whitespace and newlines count toward the totals is the one configuration surface, because that specific ambiguity (does a trailing newline count as a character) is one people actually disagree about, unlike most of the rest of the surface area a counter could grow.
Try it / source
- Tool: Count Text
- Source: github.com/GeppettoAndRomero/count-text