What are Unicode invisible characters?
Unicode invisible characters are codepoints that a font renders with no visible mark, yet a text field stores as real data. They are not deletions and not empty strings. A name field holding one Hangul Filler holds one character, occupies three bytes, and passes a "field must not be empty" check.
Unicode 16.0 assigns 154,998 graphic and format characters. Roughly 200 of them render as nothing under normal typesetting. They fall into five General Categories, and that category is what determines how software treats each one.
Two properties decide almost everything about how an invisible character behaves:
- General Category — the class Unicode assigns each codepoint, such as
ZsorCf White_Space— a yes/no property that governs whethertrim(),strip()and the regex class\streat the character as removable
A character is useful in a name field when its White_Space value is No. A character is useful for typographic spacing when its White_Space value is Yes. The two groups are not interchangeable.
How Unicode classifies invisible characters
Unicode sorts every codepoint into a two-letter General Category, and invisible characters occupy five of them. The category is a fixed property in the Unicode Character Database (UCD), not a convention.
Zs — Space Separator (17 characters)
Zs contains exactly 17 characters, and every one of them is a space of some defined width. The complete set: U+0020 Space, U+00A0 No-Break Space, U+1680 Ogham Space Mark, U+2000 En Quad, U+2001 Em Quad, U+2002 En Space, U+2003 Em Space, U+2004 Three-Per-Em Space, U+2005 Four-Per-Em Space, U+2006 Six-Per-Em Space, U+2007 Figure Space, U+2008 Punctuation Space, U+2009 Thin Space, U+200A Hair Space, U+202F Narrow No-Break Space, U+205F Medium Mathematical Space, and U+3000 Ideographic Space.
Every Zs character has White_Space=Yes. Every one is removed bytrim() and matched by \s. This is why a username made of Em Spaces fails on most platforms.
Cf — Format (170 characters)
Cf contains 170 characters in Unicode 16.0. These control how text is laid out without contributing a glyph. The ones used for invisible text are U+200B Zero-Width Space (ZWSP), U+200C Zero-Width Non-Joiner (ZWNJ), U+200D Zero-Width Joiner (ZWJ), U+2060 Word Joiner, U+FEFF Zero-Width No-Break Space, U+00AD Soft Hyphen, and the directional marks U+200E and U+200F.
Every Cf character has White_Space=No. None is removed bytrim(). None is matched by \s. A Cf character survives exactly the validation that destroys a Zs character.
Lo, So and Mn — characters that are invisible by accident
Three more categories contribute invisible characters, and none of them was designed to be blank.
Lo (Letter, other) holds U+3164 Hangul Filler, U+1160 Hangul Jungseong Filler, U+115F Hangul Choseong Filler and U+FFA0 Halfwidth Hangul Filler. Unicode classifies these as letters. A field that requires "letters only" accepts them, which is why they work on gaming platforms that reject punctuation and spaces.
So (Symbol, other) holds U+2800 Braille Pattern Blank and U+FFFC Object Replacement Character. U+2800 is a Braille cell with no raised dots, so it renders as a blank of fixed width.
Mn (Mark, nonspacing) holds U+034F Combining Grapheme Joiner and the Khmer inherent vowels U+17B4 and U+17B5. These attach to a preceding character and add no width.
Why some invisible characters survive a name field and others vanish
The White_Space property decides it, and nothing else does. A character with White_Space=Yes is removed by every standard trimming function. A character with White_Space=No is not. Both render as blank, which is why the outcome looks arbitrary until you check the property.
The White_Space property decides what trim() removes
String.prototype.trim() in JavaScript, str.strip() in Python andTRIM() in SQL all remove leading and trailing characters whoseWhite_Space value is Yes. All 17 Zs characters qualify. U+00A0 No-Break Space qualifies, despite its name — non-breaking refers to line wrapping, not to trimming.
The regex class \s follows the same rule. \s matches U+2003 Em Space. \s does not match U+200B Zero-Width Space. A form that sanitises input with value.replace(/\s/g, '') strips every space variant and leaves everyCf character in place.
This produces a reliable test. If a platform accepts your invisible name and then shows your old name after a refresh, the platform trimmed a White_Space=Yes character and fell back. Switch to a Cf or Lo character.
CSS collapses exactly five characters
The CSS white-space processing model collapses exactly five characters: U+0020 Space, U+0009 Tab, U+000A Line Feed, U+000D Carriage Return and U+000C Form Feed. Twelve consecutive spaces in HTML render as one space, because eleven of them collapse.
No other character collapses. Twelve consecutive U+2003 Em Spaces render as twelve Em Spaces. Twelve U+00A0 No-Break Spaces render as twelve No-Break Spaces. This is the reason has been the standard HTML indentation workaround since 1995, and it is a property of the CSS specification rather than a browser quirk.
Which invisible characters survive Unicode normalization
Normalization Form NFKC folds 16 of the 17 Space Separator characters into a plain space. Only U+0020 Space and U+1680 Ogham Space Mark pass through NFKC unchanged. Any system that normalizes input with NFKC before storing it destroys every other space variant.
NFKC folds 16 of the 17 space separators to a plain space
NFKC applies compatibility decomposition, which replaces a character with the plain equivalent Unicode considers it compatible with. U+2003 Em Space becomes U+0020. U+00A0 No-Break Space becomes U+0020. U+3000 Ideographic Space becomes U+0020. The width information is discarded.
Search indexes, username uniqueness checks and account-recovery lookups commonly normalize with NFKC, because normalizing is what stops two visually identical usernames from being registered separately. A username of Em Spaces normalizes to a username of plain spaces, which then trims to an empty string, which then fails validation.
Cf characters are unaffected. NFKC leaves U+200B, U+200C, U+200D, U+2060 and U+FEFF unchanged. So does NFC.
Hangul Filler changes identity under NFKC
U+3164 Hangul Filler folds to U+1160 Hangul Jungseong Filler under NFKC. U+FFA0 Halfwidth Hangul Filler folds to U+1160 as well.
This matters because U+3164 is the character most widely recommended for blank usernames. It works on many platforms. It does not survive NFKC intact — it survives as a different codepoint. A profile stored as U+3164 and later re-read through an NFKC-normalizing pipeline contains U+1160.
The practical consequence is narrow but real: if a platform normalizes, store U+1160 directly. It is the form U+3164 folds to, it is already Lo, it is alreadyWhite_Space=No, and NFKC leaves it unchanged. It is the stable choice for any system whose normalization behaviour you cannot inspect.
How much space an invisible character costs
Almost every invisible character costs 3 bytes in UTF-8, against 1 byte for a plain space. Character limits and byte limits are separate budgets, and platforms enforce different ones.
A field that counts characters treats one Hangul Filler as one character. A field that counts bytes treats it as three. A 150-byte bio holds 150 plain spaces or 50 Hangul Fillers.
Codepoints and grapheme clusters differ as well. A ZWJ emoji sequence such as a four-person family is 7 codepoints and 25 bytes, and renders as one grapheme. Platforms that count graphemes, codepoints and bytes will each report a different length for identical text.
Why an invisible character stops working
Four failures account for nearly every case, and each has a different fix. Identify the failure by what the platform does after you paste, not by what it does while you type.
- The field reverts to your previous value. The platform trimmed the input and found an empty string. You used a
White_Space=Yescharacter. Replace it with U+3164 Hangul Filler or U+2800 Braille Pattern Blank. - The field accepts the value, then shows a plain space. The platform normalized with NFKC before storing. Your character folded to U+0020. Replace it with a
CforLocharacter, which NFKC leaves unchanged. - A box or question mark appears. The rendering font has no glyph at that codepoint, so the renderer draws
.notdef. The data is intact; only the display is wrong. Switch to U+200B or U+2800, which have wide font coverage. - The value works on desktop and fails in the mobile app. The two clients validate separately, and mobile keyboards sometimes replace pasted content. Paste into a plain-text field first, then copy from there.
One failure has no fix. A platform that rejects any name shorter than a set number ofvisible characters measures rendered width rather than string length, and no invisible character satisfies it.
How to detect invisible characters in text
To detect invisible characters, match the categories rather than listing individual codepoints. A codepoint list goes stale with each Unicode release; a category match does not.
The regex \p{Cf} matches all 170 Format characters.\p{Zs} matches all 17 Space Separators. These classes work in Python (regex module), Java, .NET, PCRE, and JavaScript with the u flag.
Three signals indicate invisible characters without any tooling:
- A string length that exceeds the visible character count
- A cursor that requires two arrow-key presses to cross one apparent gap
- Text that fails an exact-match search against its own visible content
Text pasted from an AI assistant, a PDF export or a spreadsheet frequently carries U+00A0 and U+200B. Neither is inserted deliberately; both come from the source formatting.
For a per-character breakdown of any pasted string, use the invisible text detector.
How to remove invisible characters from text
To remove invisible characters, delete the Cf category and fold theZs category to a plain space. Deleting both categories outright destroys legitimate spacing.
Two cautions apply. U+200D Zero-Width Joiner is structural inside emoji sequences, so stripping all Cf characters breaks family and profession emoji. U+200C Zero-Width Non-Joiner is orthographically required in Persian, Hindi and other scripts, where removing it changes the word.
Strip Cf characters from usernames, identifiers and code. Preserve them in body text that may contain emoji or non-Latin scripts. For AI-generated text specifically, use the AI invisible text remover.
Are invisible Unicode characters a security risk?
Yes, in three specific contexts, and no in ordinary text. The risk comes from invisible characters changing meaning without changing appearance.
Source code. The bidirectional overrides U+202E and U+202D reorder how a line displays without changing how a compiler reads it. A code review can show one logical structure while the compiler acts on another. This is the Trojan Source class of vulnerability, disclosed in 2021, and major compilers now warn on bidirectional overrides in source files.
Domains and identifiers. A zero-width character inside a domain label or package name produces a string that looks identical to a legitimate one. Registries counter this with script-mixing restrictions and normalization.
Filter evasion. A moderation filter matching a literal word fails when a ZWSP splits that word. The word stays readable and stops matching.
Ordinary use carries no such risk. A blank username, a spacer in an Instagram bio and an empty message body change nothing about how the surrounding system interprets text.
One accessibility consideration applies regardless of security. Screen readers announce nothing for these characters, so a profile named entirely with invisible characters is announced as unnamed by NVDA, JAWS and VoiceOver. Users navigating by audio cannot distinguish it from an unlabelled element.