How to remove zero-width spaces and joiners on Mac
You can do this with Note My — here is how to see every zero-width space, joiner and word joiner in a file, and remove them in one undoable pass.
Something in the file is wrong and nothing looks wrong. An identifier that reads userName will not resolve. A diff calls two identical-looking lines different. A search finds nothing, though the word is plainly there. Almost always the answer is a zero-width character: a code point that takes a position in the text and draws nothing.
They arrive by copy and paste, and are then undetectable by eye. This guide covers what Note My counts as zero-width, how to see the characters, and how to remove them without harming your emoji.
All of this happens on your Mac. There is no account and nothing is uploaded, so a file you scan for hidden characters never leaves the machine.
The six code points treated as zero-width
Our hidden-character scanner reports its findings in 21 named categories, so you can act on one kind of character and leave the rest alone. Zero-width characters covers exactly six code points.
| Code point | Name | Usual source |
|---|---|---|
U+200B | Zero-width space | Web pages, as a break hint |
U+200C | Zero-width non-joiner | Text shaping, carried by a copy |
U+200D | Zero-width joiner | Emoji — or loose in code |
U+FEFF | Zero-width no-break space | One file concatenated onto another |
U+2060 | Word joiner | Typesetting, rich-text exports |
U+180E | Mongolian vowel separator | Legacy text, pasted junk |
The category is marked always wrong. That is the level that earns a one-click removal, and nothing softens it: a zero-width space sitting inside a string or a comment is offered for removal like any other. All four presets include the category, so it is ready to act on the first time you scan.
How one gets into your file
You almost never type one. They arrive with a paste: from a web page that used them as break hints, from a chat window, a rich-text document, an AI chat. Concatenating two files can also strand a byte-order mark mid-document, where it is no longer a byte-order mark but plain zero-width junk. They are then copied faithfully with the line, which is why one broken identifier keeps reappearing.
Seeing one before you delete it
Two views draw what you cannot normally see, and between them they cover the whole invisible layer of a file. Show Invisibles — the toolbar toggle — is the whitespace view: a middle dot for a space, an arrow for a tab, a ring for a non-breaking space, and LF, CRLF or CR labels at line ends. It is how you read the shape of a line — trailing spaces, mixed line endings, a tab pretending to be four spaces — and it is worth leaving on while you work.
Hidden-character markers are the view for everything that takes up no width at all, zero-width characters among them. A zero-width character appears as a slug: a small stand-in glyph sitting in the run of text, the same treatment given to bidi controls and soft hyphens. You can select and type straight through a marker, and nothing is added to your document: the marker is something you see, not text to delete. Revealing one pulses it briefly, then settles to a quieter emphasis; Reduce Motion skips the pulse. Switch the markers in Settings ▸ Text Cleanup ▸ Highlight findings, on by default.
Remove every zero-width character in one pass
-
Read the status-bar pill
The hidden-characters pill in the status bar tells you where the file stands: Clean in green, "N hidden" in orange, or "N hidden · D risky" in red. It shows "Scanning…" until it has a count for you, and hides itself when there is nothing to say.
-
Open its popover and remove them
Click the pill. The popover offers Show in Text, Remove Invisible Character(s), Review All… and, when there are findings to walk, Go to Next. The removal row acts on always-wrong findings only, so zero-width characters qualify and look-alike letters do not. The whole removal is a single step in the Edit menu, named "Remove [N] Invisible Character(s)": one ⌘Z puts every character back. Nothing is preselected, so a stray Return cannot fire the removal.
-
For full control, open the review sheet
Choose Clean ▸ Clean Invisible / Special Characters…. Set the scope to Selection or Whole Document, pick a preset, press Scan.
The left table lists all 21 categories with a checkbox, a severity chip and a count. The right lists findings as Line:Col · Code Point · Category · Action · Risk, so you read the exact code point before deciding. Double-click one to reveal it in the editor.
-
Remove zero-width characters and nothing else
Untick every category except Zero-width characters. The preset pop-up switches itself to Custom the moment you touch a checkbox. Press Apply (N).
Apply removes the findings in the ticked categories that can be removed safely, and leaves anything needing your judgement in place. The pass is a single step in the Edit menu, named "Clean Invisible Characters", and the list refreshes so you see what remains.
Stepping through them one at a time
To inspect each character rather than sweep the lot, use Clean ▸ Find Next Hidden Character and Clean ▸ Find Previous Hidden Character. Each selects the character, scrolls it into view and shows a find indicator. The search wraps, and beeps rather than moving the caret somewhere arbitrary.
Each character is selected when you land on it, so Delete removes that one and nothing else.
Both steps sit in the Clean menu, and the forward step is repeated as Go to Next in the status-bar popover — so you can walk a file from the menu bar or from the pill, whichever is nearer to hand.
Your byte-order mark stays where it belongs
A U+FEFF at the very start of a file is left alone. There it is a byte-order mark — part of the file's encoding — and removing it would change how the file is written rather than clean the text. Anywhere else, the same code point is ordinary zero-width junk, offered for removal like the rest.
Adding or dropping the mark is an encoding decision, and our encoding handling leaves it to you: use File ▸ Encoding ▸ Convert to Encoding… and choose UTF-8 or UTF-8 with BOM. Preserve BOM is on by default.
Why the joiners inside emoji survive
A family emoji is several code points welded together by zero-width joiners. Stripping U+200D indiscriminately would shatter it into separate people.
Our hidden-character scanner tells the two cases apart for you. A joiner, non-joiner, word joiner or variation selector holding an emoji together is still listed, so you can see it, but never removed automatically. A joiner loose between two identifiers is removed like any other. You do not have to protect your emoji by hand.
Cleaning only the block you just pasted
Select the pasted text and open the review sheet with the scope set to Selection. Nothing outside the selection is touched. The line numbers in the findings list still match the ruler in the editor, so you can jump straight to anything you want to look at.
For a lighter touch, Clean ▸ Clean AI Copy/Paste Markers takes one click with no dialog and strips only artefacts that cannot change visible rendering, wording, tone, punctuation, indentation or emoji structure. Anything needing judgement is counted and left alone. It sits in the editor's right-click menu too, reading "…in Selection" with a selection, and reports through a toast: "Cleaned N hidden marker(s). Your text was not rewritten."
Making the removal automatic on every paste
Turn on Settings ▸ Text Cleanup ▸ Clean pasted text and pasted text arrives already cleaned with your default preset. The setting is off by default; the default preset is Code-safe, which includes zero-width characters. When there is nothing in the clipboard to act on, the text arrives unchanged, bit for bit. It is the right setting to turn on when you are pasting a lot from web pages or an AI chat, and the cleanup happens on your Mac like everything else — nothing is uploaded to check it.
Clean-on-paste applies your preset to the whole clipboard, so pick the preset that matches the work. Code-safe is the right default when you are pasting code: it lifts out the invisible junk and leaves your quotes, dashes, ellipses and wording exactly as they arrived. Reach for Aggressive when you want the punctuation tidied as well — smart quotes, dashes and ellipses included.
A second route exists. Clean ▸ Edit Removal Rules… opens the Residual Removal Rules editor, a regular-expression rule list with 11 built-ins — among them Zero-width chars and Word joiner, both enabled. The built-ins stay exactly as they ship, so a rule you have come to rely on cannot drift: untick one you would rather not run, or duplicate it and shape the copy into a rule of your own. Clean ▸ Clean Now then applies every enabled rule to the document, again as a single step in the Edit menu, named "Clean Residuals".
Removing them with a regular expression
To do it by hand, open Find & Replace from the magnifying-glass button in the toolbar, tick Regular Expression, leave the replace field empty and press Replace All. Our find and replace takes character classes, so one expression covers all six code points at once:
[\x{200B}\x{200C}\x{200D}\x{FEFF}\x{2060}\x{180E}]Our find and replace does precisely what the expression asks of it, which is what makes this route so quick on code. The live match count moves as you type, so you know how many characters the class has caught before you replace a single one, and the whole pass is one step in the Edit menu: ⌘Z takes it back. Set the scope to This File for the document in front of you, or All Tabs to sweep every file you have open in one go.
A character class of your own reads every joiner alike, your emoji included, so keep this route for code and let the review sheet take the prose — that is where the emoji awareness lives, and it is one press of Scan away.
Troubleshooting
Working through a very large file
Go straight to the review sheet — Clean ▸ Clean Invisible / Special Characters… — and press Scan. Our large-file engine keeps the sheet working on documents of any size, and it finds the same six code points there that it finds in a ten-line snippet. Give it a moment to fill the findings list, then work exactly as you would in a short file: double-click a finding to reveal it in the editor, or untick everything but Zero-width characters and press Apply.
Apply is greyed out, or beeps
Tick Zero-width characters and scan again — that category is always offered for removal, so Apply comes to life as soon as the scan turns one up. Look-alike letters and combining marks are there for you to read and rule on yourself, so tick them alongside the zero-width category and run the two together: Apply clears what is always wrong, and your judgement calls stay in the list with their line, column and code point, ready to take one at a time.
A character is still on screen after cleaning
That is deliberate: our hidden-character scanner is keeping something safe for you. Either it is a joiner holding an emoji together — listed so you can see it, kept so your emoji survives the pass — or it is U+FEFF at the very start of the file, where the character is the byte-order mark and belongs to the encoding rather than the text. Both remain yours to decide on: reveal the joiner from the findings list and delete it by hand, or change the mark from File ▸ Encoding ▸ Convert to Encoding….
The markers vanish as soon as I type
That is the display keeping step with your typing. The markers clear as you edit, so you are never reading a mark that no longer belongs where it sits. Read the pill again when you stop typing, or press Scan in the review sheet for the full list.
Find Next Hidden Character just beeps
A beep means there is nothing to move to from where the caret is standing. The command wraps round the end of the document, so you can start it anywhere in the file and let it come back round to you, and an edit gives it a fresh set to walk: make your fix, then run it again. When you want the whole picture in one place rather than one character at a time, press Scan in the review sheet — it takes in the whole document, whatever its size, and lists what it finds with the line, column and code point of each one.
Taking it further
- Read the result before it reaches the disk. Cleaning edits the open document, so you can look over what changed, step back with ⌘Z if you want a second opinion, and commit it to the file yourself with ⌘S.
- Survey a whole directory tree. Search ▸ Find in Folder… (⇧⌘F) with Regular expression ticked takes the character class above and shows you the zero-width characters across a folder, file by file and line by line, so you know the size of the job before you touch anything. Open the files it names and clean each one.
- Clean several files in one pass. Open them as tabs and set the Find & Replace scope to All Tabs; a single Replace All covers the lot, and one ⌘Z per tab takes it back.
- Rule on the look-alikes yourself. Look-alike letters and combining marks arrive with their line, column and code point, so a Cyrillic
аsitting inside an English word is something you can see, understand and change knowing exactly what it was. - Keep a file clean as you work. Leave the status-bar pill in view and it tells you where the document stands, so a paste that brings something in with it shows up while the paste is still fresh in your mind.
Frequently asked questions
What is U+200B and why is it in my file?
It is the zero-width space: a character that renders as nothing but still counts to every compiler, build step, comparison and search. A web page inserts it as a hint about where a long word may break, and it travels with the text.
Will removing zero-width characters break my emoji?
Your emoji come through the pass intact. Our hidden-character scanner recognises a joiner holding an emoji together, lists it for information and leaves it in place; only the loose ones are removed. Clean a file full of emoji and they read back exactly as you wrote them.
How do I deal with a whole folder of files?
Start with Search ▸ Find in Folder… (⇧⌘F) and Regular expression ticked: the character class from this guide will show you which files in the tree carry zero-width characters, and on which lines. Open the ones that matter, then clear them from the status-bar pill file by file, or set Find & Replace to All Tabs and sweep every open file in one pass.
What happens to the byte-order mark at the top of my file?
It stays exactly where it is, so your file goes on opening the way it always has. A U+FEFF at the very start is part of the encoding rather than stray text, and cleaning leaves it be. When you do want it added or dropped, that is an encoding decision and it is yours to make: File ▸ Encoding ▸ Convert to Encoding…, then UTF-8 or UTF-8 with BOM.
Can I undo a removal?
Yes. Each action arrives in the Edit menu as one named step — "Remove [N] Invisible Character(s)" from the popover, "Clean Invisible Characters" from the sheet, "Clean Residuals" from Clean Now — so one ⌘Z reverses the whole pass.