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:

FormWhat it doesUse it for
NFCComposes: e + accent becomes éStoring and comparing. The default answer, and what the web platform expects
NFDDecomposes: é becomes e + accentStripping accents, and matching macOS filenames
NFKCComposes and folds compatibility characters: fi to fi, ① to 1, full-width to ASCIIUsernames, search keys, anything where lookalikes must collapse
NFKDBoth of the aboveAggressive 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.

CharacterWhere it comes from
U+00A0 non-breaking spaceCopying from a rendered web page. Looks like a space, is not one
U+200B zero-width spaceWord-wrap hints, and text copied out of a CMS
U+FEFF byte-order markFiles saved as UTF-8 by Windows tools. Invisible everywhere except the first comparison
U+200D zero-width joinerHolding a multi-part emoji together, on purpose
U+00AD soft hyphenTypesetting. Only appears if the line wraps there
U+E0000 tag charactersNothing 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".