A form rejects an email address that looks perfect. Two identical strings compare as different. A search finds nothing. The usual cause is a character that occupies space in the data but paints nothing on screen.
The symptom
Some bugs share a signature: the data looks right and behaves wrong.
- A form rejects an email address that is visibly correct.
- Two strings that look identical compare as unequal.
- Ctrl+F fails to find a word that is plainly on the page.
- A CSV import creates a column whose name almost matches.
- A password copied from a manager is rejected, then works when typed.
In each case something is present in the text that has width zero, or renders as an ordinary space while being a different character underneath.
The usual suspects
These are real characters with real code points. They are not corruption, and most exist for legitimate typographic reasons:
- Zero-width space (U+200B) — a legal line-break point that paints nothing. Common in text copied from web pages.
- Zero-width joiner and non-joiner (U+200D, U+200C) — control how adjacent glyphs connect. Essential in Arabic and Indic scripts, and used to build emoji sequences.
- Non-breaking space (U+00A0) — a space that forbids a line break. Produced by in HTML, so it is everywhere in text copied from the web.
- Byte-order mark (U+FEFF) — marks encoding at the start of a file. When it survives into the middle of a string it is invisible and confusing.
- Soft hyphen (U+00AD) — an optional hyphenation point, shown only when a word actually wraps.
- Word joiner (U+2060) — forbids a break without adding width.
- Directional marks (U+200E, U+200F, U+202A–U+202E) — control left-to-right and right-to-left rendering in mixed-script text.
Where they come from
Rarely from typing. Almost always from copying.
Web pages are the biggest source: a non-breaking space entered as is a non-breaking space in your clipboard. Word processors insert them to prevent awkward line wraps. PDFs pick them up from positioning hints. Chat and AI interfaces carry them through from their own rendering. Spreadsheets contribute them via imported data that has been through several systems already.
A non-breaking space is the one most likely to bite, because it looks exactly like a space. Text with one reads perfectly and fails every comparison against the version with a normal space.
Why they break things specifically
Software compares bytes, not appearances. "user\[email protected]" and "[email protected]" are different strings, so an email regex fails on the first while a human sees nothing wrong. The error message says the address is invalid; the address looks completely valid.
Identifiers behave the same way. A zero-width character in a variable name, a JSON key, or a column header produces a mismatch that no amount of staring reveals. Diff tools may show the lines as identical while the files differ.
Search and sort are affected too. A zero-width space inside a word means a search for that word does not match it, and sorting places the entry somewhere unexpected.
How to find them
Because they are invisible, the reliable methods do not involve looking:
- 1Compare lengths
If a string is longer than its visible characters, something is hiding in it.
- 2Use a "show invisibles" mode
Most code editors can render whitespace and control characters explicitly.
- 3Check the character codes
In a browser console, [...text].map(c => c.codePointAt(0)) shows exactly what is there. Anything above 127 that is not obviously a letter deserves a look.
- 4Run it through a detector
A tool that counts hidden characters answers the question directly, which is usually all you need before deciding to strip them.
Removing them safely
Two cautions are worth stating, because the naive approach can do damage.
First, non-breaking spaces should be converted to ordinary spaces, not deleted. Deleting them runs words together.
Second, some invisible characters are load-bearing. Zero-width joiners hold emoji sequences together, and stripping them turns a single emoji into its component parts. Directional marks are required for correct display of mixed left-to-right and right-to-left text, so removing them can visually scramble Arabic or Hebrew. If your text contains either, strip selectively rather than wholesale.
For the common case — Latin-script text copied from a web page, a PDF, or a chat window — removing zero-width characters and normalising non-breaking spaces is safe and fixes the problem outright.
Fix it now
Detect zero-width spaces, byte-order marks, soft hyphens, word joiners, and non-breaking spaces that can break forms or code.
Common questions
U+200B, a Unicode character marking a legal line-break point while rendering as nothing. It occupies no visual width but is a real character in the data, so it breaks comparisons and searches.
A hidden character such as a zero-width space or non-breaking space is probably inside it, usually picked up when copying from a web page or document. The validator compares characters, not appearance.
No. It renders identically but is U+00A0 rather than U+0020, so any exact comparison treats the two as different.
Usually, for Latin-script text. Be careful with emoji, which use zero-width joiners to combine, and with right-to-left languages, which need directional marks to display correctly.