Failed icon fonts leave meaningless characters
Aliases: icon font · PUA · tofu · ligature icons · .notdef
What it is
An icon font encodes pictures as glyphs, usually in the Private Use Area (PUA) or via ligatures that replace a letter sequence with an icon. If the face never arrives, is blocked, or the subset lacks that codepoint, the browser does not leave a blank image. It falls back as ordinary text: boxes, tofu, gaps, or the letter the author used as the icon. To a screen reader that is still a character in the text stream, spoken as a codepoint or a letter. Failure leaves a meaningless character, not "the icon didn't draw".
This is that failure mode — not the FOIT/FOUT tradeoff for body text, and not whether CJK body faces should be subsetted.
Why it happens
Text layout maps each codepoint to a glyph in the current face. PUA codepoints usually have no picture in the system fallback chain, so .notdef boxes or blanks appear; if the icon is content: "\e001" and the face fails, the screen shows tofu. Ligature-based sets draw content: "home" (or a class's letter) as a house icon; when the face fails, "home" or that letter appears as real text inside the button. Either way the node is still text: copy yields a strange character, translation tools try to translate it, screen readers speak the character name, search hits those letters.
SVG, inline paths, and <img> fail differently. A missing image can omit paint, use alt, or leave a sized hole; it does not inject a codepoint into the text stream. Icon fonts parasitise the text pipeline, so the pipeline's fallback rules apply as designed — and those rules exist for missing language glyphs, not for a missing picture.
Where it stops holding
Internal tools whose icon face is cached and never updated can fail rarely, but a blocked CDN, an enterprise network that strips fonts, or "allow no remote fonts" exposes the characters at once. Using icon fonts only as decoration with aria-hidden reduces screen-reader noise; the visual garbage remains. Emoji fonts and system symbol fonts follow platform fallback into other colour glyphs, not PUA tofu; do not conflate them. Variable fonts and subsetting address body coverage and weight, not "the icon became a character". Icon strings in CSS content sit on pseudo-elements; some screen readers skip those, some do not — that is not an accessibility strategy.
Applying it
- Ship interactive icons as SVG, inline paths, or
<img>withalt/ an accessible name. Do not put operable meaning in an icon-font codepoint. - If an icon font remains: give every icon a text alternative (visible text or
aria-label) andaria-hidden="true"the decorative codepoint.font-display: optionalis not enough — optional failure still leaves the character. - Do not use letter ligatures as icons (failure reveals the word). Do not put PUA characters in copyable, translatable text.
- How to check: disable the face in devtools or block its request. Each icon slot should show a blank or a replacement graphic, not a box, a letter, or a speakable codepoint. With a screen reader in the failed state: it must not speak "private use" or "home". Copy the button; the clipboard must not contain a strange character.