Invisible Unicode Characters: The Complete List
One click puts the character on your clipboard. Nothing to select, nothing to download — press the button, then paste it where you need it.
Want them out of your text instead of reading about them? Use the zero-width space remover, the Claude watermark remover, the JSON cleaner or the Excel cell cleaner or the Word and Docs cleaner. Some of them can carry text: see Unicode tag characters.
These characters take up no room, print nothing, and survive copy and paste. Most of them exist for a good reason — telling a browser where a word may break, holding an emoji family together, setting text direction for Arabic. The trouble starts when they travel somewhere they were not meant to be: into a database key, a JSON string, a search box, or text copied out of a chat window.
Every character below is listed with what it actually does and whether deleting it is safe. The copy button puts the character itself on your clipboard, which is handy when you need one to test with. Everything on this page is standard invisible Unicode — no font trick, no image, nothing to install. If what you want is a blank name rather than a character to test with, the sections further down cover that; for a gap you can type into rather than a character to copy, see the empty character page.
What you just copied
If you searched for invisible character copy and paste, the button at the top is the whole answer: U+200B has no width, prints nothing, and survives the trip through the clipboard into almost any input box. It is one codepoint, not a trick.
Dropping the last two words changes nothing. An invisible character copy is the same button and the same codepoint, and it pastes the same way — the two searches differ only in what you plan to do with it afterwards.
The same codepoint is what most people mean by invisible text copy: a string built out of characters that render as nothing. A field holding only those is empty to look at and not empty to the software reading it, which is why some forms accept it and others hand it back. Nothing about the character predicts which — paste it and see.
Where invisible text copy and paste usually goes wrong is length, not the character. A single one tucked inside a word is invisible and harmless. A long run of them in a field that is supposed to be blank reads as noise to anything checking that field, because to it the field is not empty — it is full of something it cannot display.
There is no invisible font
Searches for an invisible font start from a sensible guess: somewhere out there is a typeface that draws nothing. Fonts do not work that way. A font maps each character to a glyph, and a character whose glyph happens to be blank is still a character — it still counts toward the length of your string, it still gets copied, and it still shows up in a diff. What those generators actually do is swap in invisible characters, not switch typeface.
That distinction is worth keeping, because a generator hands you characters you did not pick and cannot see, and some of those carry meaning. The joiner at U+200D is what holds an emoji family together as one picture; deleting it changes what people see. Copying from the table here means you know exactly which codepoint you ended up with.
Blank Unicode, and the symbol that does not exist
A blank unicode character is not a separate category in the standard — it is an ordinary codepoint whose glyph draws nothing, which is why that search and the one for an invisible character keep landing on the same handful of characters.
What people picture when they type blank symbol is a punctuation mark that paints nothing. There is no such mark: every block that holds symbols has a glyph for each one, so the request falls back to the characters in the tables below.
Invisible symbols, and copying one
An invisible symbol is the same set of codepoints under a third name, and what is usually wanted from that phrase is a mark that holds a position in a line rather than collapsing out of it.
For invisible symbol copy and paste the buttons in the tables are the whole procedure: press Copy, paste it where it belongs, and check the width afterwards, because the marks that take no room and the ones that take a full slot sit in the same list.
Always invisible, always safe to delete
These carry no meaning in finished text. Every one of them is removed by default, in both tools on this site.
| Codepoint | Name | What it does | Copy |
|---|---|---|---|
| U+200B | ZERO WIDTH SPACE | Lets a browser break a long word. Invisible, survives copy and paste, and the most common hidden character there is. HTML entity |
|
| U+2060 | WORD JOINER | Stops a line break between two words without drawing anything. HTML entity |
|
| U+00AD | SOFT HYPHEN | Marks where a word may break. Only becomes visible if the line actually breaks there. HTML entity |
|
| U+034F | COMBINING GRAPHEME JOINER | Ties two characters together so they behave as one. HTML entity ͏ |
|
| U+FEFF | ZERO WIDTH NO-BREAK SPACE | A byte order mark at the start of a file. Invisible and unwanted anywhere else. HTML entity |
|
| U+E0001 | LANGUAGE TAG | Part of the tag character block, used to label language inside a string. HTML entity |
|
| U+E0020–U+E007F | TAG CHARACTERS | 96 characters that spell out hidden ASCII text. Used to fingerprint copied output. | range |
| U+E000–U+F8FF | PRIVATE USE AREA | Reserved for private agreements. In copied text it is nearly always tracking or a watermark. | range |
| U+F0000–U+FFFFD | PRIVATE USE PLANE 15 | Same idea as the private use area, one plane up. | range |
| U+100000–U+10FFFD | PRIVATE USE PLANE 16 | The last private use plane. | range |
| U+FFF9–U+FFFB | INTERLINEAR ANNOTATION | Three marks for ruby-style annotation. Rarely intended in plain text. | range |
Invisible, but they carry meaning
Deleting these changes how text renders. They stay unless you turn on aggressive mode.
| Codepoint | Name | What it does | Copy |
|---|---|---|---|
| U+200D | ZERO WIDTH JOINER | Joins characters. This is what holds an emoji family together as one picture. HTML entity |
|
| U+200C | ZERO WIDTH NON-JOINER | Stops a join. Required for correct Persian, Arabic and Devanagari. HTML entity |
|
| U+200E | LEFT-TO-RIGHT MARK | Forces left-to-right direction for the characters after it. HTML entity |
|
| U+200F | RIGHT-TO-LEFT MARK | Forces right-to-left direction for the characters after it. HTML entity |
|
| U+061C | ARABIC LETTER MARK | Bidirectional mark used with Arabic script. HTML entity |
|
| U+202A–U+202E | BIDI EMBEDDING AND OVERRIDE | Five characters that set text direction. Safe to delete only in matched pairs. | range |
| U+2066–U+2069 | BIDI ISOLATES | Four characters that isolate a run from its surroundings. | range |
| U+180B–U+180D | MONGOLIAN FREE VARIATION SELECTORS | Select a glyph shape in Mongolian. | range |
| U+FE00–U+FE0F | VARIATION SELECTORS | Choose a glyph variant. Selector 16 is what keeps an emoji looking like an emoji. | range |
| U+E0100–U+E01EF | VARIATION SELECTOR SUPPLEMENT | More glyph variants, mostly for CJK ideographs. | range |
Spaces that are not the space bar
These look like a space and are not U+0020. They break comparisons and searches. Left alone unless you turn on the spaces option.
| Codepoint | Name | What it does | Copy |
|---|---|---|---|
| U+00A0 | NO-BREAK SPACE | A space that will not break a line. The one people paste by accident most often. HTML entity |
|
| U+202F | NARROW NO-BREAK SPACE | A thin unbreakable space, used before units and currency in French typography. HTML entity |
|
| U+205F | MEDIUM MATHEMATICAL SPACE | A medium space for use inside formulas. HTML entity |
|
| U+3000 | IDEOGRAPHIC SPACE | A full-width space that matches the width of a CJK character. HTML entity |
|
| U+2000–U+200A | FIXED-WIDTH SPACES | Eleven widths from EN QUAD down to HAIR SPACE. | range |
Invisible and deliberately left alone
These two are line and paragraph breaks. They are real structure, not junk, so neither tool removes them.
| Codepoint | Name | What it does | Copy |
|---|---|---|---|
| U+2028 | LINE SEPARATOR | A line break that is not a newline character. It breaks JSON strings and some parsers. HTML entity
|
|
| U+2029 | PARAGRAPH SEPARATOR | A paragraph break that is not a newline character. Same parsing problem. HTML entity
|
For a blank name or a blank bio
A blank space copy is not the space bar. Most forms trim U+0020 off both ends
of a name, which is why a name built out of ordinary spaces comes back as empty. U+00A0 is a
different codepoint that looks identical, and it is the one usually recommended instead —
but measured in a browser and in Python it does not survive either. JavaScript
trim() removes it, Python strip() removes it, and a name made only of
U+00A0 comes back empty for the same reason a name of space bar characters does.
U+200B is the one that survives both. Of 23 invisible codepoints put through the same test,
seven are emptied by trim() and six by strip(); U+200B is on neither
list, and all four normalisation forms leave it exactly as it is. People after an
invisible blank want a field that looks occupied while showing nothing, and
that is where to start. Which character a given site accepts is still decided by that site, not
by Unicode, so if one is rejected it is worth trying another before concluding the field cannot
be blanked at all.
Why a blank name sometimes gets rejected
The search blank space copy and paste instagram is the job above with one extra constraint: the check runs on their side, and it changes without notice. A character that went through last month can start coming back as invalid. Copy one, paste it, save, then look at the profile from a second device before assuming it held.
What gets called an invisible name symbol is the same handful of codepoints under a different name. When U+200B is refused, the usual fallback is U+3164, the Hangul filler — a real letter slot that renders blank in most fonts rather than a zero-width mark, so it takes up room instead of collapsing to nothing. That difference is also why it behaves differently in fields that measure width.
Why they end up in ordinary text
Three sources cover almost all of it. Rich text editors insert zero width spaces and no-break spaces as you type, and they come along when you copy. AI chat output carries invisible marks that identify it. And text copied from a web page picks up whatever the page was using for layout.
None of those are visible, which is the whole problem. A string that renders identically to another one can still fail to match it.
Invisible character: what it is, and what it does to your forms
Short answer: an invisible character is a Unicode format
character — general category Cf — that holds a position in your string
and draws nothing on screen. Unicode 15.1 assigns 170 of them. Measured in
Chromium 153 at 16px, 136 render at exactly 0.00 px and the other
34 advance 9 px to 25 px. The one you almost
certainly have is U+200B ZERO WIDTH SPACE: 3 bytes, 1 character, invisible,
survives copy and paste. On a form, measured: Chromium rejects every one of the 136
inside an email field, while a field holding only one still counts as filled
for required, and five of them are enough to fill a maxlength="5"
input that looks completely empty.
What counts as an invisible character
Unicode puts these in general category Cf, "format". They are real characters with
real code points; they just have no glyph of their own. A string containing one has a different
length, different bytes and a different identity from the same string without it, while looking
identical in every UI you own.
Enumerated with Python 3.13 (unicodedata, Unicode 15.1.0): 170 Cf
characters in 21 ranges, next to 65 control characters (Cc), 17 spaces
(Zs) and one each of line and paragraph separator. Then each of the 170 was
measured in Chromium 153 by rendering |X| and subtracting the width of
|| — twenty-four configurations in all: three font stacks, four font
sizes and two device scale factors. The table is the 16 px run, where 136 of the 170
advance 0.00 px:
| Codepoint range | Count | Measured advance width |
|---|---|---|
| U+200B–U+200F | 5 | 0.00 px |
| U+202A–U+202E | 5 | 0.00 px |
| U+2060–U+2064 | 5 | 0.00 px |
| U+2066–U+206F | 10 | 0.00 px |
| U+E0020–U+E007F | 96 | 0.00 px |
| U+1D173–U+1D17A | 8 | 0.00 px |
| U+E0001 | 1 | 0.00 px |
| U+FEFF | 1 | 0.00 px |
| U+00AD | 1 | 0.00 px |
| U+180E | 1 | 0.00 px |
| U+061C | 1 | 0.00 px |
| U+070F | 1 | 0.00 px — draws a glyph |
| U+0600–U+0604 | 5 | visible, 9–25 px (Arabic number and verse marks) |
| U+0605 | 1 | draws a glyph, advances 0.00 px |
| U+06DD, U+0890–U+0891, U+08E2 | 4 | visible, 9–18 px |
| U+FFF9–U+FFFB | 3 | visible, 11 px (interlinear annotation) |
| U+110BD, U+110CD | 2 | visible, 10 px (Kaithi number signs) |
| U+13430–U+1343F | 16 | visible, 10–11 px (Egyptian hieroglyph format) |
| U+1BCA0–U+1BCA3 | 4 | visible, 18 px (shorthand format) |
For scale, the widest of the 34 is 25 px — more than twice an ordinary capital letter
measured in the same run — so the visible ones are not marginal. Being in category
Cf does
not guarantee you cannot see it.
Eight of them, measured end to end
Node 22.22.2 and Python 3.13.14 for the language behaviour, Chromium 153.0.8010.12 for the
rendering and the form checks. Advance width is the |X| minus ||
measurement above.
| Codepoint | Name | Width | JS trim() | JS /\s/ | NFKC | UTF-8 | In a URL |
|---|---|---|---|---|---|---|---|
| U+200B | ZERO WIDTH SPACE | 0.00 px | kept | no | kept | 3 B | %E2%80%8B |
| U+200C | ZERO WIDTH NON-JOINER | 0.00 px | kept | no | kept | 3 B | %E2%80%8C |
| U+200D | ZERO WIDTH JOINER | 0.00 px | kept | no | kept | 3 B | %E2%80%8D |
| U+2060 | WORD JOINER | 0.00 px | kept | no | kept | 3 B | %E2%81%A0 |
| U+FEFF | ZERO WIDTH NO-BREAK SPACE | 0.00 px | removed | yes | kept | 3 B | %EF%BB%BF |
| U+00AD | SOFT HYPHEN | 0.00 px | kept | no | kept | 2 B | %C2%AD |
| U+180E | MONGOLIAN VOWEL SEPARATOR | 0.00 px | kept | no | kept | 3 B | %E1%A0%8E |
| U+2062 | INVISIBLE TIMES | 0.00 px | kept | no | kept | 3 B | %E2%81%A2 |
We expected none of them to be whitespace. That was wrong for one. U+FEFF is
the only one of the eight that JavaScript treats as whitespace: /\s/.test("\uFEFF")
is true, and trim() strips it off both ends. The other seven survive
trim(), so "ada@example.com\u200B".trim() is still 16 characters long
and still carries the character. Python disagrees: str.strip() removes
none of the eight, and re.search(r"\s", "\uFEFF") finds nothing. If your
pipeline trims input in Python and validates it in a browser, the same string can be clean on
one side and dirty on the other.
Measured: what happens on a real form
Each row was run in Chromium 153 against a real input element, not reasoned about.
| Check | Result |
|---|---|
<input type="email"> with the character in the local part |
Rejected, 136 out of 136. Zero of the zero-width format characters are accepted. Chromium's message names the character: "A part followed by '@' should not contain the symbol ' '." |
Regex /^[^\s@]+@[^\s@]+\.[^\s@]+$/ |
Accepts U+200B, U+200C, U+200D, U+2060, U+00AD, U+180E and U+2062 in the local part. Rejects U+FEFF — the only one \s matches. |
Regex /^\S+@\S+\.\S+$/ |
Same split: seven pass, U+FEFF fails. |
Regex /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/ |
Rejects all eight, and rejects any non-ASCII character at all — including ones you want, like a non-Latin-script address. |
required text input holding only that character |
Passes as filled, 136 out of 136. An empty string is the only value that fails the check. |
maxlength="5", typing the character 12 times |
Five BMP invisible characters fit (5 UTF-16 units). Only two astral ones fit — U+E0020 and U+1D173 cost 2 units each, so the field stops at 4 units and looks empty. |
| Equality and search | "hello" === "he\u200Bllo" is false, indexOf("hello") returns -1, but localeCompare returns 0 — collation calls them equal, code does not. |
| Serialization | JSON.stringify emits U+200B raw into the output. Python json.dumps escapes it as \u200b. Both parse back to the same 3-character string. |
| Size on the wire | 3 bytes in UTF-8, 9 characters once percent-encoded (%E2%80%8B). A string that looks 15 characters long can be 17 bytes in a POST body. |
Why the browser catches it and your regex does not
The HTML specification defines what an email address may contain, and Chromium implements that
list literally: an unlisted character in the local part is an error, invisible or not. A
hand-written pattern like [^\s@]+ means "anything that is neither whitespace nor an
at sign" — and U+200B is neither, so it sails through. The two disagree in the opposite
direction as well: required only asks whether the value is the empty string, and a
single invisible character is not empty.
None of this is a bug in either place. It means "the form accepted it" and "the string is clean" are two different statements, and a field can be simultaneously valid and wrong.
How to find one
Paste into the box at the top of this page, or run one of these. Both were run on
Hello<U+200B>World<U+FEFF>! and both report two hits.
// JavaScript
const RE = /[\u00AD\u180E\u200B-\u200F\u202A-\u202E\u2060-\u206F\uFEFF\uFFF9-\uFFFB]/gu;
const hits = text.match(RE) || []; // -> ['\u200B', '\uFEFF']
const clean = text.replace(RE, ''); // -> 'HelloWorld!'
# Python
hits = [ch for ch in text if not ch.isprintable()] # -> ['\u200b', '\ufeff']
clean = ''.join(ch for ch in text if ch.isprintable())
Python's isprintable() returned False for all eight characters tested
and True for ordinary letters, which makes it a one-line detector with no table to
maintain. In a database, the tell is length: LENGTH() in MySQL counts bytes and
CHAR_LENGTH() counts characters, so a 3-byte invisible character shows up as a
difference between the two.
How to remove one, and what not to remove
Deleting by codepoint range is the safe move, and the ranges above are measured. Three of them carry meaning that you can see:
- U+200D ZERO WIDTH JOINER holds emoji sequences together as one picture. Remove it and a family emoji becomes four separate faces.
- U+200C ZERO WIDTH NON-JOINER is required for correct Persian, Arabic and Devanagari.
- U+FE0F VARIATION SELECTOR-16 is what keeps an emoji rendering as a colour emoji.
Everything in the zero-width table above is otherwise safe to delete from ordinary text. If you would rather not decide, the Claude watermark remover keeps the meaningful ones by default and only takes the rest out when you turn on aggressive mode; the zero-width space remover shows you where each one sits before anything is deleted.
What this does not tell you
These measurements describe how invisible characters behave. They say nothing about who put one there or why, and finding one is not evidence that a text was generated by a model — rich text editors, document exports and web pages all produce them too. The width and validation numbers are from one browser build, swept across twenty-four configurations. How many advance 0.00 px is a fact about the size: 136 at 16 px, 135 at 32 px and above, the same in every font stack tested. At 16 px this build rounds each glyph advance to the nearest whole pixel, so every figure in the table is a whole number. Every count came from Unicode 15.1 and Chromium 153, so re-run the snippets on your own stack before you rely on a specific number.
Measured 2026-09-23 with Python 3.13.14 (unicodedata 15.1.0), Node 22.22.2 and Chromium 153.0.8010.12. Tools: Claude watermark remover, zero-width space remover, JSON cleaner, Excel cell cleaner. Full codepoint reference: invisible Unicode characters. Feeding the text to a model? What hidden characters cost you in tokens. Building JSON in JavaScript? U+2028 and U+2029. Text that looks empty but is not? Unicode tag characters hide a whole ASCII message with nothing on screen.
Check whether your text has any
Paste a line in. It is scanned in your browser — nothing is uploaded — and it answers two questions: are there any, and how many.
Nothing scanned yet.
To take them out and carry on, the zero-width space remover marks each one in place before anything is deleted.
A note on the other side of this
The same characters are also used to hide text in a URL, to smuggle words past a filter, and to fingerprint text so its origin can be traced later. That is worth knowing, because it explains why some of them show up in places they were never typed. This page exists to help you find them and take them out, not to help you hide anything.