Toolbox runs in your browser

Index / Developer

Invisible and Confusable Characters

Two strings that look identical and compare unequal. This finds the reason: a zero-width space, a non-breaking space, a bidi override, a Cyrillic а standing in for a Latin one, or a character that changes the moment anything normalises it. Every one is located by position and named, and a cleaned copy comes back.

The text does not leave your browser. It is read in this tab and nothing else. The page has no network code, and the site's content security policy sets connect-src 'none', so it could not send the text anywhere even if it tried. Nothing is put in the URL either — people paste tokens, keys and other people's data into a page like this, and none of that belongs in a browser history.
Text
The example above is not what it looks like: it has a Cyrillic letter and a zero-width space in it.
nothing suspect
0

Code points 0
UTF-8 bytes 0

What is in there

The same text, cleaned

What normalising would do to it

How long it is, by every measure that matters

the four answers to “how many characters”
What counts as suspect here, and what this page does not claim Three separate things get flagged, and they are not the same problem.

Invisible characters come from a table of 278 code points: every control character, every format character, every space separator other than the ordinary one, the variation selectors, and a handful of letters that render as nothing. Zero-width space, zero-width joiner, non-breaking space, soft hyphen and the byte order mark are the ones that turn up in pasted text. The bidi controls and the tag characters are called out separately, because those two can make text display in an order that is not the order it is stored in, which is how a filename or a link can read as one thing and be another.

Characters that normalise to ASCII need no table at all. Your browser already ships a Unicode normaliser, so this page asks it: if NFKC turns a character into plain ASCII, it says so and shows what it becomes. That one check covers all 1,439 of them — every fullwidth form, every mathematical alphanumeric, the ligatures, the superscripts — without shipping a byte of data.

Homoglyphs are the ones normalisation will never touch, because they are genuinely different letters that happen to be drawn the same. Cyrillic а and Latin a are not the same character and never will be. Those need a table, and the one here has 92 entries — the Cyrillic and Greek lookalikes, the dashes, the curly quotes and primes, and a few pieces of punctuation. It is a curated list, not the whole of Unicode's confusables data, which is far larger and is mostly pairs nobody has ever been caught by. If a lookalike is missing, that is a gap in this page, not evidence the text is clean.

The cleaned copy is a suggestion, not a fix. It drops the invisible characters, apart from tab, newline and carriage return, and maps everything flagged to its ASCII stand-in. That is right for a password or an identifier and wrong for prose in any language that is not English, which is why the count of what changed is printed with it rather than the change being made quietly.

“How many characters” has four different answers and the page gives all of them, because that is usually the underlying confusion. JavaScript counts UTF-16 code units, so an emoji is two. Unicode counts code points. A database column counts bytes, and which byte count depends on the encoding. None of these is wrong; they are answers to different questions.

Comparing text is a fifth question again. Two strings can be canonically equivalent and not equal: é written as one code point and as two is the same text by Unicode's rules and a different string to almost every programming language. The normalisation panel says whether that is what is happening here.