Each Emoji Counts as One Character: Grapheme Clusters in the Word Counter
I work on OnePassApps, the team behind app.onepassapps.com. This is about our own Word Counter, and every number below was read off the live panel on 8 November 2026 - one build, one signed-out session, one browser. Our
I work on OnePassApps, the team behind app.onepassapps.com. This is about our own Word Counter, and every number below was read off the live panel on 8 November 2026 - one build, one signed-out session, one browser.
Our guide for the app makes a promise about emoji: "Each emoji or accented character counts as one character." Three earlier posts of ours about this app said, in their own limits sections, that we had not tested emoji or accented characters, so that promise was still untested on this panel. This post tests it.
The interesting part is what "one character" means when the character is more than one thing underneath.
The two panels
The Word Counter shows Words, Characters with spaces, Characters without spaces, Sentences, Paragraphs, Lines, Average word length, Characters per word, Reading time, Speaking time, and a counter above the input that reads "N / 2,000,000". The Text Studio has its own set: Words, Characters, Sentences, Lines, UTF-8 bytes.
I put 13 strings into both panels and, in the same page, computed four candidate rules for each one with the browser's own APIs: Unicode code points, grapheme clusters, UTF-16 code units and UTF-8 bytes.
Characters is grapheme clusters
| Input | Code points | Graphemes | Characters | UTF-8 bytes | Words | Input counter |
|---|---|---|---|---|---|---|
| family emoji, ZWJ-joined | 7 | 1 | 1 | 25 | 0 | 11 |
| flag, two regional indicators | 2 | 1 | 1 | 8 | 0 | 4 |
| thumbs up plus a skin tone | 2 | 1 | 1 | 8 | 0 | 4 |
| heart plus a variation selector | 2 | 1 | 1 | 6 | 0 | 2 |
| keycap digit | 3 | 1 | 1 | 7 | 1 | 3 |
| e plus a combining acute accent | 2 | 1 | 1 | 3 | 1 | 2 |
| precomposed e-acute | 1 | 1 | 1 | 2 | 1 | 1 |
| two family emoji | 14 | 2 | 2 | 50 | 0 | 22 |
| an emoji between two letters | 3 | 3 | 3 | 6 | 2 | 4 |
| Hi plus a wave plus there. | 11 | 11 | 11 | 14 | 2 | 12 |
| Cafe naive with precomposed accents | 10 | 10 | 10 | 12 | 2 | 10 |
| four CJK characters | 4 | 4 | 4 | 12 | 2 | 4 |
| Hello world. | 12 | 12 | 12 | 12 | 2 | 12 |
Seven of the 13 strings are the ones where two candidate rules disagree, and on every one of them the panel follows the grapheme count. A family emoji is 7 code points and 1 grapheme, and the panel reads 1 character. A flag is 2 code points and reads 1. A keycap digit is 3 code points and reads 1. An e with a combining acute accent is 2 code points and reads 1, the same as the precomposed e-acute.
So "each emoji counts as one character" is true as written - but the unit doing the counting is the grapheme cluster, which is the thing a person actually sees, and not the code point.
The counter above the input counts something else again
Same family emoji: the Characters tile reads 1 and the counter above the input reads 11. That counter is UTF-16 code units - the length of the string as JavaScript sees it. An earlier post of ours established that same rule for the Text Studio's counter; what is new here is that the Word Counter's own panel carries it, and that the Characters tile beside it does not count the same way. I put 200,000 family emoji into the field and the counter read 2,200,000 while the text was 200,000 characters, 1,400,000 code points and 5,000,000 UTF-8 bytes.
On emoji-heavy text the number on screen is therefore several times the character count, and it is the one number on this panel that is not a character count.
Words is the browser's word segmenter
Not a whitespace split. a plus an emoji plus b reads 2 words, because the segmenter finds a word on each side of the emoji. The keycap digit reads 1 word. A family emoji, a flag, a skin tone and a heart all read 0 words, because none of them is a word on its own. The panel says so itself: "Your text stays in this tab."
What this settles about the guide's line
A probe set built only from single-code-point emoji cannot tell code points from graphemes apart - the two rules give the same number for every one of those strings. An earlier post of ours about the Text Studio panel said the Characters row counts Unicode code points, and that holds for every string in that post's own table, including its one emoji, because a single-code-point emoji gives the same number under both rules. The strings in the table above are the ones that tell the two rules apart, and the panel goes with the grapheme count on all of them. The accented case comes out the same either way: e with a combining accent and the precomposed e-acute both read 1 character.
The reading and speaking times in the same panel are estimates, not measurements, and the panel says so: "Reading and speaking times are estimates from the adjustable rates in Settings, not measurements." It opens at 238 and 150 words per minute, and "At 238 words per minute" is what the reading row says.
Limits
One build, one browser, one signed-out session, 8 November 2026. The strings went into the field through the browser's own text input, not by assigning the field's value in the page. I did not open a file, so the 6 MB file ceiling is the guide's line and not something I loaded. I did not test Chinese, Japanese or Thai word breaks, so the guide's line about those stays the guide's line. The 200,000-emoji run is one input and I did not push further to find where the input is refused, so I make no claim about where that ceiling is enforced.
Try it on the Word Counter: https://app.onepassapps.com/apps/word-counter?utm_source=devto&utm_medium=post&utm_campaign=word-counter-grapheme&utm_content=word-counter-link
And the guide for it: https://app.onepassapps.com/guides/word-counter?utm_source=devto&utm_medium=post&utm_campaign=word-counter-grapheme&utm_content=guides-link
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.