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.

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.

CodepointNameWhat it doesCopy
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.

CodepointNameWhat it doesCopy
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.

CodepointNameWhat it doesCopy
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.

CodepointNameWhat it doesCopy
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 rangeCountMeasured advance width
U+200B–U+200F50.00 px
U+202A–U+202E50.00 px
U+2060–U+206450.00 px
U+2066–U+206F100.00 px
U+E0020–U+E007F960.00 px
U+1D173–U+1D17A80.00 px
U+E000110.00 px
U+FEFF10.00 px
U+00AD10.00 px
U+180E10.00 px
U+061C10.00 px
U+070F10.00 px — draws a glyph
U+0600–U+06045visible, 9–25 px (Arabic number and verse marks)
U+06051draws a glyph, advances 0.00 px
U+06DD, U+0890–U+0891, U+08E24visible, 9–18 px
U+FFF9–U+FFFB3visible, 11 px (interlinear annotation)
U+110BD, U+110CD2visible, 10 px (Kaithi number signs)
U+13430–U+1343F16visible, 10–11 px (Egyptian hieroglyph format)
U+1BCA0–U+1BCA34visible, 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.

CodepointNameWidthJS trim()JS /\s/NFKCUTF-8In a URL
U+200BZERO WIDTH SPACE0.00 pxkeptnokept3 B%E2%80%8B
U+200CZERO WIDTH NON-JOINER0.00 pxkeptnokept3 B%E2%80%8C
U+200DZERO WIDTH JOINER0.00 pxkeptnokept3 B%E2%80%8D
U+2060WORD JOINER0.00 pxkeptnokept3 B%E2%81%A0
U+FEFFZERO WIDTH NO-BREAK SPACE0.00 pxremovedyeskept3 B%EF%BB%BF
U+00ADSOFT HYPHEN0.00 pxkeptnokept2 B%C2%AD
U+180EMONGOLIAN VOWEL SEPARATOR0.00 pxkeptnokept3 B%E1%A0%8E
U+2062INVISIBLE TIMES0.00 pxkeptnokept3 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.

CheckResult
<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:

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.

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.

What to count

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.