# MD084 - Invisible Unicode characters
Aliases: `invisible-characters`
**Disabled by default.** This rule is opt-in: enable it explicitly with
`extend-enable`.
## What this rule does
This rule detects invisible Unicode characters that can cause copy/paste and rendering bugs,
confuse readers, or block `rumdl` from applying auto-fixes.
Those characters are typically introduced in your documents by copy/pasting contents from the Web,
and are by nature very difficult to spot, until they create issues later on.
By default, it reports only suspicious patterns:
- multiple consecutive invisible characters
- any invisible character at the start or end of a line
- invisible characters adjacent to any whitespace character
It doesn't report invisible character surrounded on both side by visible characters,
since that is how those invisible characters are typically used.
It also doesn't report two kinds of character that are presentation rather than hidden
content, because each is part of a glyph the reader actually sees:
- a variation selector that follows a base character. `U+26A0 U+FE0F` renders as the
emoji `⚠️`, while `U+26A0` on its own renders as the text glyph `⚠`.
- a zero width joiner that fuses the characters on either side of it, as in the
rainbow flag `🏳️🌈` (`U+1F3F3 U+FE0F U+200D U+1F308`) or `👩❤️👨`. Removing the
joiner would split the emoji into separate glyphs.
Either one is still reported when it isn't doing that job: at the start or end of a
line, next to whitespace, or with another invisible character where its base should
be. A duplicated variation selector is reported, since only the first one has a base
to modify.
Presentation characters are never reported or removed, but they still count as
invisible characters when looking for consecutive ones, so nothing can hide behind an
emoji. The zero-width space in `⚠️x` is reported, and the auto-fix removes only the
zero-width space.
With `strict = true`, it reports any detected invisible character anywhere.
The list of non-visible characters that this rule will trigger on is:
| 0x0000..=0x001F | C0 control characters |
| 0x007F..=0x009F | DEL + C1 control characters |
| 0x00AD | SOFT HYPHEN |
| 0x034F | COMBINING GRAPHEME JOINER |
| 0x061C | ARABIC LETTER MARK |
| 0x115F | HANGUL CHOSEONG FILLER |
| 0x1160 | HANGUL JUNGSEONG FILLER |
| 0x17B4 | KHMER VOWEL INHERENT AQ |
| 0x17B5 | KHMER VOWEL INHERENT AA |
| 0x180B..=0x180E | Mongolian variation selectors + MONGOLIAN VOWEL SEPARATOR |
| 0x200B..=0x200F | ZWSP, ZWNJ, ZWJ, LRM, RLM |
| 0x202A..=0x202E | Bidi embedding/override controls |
| 0x2060..=0x206F | WORD JOINER, invisibles, and bidi isolate controls |
| 0x3164 | HANGUL FILLER |
| 0xFE00..=0xFE0F | Variation Selectors (VS1..VS16) |
| 0xFEFF | ZERO WIDTH NO-BREAK SPACE (BOM) |
| 0xFFA0 | HALFWIDTH HANGUL FILLER |
| 0xFFF0..=0xFFF8 | Interlinear annotation and reserved non-rendering specials |
| 0x1BCA0..=0x1BCA3 | Shorthand format controls |
| 0x1D173..=0x1D17A | Musical symbol format controls |
| 0xE0000..=0xE0FFF | Tags block + Variation Selectors Supplement |
## Why this matters
Invisible code points are difficult to spot in reviews and can produce subtle
behavior differences across editors, terminals, and renderers. Catching them
early keeps Markdown content predictable and easier to maintain.
## Examples
### Incorrect (default mode)
```text
AB
```
```text
Starts with hidden char
```
```text
left right
```
### Correct
```text
AB
```
```text
Starts with visible text
```
```text
left right
```
## Configuration
```toml
[MD084]
strict = false
allow = []
```
- `strict`: When `true`, report any invisible character occurrence.
Default: `false`.
- `allow`: Allow-list of codepoints to ignore, written as `U+XXXX`,
`U+XXXXX`, or `U+XXXXXX` (for example `U+200B`). Default: `[]`.
### Strict mode
```toml
[MD084]
strict = true
```
Strict mode is deliberately literal, so it also reports variation selectors that
default mode treats as presentation. On content using emoji such as `⚠️` or `❤️`
that means a finding per emoji, and the auto-fix would strip the selector and change
the rendered glyph. Allow-list the selector to keep those intact:
```toml
[MD084]
strict = true
allow = ["U+FE0F"]
```
### Allow-list example
```toml
[MD084]
allow = ["U+200B", "U+2060"]
```
Allow-list entries must use Unicode codepoint format. Invalid entries are
rejected during config parsing.
## Notes
- The rule checks all content, including code blocks and frontmatter.