Unicode Character Inspector
See every character in a string, and find the invisible one that is breaking it
How long is it
Character by character
| Nothing yet. |
Normalisation
Escapes for the selected character
- Nothing selected
- Click a row above.
Three different things get called "a character", and nearly every text bug is one of them being mistaken for another.
A code unit 16 bits. What JavaScript indexes and slices
A code point One Unicode character. Up to 21 bits
A grapheme One thing a person would call a character
"é" as e + combining accent is
2 code units, 2 code points, 1 grapheme.JavaScript indexes code units, so "👍".length is 2 and"👍"[0] is half an emoji. Spreading a string with[...text] gives code points, which is better and still not what a person counts. Only Intl.Segmenter gives graphemes, and that is what the top row above uses.
"👨👩👧👦"
1 what you see
7 code points 4 people and 3 zero-width joiners
11 UTF-16 code units what String.length returns
25 UTF-8 bytes what a database column counts
Four correct answers to "how long is this".This is why a 280-character limit, a VARCHAR(255) and a progress bar can each disagree about the same message, and why truncating a string at a byte or a code unit boundary produces a replacement character where an emoji used to be.
Two strings that look identical and are not
"é" can be either of two things:
U+00E9 LATIN SMALL LETTER E WITH ACUTE
U+0065 U+0301 e, then COMBINING ACUTE ACCENT
They render identically. They are different strings.
'é' === 'é' is false, and every == in your codebase agrees.This is the single most common cause of "the search does not find it" and "the file name does not match". macOS stores filenames decomposed, Linux and Windows store what you gave them, and text typed on one and looked up on the other does not compare equal.
Normalisation is the fix, and there are four forms:
| Form | What it does | Use it for |
|---|---|---|
| NFC | Composes: e + accent becomes é | Storing and comparing. The default answer, and what the web platform expects |
| NFD | Decomposes: é becomes e + accent | Stripping accents, and matching macOS filenames |
| NFKC | Composes and folds compatibility characters: fi to fi, ① to 1, full-width to ASCII | Usernames, search keys, anything where lookalikes must collapse |
| NFKD | Both of the above | Aggressive normalisation before a fuzzy match |
The K forms are lossy on purpose: x^2 becomes x2and the meaning goes with it. Use them for a comparison key, never for the value you store.
The characters with no appearance
A string can contain characters that render as nothing at all, and they are what to look for when text refuses to match something it visibly equals.
| Character | Where it comes from |
|---|---|
| U+00A0 non-breaking space | Copying from a rendered web page. Looks like a space, is not one |
| U+200B zero-width space | Word-wrap hints, and text copied out of a CMS |
| U+FEFF byte-order mark | Files saved as UTF-8 by Windows tools. Invisible everywhere except the first comparison |
| U+200D zero-width joiner | Holding a multi-part emoji together, on purpose |
| U+00AD soft hyphen | Typesetting. Only appears if the line wraps there |
| U+E0000 tag characters | Nothing legitimate. Used to hide text inside other text |
Every one of these gets called out above with its position, and position is the fastest way to answer "why does this string not equal that string".
Characters that look like other characters
Cyrillic а is U+0430. Latin a is U+0061. They render identically in every font, and аpple.com is a domain somebody else can register.
The same trick works in usernames, in package names on a registry, and in anything compared as a string by a human. Text mixing writing systems is flagged above, which is not proof of anything and is a good reason to look more carefully.
Text that reads differently from how it runs
U+202E, the right-to-left override, makes everything after it display in reverse. So a file called file<RLO>gnp.exe shows in a file manager as filexe.png and is an executable.
The same characters inside a source file are the Trojan Source attack, published in 2021: a comment that a reviewer reads as inert can contain a bidirectional override that moves the end of the comment, so the code compiles differently from how it reads. Every one of those characters is called out above.
Half a character
JavaScript strings are UTF-16, and anything outside the first 65,536 code points is stored as two code units called a surrogate pair. Slice between them and you have half a character: a lone surrogate, which is a valid JavaScript string and cannot be encoded as UTF-8 by anything.
It arrives at the database as an error, or as a replacement character, a long way from wherever the string was cut. slice,substring and a fixed-width truncation all do it.
Why the names here are not all of them
The Unicode Character Database is several megabytes, and no page that has to load quickly should be shipping it. What is here instead is names for ASCII and Latin-1, for the punctuation and space blocks where the trouble lives, and for the ranges Unicode names arithmetically. CJK ideographs and Hangul syllables both have names you can compute instead of look up.
Everything else gets its block, its general category and its script, which the browser can answer from its own copy of the database through the property escapes in a regular expression. That covers the question people actually arrive with, which is not "what is this called" but "what is this and why is it causing trouble".