Unicode character inspector

Breaks text into its code points with names, categories, blocks and encodings, and flags invisible, bidi and lookalike characters.

Input

What this tool does

Paste any text and it comes back broken into the individual characters it is really made of. Each one gets its Unicode name, its U+ number, what kind of character it is, which writing system and block it belongs to, the bytes it takes in UTF-8 and UTF-16, and the escape sequence it needs in six different formats.

Most people arrive here because something does not add up. A string is longer than it looks, two values that display identically refuse to compare equal, a username was rejected for characters that appear to be plain letters, or a line of source code reads differently from how it behaves. All four have the same shape: a character doing something the screen does not show.

Characters, code points and graphemes

The word "character" means at least three different things, and mixing them up is where most Unicode confusion starts. A code point is one value in the Unicode standard, written U+0041 or U+1F600. A grapheme is what a reader counts as one character. They are frequently not the same number.

  • A plain "a" is 1 grapheme, 1 code point, 1 UTF-16 unit — everything agrees.
  • U+00E9, the precomposed e-acute, is 1 grapheme, 1 code point, 1 UTF-16 unit.
  • U+0065 U+0301, the same letter written as e plus a combining accent, is 1 grapheme but 2 code points and 2 UTF-16 units.
  • U+1F600, the grinning face, is 1 grapheme and 1 code point but 2 UTF-16 units — it lives above the BMP, so JavaScript stores it as a surrogate pair.
  • The family emoji is 1 grapheme, 5 code points and 8 UTF-16 units: three people joined by two invisible joiners.

The family emoji is the case that catches people out. It looks like one thing, JavaScript reports its length as 8, and a database column of 5 characters will not hold it. This tool groups code points under the grapheme they build, so both readings are visible at once instead of having to be reconciled in your head.

The characters you cannot see

A surprising number of Unicode characters draw nothing at all. They exist to control how the text around them behaves, and because they leave no mark they survive copying, pasting and review without anyone noticing. These are the ones the tool points at rather than merely listing:

  • Zero-width space, joiner and non-joiner (U+200B, U+200D, U+200C) — no width, no mark, and they break every string comparison they touch.
  • Soft hyphen (U+00AD) — a suggestion about where a word may break, invisible until it does.
  • No-break space (U+00A0) — looks exactly like a space and is a different character, which is why it survives so long in configuration files.
  • Byte order mark (U+FEFF) — often left at the start of a file, where it becomes an invisible first character of the first value.
  • Bidi controls and overrides (U+202A to U+202E, U+2066 to U+2069) — they reorder the text around them.

The bidi controls deserve their own mention. They were added so that Hebrew and Arabic can be mixed with Latin text correctly, and they do that job well. But an override placed inside a comment or a string literal can make a line of source code display in an order completely different from the order the compiler reads — which means a reviewer can approve code that does something else entirely. That class of trick is known as Trojan Source, and it is why this tool flags every bidi control separately from the other invisible characters.

Lookalike characters and mixed scripts

Unicode contains many characters that are visually identical to ASCII letters and are not them. Cyrillic а, Greek ο and Latin a occupy different code points and render the same in most fonts. A domain or a username built out of them looks correct and points somewhere else.

Flagging every non-ASCII lookalike would be useless: it would paint every Russian, Greek or Hebrew word red, and a warning that fires on ordinary text is a warning people learn to skip. So the rule here is the one the Unicode security standard uses — a word is suspicious when the letters inside it come from more than one writing system:

  • "paypal.com" written entirely in Latin letters: nothing to say about it.
  • The same word with a Cyrillic er and a in front of Latin "ypal": one word, two writing systems, flagged.
  • A whole Russian word in Cyrillic: one writing system, ordinary text, not flagged.
  • A Japanese sentence using Han, Hiragana and Katakana together: three scripts, one writing system, not flagged.

That last case is the reason the rule needs care. Japanese is written with three scripts at once and Korean mixes two, so a naive "more than one script is suspicious" test would call every Japanese sentence an attack. The combinations that are genuinely one writing system are treated as one, which keeps the warning meaningful.

Normalisation, or why two identical strings differ

Some characters can be written more than one way. The letter é is either the single code point U+00E9 or the pair U+0065 U+0301 — a plain e followed by a combining acute accent. Both display identically. Neither is wrong. They are not equal.

Normalisation is the process of picking one spelling, and there are four forms of it. NFC composes wherever it can and is what the web assumes by default; NFD decomposes instead. The two K forms go further and also fold compatibility characters — the fullwidth A becomes a plain A, and the ligature fi becomes fi — which is useful for search and matching and lossy for storage.

The panel here shows all four forms of whatever you paste and marks which of them differ from the input. If none differ, the text is already normalised and the mismatch you are chasing is elsewhere. If some do, you have found it.

Names, categories, scripts and blocks

Every assigned character carries four pieces of identity, and they answer different questions. The name is the character itself. The general category says what kind of thing it is — a lowercase letter, a combining mark, a currency symbol, a format control. The script is the writing system it belongs to. The block is the range of code points it was assigned in, which is an organisational fact rather than a linguistic one.

Block and script are easy to confuse and often disagree. Latin letters live in at least half a dozen blocks, and a block can hold characters from several scripts. When you want to know "what alphabet is this", the script is the answer; when you want to know "where in the standard does this live", the block is.

Names, scripts and blocks are shown in English here in every language, deliberately. They are identifiers defined by the standard, not prose — U+200D is named ZERO WIDTH JOINER everywhere in the world, and translating it would make it impossible to search for.

Encodings and escapes

A code point is a number; it becomes bytes only once an encoding is chosen. UTF-8 uses between one and four bytes and keeps ASCII unchanged, which is why it won. UTF-16 uses one or two 16-bit units, and it is what JavaScript strings are made of — which is why .length reports 2 for a single emoji.

U+0041   41              # UTF-8: one byte, same as ASCII
U+00E9   C3 A9           # UTF-8: two bytes
U+20AC   E2 82 AC        # UTF-8: three bytes
U+1F600  F0 9F 98 80     # UTF-8: four bytes

Each row here also gives the character written as an escape in JavaScript, JSON, HTML, CSS, URLs and Python, ready to copy. Those are not all the same, and the difference matters above the BMP: JavaScript and Python have a code-point escape and use it, while JSON has none and must spell an emoji as its two surrogate halves.

Frequently asked questions

Why is my string longer than the number of characters I can see?
Either it contains invisible characters, or it contains graphemes built from several code points. Both are shown here: invisible characters are flagged, and multi-code-point graphemes are grouped so you can see the one character and the several values it is made of.
Two strings look identical but are not equal. Why?
Usually normalisation. An accented letter can be one code point or a letter plus a combining mark, and both render the same. Paste each string here and compare the code point lists, or look at which normalisation forms differ from the input.
What is a zero-width space and how did it get into my text?
It is a character with no width and no mark, used to suggest a line-break opportunity. It usually arrives by copying text out of a web page, a document editor or a chat client, and because nothing displays, it survives every review until something compares strings and fails.
What is the difference between a code point and a character?
A code point is one Unicode value. A character in the everyday sense is a grapheme — what a reader counts as one — and a grapheme can be several code points. A family emoji is five code points and one grapheme.
Why does the tool say some characters have no name?
Four different reasons, and it says which. Surrogates and private-use code points have no name in the standard, unassigned ones have nothing to name, and a character in a rarely-inspected historic block has a name that this build does not carry — the name table keeps the blocks people actually paste from rather than shipping all of them.
Is a mixed-script warning proof of an attack?
No. It means a single word draws its letters from more than one writing system, which is the shape a spoofed name has and is also something ordinary text does occasionally. It is a reason to look, not a verdict.
Is anything I paste sent to a server?
No. The character database is downloaded with the page and everything runs in your browser; nothing you type is uploaded or logged, and it works with no network connection.