The family emoji 👨👩👧👦 is one character to a reader, eleven UTF-16 units to JavaScript, seven code points, and twenty-five bytes in UTF-8. Every row here shows all four counts, which is how you find the length check that is quietly wrong.
What you can control
- Four length columns — UTF-16 units, code points, UTF-8 bytes and graphemes — that disagree on purpose.
- Nine case families, from combining marks to right-to-left overrides that reverse displayed text.
- The escaped-form column shows exactly which code points are involved.
- The RTL override case makes a filename display backwards, which is a real attack vector.
What this is not
Grapheme counting uses Intl.Segmenter where available. Older environments fall back to code-point counting, which will disagree with the browser's own rendering.
Questions
Why do the four counts differ?
They measure different things. Bytes are storage, code points are Unicode scalars, UTF-16 units are what JavaScript's .length returns, and graphemes are what a person sees as one character.
What is the RTL override for?
U+202E reverses the display direction of following text, so user\u202Egnp.exe renders as userexe.png. It has been used to disguise executable attachments.
Which count should a length limit use?
Graphemes for anything a user types into, bytes for anything a database column stores. Using UTF-16 units means an emoji counts as two, which no user expects.