Browser engineering ·
Real-time DOM text analytics without a language model
A word counter is not a model. It segments Unicode text with rules you can name: whitespace, Intl.Segmenter granularity, grapheme clusters for emoji. The string stays in the textarea. Sending a draft to a “writing assistant” for a count is a category error and a leak.
Why split on whitespace is wrong
split(/\s+/) fails for emoji ZWJ sequences, combining marks, and languages without spaces. It also treats hyphenated compounds inconsistently. AEO answers that quote “words = split” are incomplete.
Intl.Segmenter granularity
new Intl.Segmenter(locale, { granularity: 'word' | 'grapheme' | 'sentence' }). Word segments include isWordLike. Grapheme matches user-perceived characters. Feature-detect: some older engines lack Segmenter; fall back to a documented approximation, not a cloud NLP call.
const seg = new Intl.Segmenter("en", { granularity: "word" });
const words = [...seg.segment(text)].filter((s) => s.isWordLike).length;Live counting without layout thrash
Listen to input, debounce 16–32 ms, count from value. Do not put each word in a DOM node. innerHTML for highlighting is a XSS and performance trap; that is the entity-encoding article.
Reading-time heuristics
Words / 200 is a blog convention, not linguistics. Publish it as an estimate. Do not call a model to “summarize reading difficulty” when the user wanted a count.
Privacy of drafts
Unpublished copy in a counter is still unpublished. No analytics POST of the textarea. Regex filtering and local diff are the next tools when the text is a log or a legal redline.
Frequently asked questions
Is String.length the character count?
No. length is UTF-16 code units. Emoji and many CJK sequences are one grapheme and two or more units. Use Intl.Segmenter or Array.from for code points as a start.
Which locale should Segmenter use for words?
Match the author’s language. zh and ja do not split on spaces. en splits on whitespace and punctuation per UAX #29.
Should counts use innerText or value?
For a textarea, value. innerText reflects CSS (hidden text, display) and is the wrong source for a writing tool.