How to emoji removal edge cases and implementation quirks
- Step 1Identify ZWJ sequences — Family emoji like 👨👩👧 are Zero-Width-Joiner sequences (man + ZWJ + woman + ZWJ + girl). Each person glyph is an emoji codepoint in `U+1F000–U+1FFFF`, so all are removed. The ZWJ (`U+200D`, General Punctuation) is outside the ranges and kept — but with no component glyphs there's nothing for it to join.
- Step 2Account for skin-tone modifiers — Fitzpatrick skin-tone modifiers (`U+1F3FB–U+1F3FF`) are inside the supplementary emoji range and removed. The base emoji they modify are also removed, so no half-rendered combinations remain — clean removal if you wanted them all gone.
- Step 3Check the Private Use Area before running — `U+E000–U+F8FF` is in the removal set as the typical custom-emoji range — but it's also where icon fonts (Font Awesome, Material Icons) live. Run [Character Coverage Map](/font-tools/character-coverage-map) first; if the PUA is populated with icons, use the [Character Whitelist Builder](/font-tools/character-whitelist-builder) instead of the Emoji Remover.
- Step 4Spot text-mode symbol variants you depend on — Characters like ☺ (U+263A), ✓ (U+2713), and ❤ (U+2764) live in `U+2600–U+27BF` and are removed. If your UI renders any of these as text from this font, whitelist them explicitly with the [Character Whitelist Builder](/font-tools/character-whitelist-builder).
- Step 5Pre-convert CFF/PostScript fonts — opentype.js's writer can throw on CFF (PostScript) outlines. If you get a writer error from the Emoji Remover, convert the OTF to TTF first with a desktop tool, then re-run — or use the hb-subset path from the [build-pipeline guide](/font-tools/guides/emoji-removal-build-pipeline-script).
- Step 6Accept (or avoid) the kerning loss — The rebuild drops `GPOS`/`GSUB`, so kerning and ligatures are gone from the output. For a display face that's fine; for a body face, do the strip in a layout-preserving pipeline so kerning survives.
How each Unicode emoji construct interacts with the filter
The filter is keyed purely on codepoint range. Multi-codepoint constructs are handled by removing each constituent codepoint independently.
| Construct | Codepoints involved | Result | Why |
|---|---|---|---|
| Single emoji (😀) | U+1F600 | Removed | Inside U+1F000–U+1FFFF |
| ZWJ family (👨👩👧) | person glyphs + U+200D joiners | Removed (joiner kept) | Person codepoints in emoji range; U+200D kept but orphaned |
| Skin-tone (👍🏽) | base + U+1F3FB–U+1F3FF | Removed | Both base and modifier are in the supplementary range |
| Flag (🇫🇷) | U+1F1E6–U+1F1FF pair | Removed | Both regional indicators are in the emoji range |
| Text-mode symbol (✓ ❤ ☺) | U+2600–U+27BF | Removed | Misc Symbols + Dingbats is in the removal set |
| Keycap (1️⃣) | 1 + U+FE0F + U+20E3 | Partly kept | Digit 1 and U+20E3 are outside the ranges; the visible composition breaks |
| Icon-font glyph (PUA) | U+E000–U+F8FF | Removed | PUA is treated as emoji — the icon-font trap |
Variation selector (U+FE0F) | U+FE0F | Kept | Outside the ranges; harmless on its own |
Implementation behaviour reference
What the rebuild actually does, drawn from the tool's code path.
| Aspect | Behaviour |
|---|---|
| Keep rule | Keep a glyph if its unicode (or any unicodes alias) is outside all four ranges |
| .notdef | Glyph 0 is always kept |
| Empty keep-set | Emoji-only font → falls back to stripping colour tables (COLR/CPAL/sbix/SVG/CBDT/CBLC) only |
| Layout tables | GPOS/GSUB (kerning, ligatures) dropped on rebuild; outlines preserved |
| Output | Always TTF, <stem>.no-emoji.ttf, uncompressed |
| CFF outlines | May throw in opentype.js's writer → clear error suggesting TTF conversion |
Cookbook
Worked examples of the tricky constructs, showing what's in the cmap before and what survives after.
ZWJ family sequence
ExampleA family emoji is a sequence, not one glyph. Each person is its own emoji codepoint; the joiner is plain punctuation.
Sequence 👨👩👧 decomposes to: U+1F468 MAN -> removed (U+1F000-1FFFF) U+200D ZWJ -> KEPT (General Punctuation) U+1F469 WOMAN -> removed U+200D ZWJ -> KEPT U+1F467 GIRL -> removed After strip: only the orphaned ZWJ remains in the font. Nothing renders for the family -> OS emoji font takes over.
Skin-tone modifier
ExampleFitzpatrick modifiers and their base both live in the supplementary range, so they go together — no half-modified leftovers.
👍🏽 = U+1F44D THUMBS UP + U+1F3FD MEDIUM SKIN TONE U+1F44D -> removed U+1F3FD -> removed (skin-tone block U+1F3FB-1F3FF) Neither survives; the OS renders 👍🏽 via its colour emoji font.
Regional-indicator flag pair
ExampleFlags are two regional-indicator letters; the OS shapes them into a flag. Both letters are removed.
🇫🇷 = U+1F1EB REGIONAL INDICATOR F
+ U+1F1F7 REGIONAL INDICATOR R
U+1F1EB -> removed
U+1F1F7 -> removed (both in U+1F1E6-1F1FF)
Flag vanishes from the font; OS shaping draws it instead.The PUA icon-font trap
ExampleAn icon font parked its glyphs in the Private Use Area. The Emoji Remover deletes the whole range, gutting the icon set.
Material Icons cmap (partial): U+E5CD 'close' -> removed (PUA U+E000-F8FF) U+E88A 'home' -> removed U+E8B6 'search' -> removed Result: the icon font is destroyed. DO NOT run Emoji Remover on icon fonts. Use Character Whitelist Builder to keep these codepoints, or Font Subsetter to subset to your icon set.
Text-mode symbol you actually used
ExampleA check-mark rendered from this font as text disappears because its codepoint is in the dingbat range.
Markup: <span class="tick">✓</span> (U+2713)
CSS: .tick { font-family: "BrandSans"; }
After Emoji Remover strips U+2600-27BF:
U+2713 removed from BrandSans
-> ✓ falls back to OS/serif or shows tofu if no fallback
Fix: whitelist U+2713 (and any other symbols you render as
text) with the Character Whitelist Builder.Edge cases and what actually happens
Every row below was probed against the live API. Some documented requirements (alphabetical axis order, numerical tuple order) are not actually enforced in practice — useful to know if you've been blaming the wrong thing for a 400.
ZWJ joiner survives but has nothing to join
PreservedU+200D (ZWJ) sits in General Punctuation, outside all four ranges, so it's kept. But every emoji it joined is removed, leaving an orphaned joiner with no effect. Harmless — it just occupies one cmap entry. The composed family/profession emoji renders via the OS font instead.
Private Use Area icon fonts are gutted
RemovedU+E000–U+F8FF is treated as the custom-emoji range, but it's the standard home for icon fonts (Font Awesome, Material Icons). Run the Emoji Remover on one and every icon vanishes. This is the single most common misuse. Use the Character Whitelist Builder to keep the icon codepoints, or don't route icon fonts through this tool at all.
Text-mode symbol variants disappear
RemovedU+2600–U+27BF (Misc Symbols + Dingbats) holds text-presentation symbols like ☺ ✓ ❤ ✈ ✉ as well as emoji. They're all removed. If your UI renders any as text from the stripped font, they fall back or show tofu. Whitelist the exact codepoints with the Character Whitelist Builder.
Keycap and other composed sequences break partially
PartialKeycap emoji (1️⃣ = 1 + U+FE0F + U+20E3) mix a normal digit with combining marks. The digit and U+20E3 (Combining Enclosing Keycap) are outside the ranges and kept; only the variation selector path changes. The visible keycap composition no longer renders from your font and falls through to the OS — which is usually what you want.
Emoji-only font falls back to colour-table strip
By designIf every cmap codepoint is in the emoji ranges, the keep-set is empty. Rather than error, the tool removes the colour tables (COLR/CPAL/sbix/SVG/CBDT/CBLC) and returns the result labelled 'Colour-table strip (emoji-only font)'. The emoji glyphs themselves remain — this only de-colours them. For full colour removal use the Colour Table Remover.
CFF / PostScript-outline font fails in the writer
Erroropentype.js's toArrayBuffer() is reliable for TrueType outlines but can throw for CFF (PostScript) outlines or non-standard tables. The tool surfaces a specific message recommending TTF conversion or a smaller charset via the Character Whitelist Builder. The failure is in the rebuild, not the emoji detection.
Kerning and ligatures always dropped
By designEvery rebuild goes through opentype.js's writer, which doesn't carry GPOS/GSUB. So even a perfect emoji strip loses kerning pairs and ligatures. The metrics note says so. For kerning-critical fonts, strip emoji with hb-subset (see the build-pipeline guide) instead.
Variation selectors and combining marks left in place
PreservedU+FE0F (emoji variation selector) and U+FE0E (text variation selector) are in Variation Selectors, outside the ranges, so they're kept. They're inert on their own. This means a stripped font may retain a few selector codepoints with no base emoji — cosmetically odd in a coverage report but functionally harmless.
Glyph mapped to both emoji and non-emoji codepoints survives
PreservedThe keep test checks the primary unicode and all unicodes aliases — a glyph survives if any of its mappings is outside the ranges. A rare glyph cmapped to both an emoji and a non-emoji codepoint is therefore kept, retaining the emoji mapping as a side effect. Uncommon, but it means removal is by codepoint, not by glyph identity.
Frequently asked questions
Why would removing emoji break my icon font?
Because icon fonts usually store glyphs in the Private Use Area (U+E000–U+F8FF), and the Emoji Remover's ranges include the whole PUA. So it deletes your icons along with any custom emoji. Use the Character Whitelist Builder to keep the icon codepoints, or don't run the Emoji Remover on icon fonts at all.
What happens to flag emoji?
Flags are pairs of regional-indicator symbols (U+1F1E6–U+1F1FF) that the OS shapes into a flag. Both indicators are in the emoji range, so both are removed and the flag vanishes from your font — the OS draws it instead. Expected behaviour.
Does removal affect text-mode emoji?
Yes. Text-mode symbol variants (☺ vs 😊, ✓, ❤) live in U+2600–U+27BF, which is in the removal set, so they're removed too. If you need a text-mode symbol preserved, use the Character Whitelist Builder with its explicit codepoint.
How are ZWJ family sequences handled?
Each person glyph in a sequence is an emoji codepoint and is removed; the joiner U+200D (General Punctuation) is outside the ranges and kept but orphaned. The composed family emoji no longer renders from your font and falls through to the OS emoji font.
Are skin-tone modifiers removed?
Yes — the Fitzpatrick modifiers U+1F3FB–U+1F3FF are inside the supplementary emoji range, as are the base emoji they modify, so both go together. No half-modified glyphs are left behind.
Does it remove variation selectors?
No. U+FE0E/U+FE0F (text/emoji variation selectors) and combining marks like U+20E3 are outside the four ranges and kept. They're inert without a base glyph, so leftover selectors are harmless — they may just look odd in a coverage report.
What if my entire font is emoji?
Then the keep-set is empty and the tool switches to a colour-table strip: it removes COLR/CPAL/sbix/SVG/CBDT/CBLC and returns the font de-coloured (label: 'Colour-table strip (emoji-only font)'). The emoji glyphs themselves stay. For full colour stripping use the Colour Table Remover; to keep only some emoji use the Character Whitelist Builder.
Why did my OTF fail to process?
opentype.js's writer can throw on CFF (PostScript) outlines, which most OTFs use. The error suggests converting to TTF first or using a smaller charset. Convert with a desktop tool, or run the strip through the hb-subset path in the build-pipeline guide, which handles CFF natively.
Will kerning survive emoji removal?
Not through this tool. The rebuild drops GPOS/GSUB, so kerning and ligatures are removed regardless of the emoji outcome. For kerning-critical fonts, use hb-subset with --layout-features='*'.
Can I exclude the Private Use Area from removal?
Not in the hosted tool — its ranges are fixed and include the PUA. To spare PUA glyphs, either use the Character Whitelist Builder to keep them explicitly, or run your own opentype.js script with the [0xe000,0xf8ff] range omitted (see the build-pipeline guide).
How do I see exactly what a font has before stripping?
Run Character Coverage Map to score the font against the real Unicode blocks, or Glyph Inspector to examine individual glyphs (it emits a real per-glyph SVG path, bbox, and viewBox). That tells you whether the emoji/dingbat/PUA ranges are populated and whether any are glyphs you actually use.
Is the output guaranteed to be smaller?
Smaller in glyph count, yes; smaller in bytes, not necessarily — output is uncompressed TTF and the rebuild drops compact layout tables, so a font with few emoji can end up similar in size or larger. The real win comes after compressing the result with TTF→WOFF2.
Privacy first
Every JAD Font tool runs entirely in your browser using opentype.js and the wawoff2 WASM Brotli encoder. Your fonts never leave your device — verified by zero outbound network requests during processing.