Icons injected by CSS stay silent for screen readers, so icon-only buttons still need a name
Marcus OseiEvery icon system eventually fails somebody. With icon fonts it was the screen reader user who heard a private-use codepoint read aloud, and the dyslexic reader whose font override wiped the icons out. With inline SVG it is usually the reviewer, who has to dig the control's actual label out from under a wall of path data. Lea Verou's new post, "The best way to use icons in Web UIs", argues for a third route: keep icons entirely in CSS, and keep them out of the HTML.
My question about any proposal like this is who it locks out. This one holds up better than I expected, provided you get one detail right.
Who the old approaches let down
Verou says the most common way to include icons today is inline SVG in the HTML. She also lists what went wrong with icon fonts, which put icons in CSS long before this proposal: large downloads, fallback glyphs when the font fails to load, screen readers announcing private-use codepoints, user font overrides (her example is overrides for dyslexia) removing the icons, and text hinting that was never designed for pictures. Her diagnosis is that "the problem was the font, not the CSS."
Two of those failures are accessibility failures, and both came from the delivery mechanism.
Icons as tokens, painted by a pseudo-element
In Verou's system, icons are design tokens. Each one is a CSS variable on :root, alongside your colors and other tokens, holding an image:
:root {
--icon-link-external: url("icons/link-external.svg");
}
Components opt in through custom properties: --icon-start prepends an icon through ::before, --icon-end appends one through ::after, and a higher-level --icon lets each component decide where its icon goes. The pseudo-element is an empty 1em square with background-color: currentColor, and a CSS mask cuts it into the icon's shape. A style query, @container style(--icon-start), limits all of this to elements that actually set the property.
Because the icon is a CSS value, it can change with pseudo-classes, media queries (high contrast among them), container queries and writing mode, and it can sit on details::marker, select::picker-icon or option::checkmark, which no markup approach can reach.
Where the accessible name comes from
According to Verou, the pseudo-element has no text, so assistive technology ignores it entirely. For an icon sitting next to a visible label, that is the behaviour you want. The button is announced by its text, and the picture adds nothing a screen reader needs to repeat.
An icon-only control has no visible label, so the icon has to supply one. Verou's answer is the alternative-text syntax for generated content:
content: "" / var(--icon-style-alt, "");
With that in place, she writes, <button style="--icon: var(--icon-trash); --icon-style-alt: 'Delete'"> is announced as "Delete". She adds that aria-label on the control still works. Whichever you choose, the rule is the same: an icon-only button without a name is a button nobody using a screen reader can identify.
She also covers forced colors. Her polish step adds forced-color-adjust: none so the masked fill survives forced-colors mode, and currentColor still follows the forced text color. People who rely on forced colors keep their icons.
The fallback for icon-only controls
Style queries are Baseline 2026. Verou cites Can I Use for roughly 93% global support, and Firefox was the last major browser to ship them, in May 2026. For most icons she treats the rest as acceptable progressive enhancement, since the UI still works without the pictures.
She makes an exception for cases where the icon carries the meaning, naming icon-only controls specifically. There she recommends a class such as .icon, .icon-start or .icon-end, so browsers without style query support still get the icon. Think about what that means in the code: if the accessible name lives in --icon-style-alt inside a @container style() block, a browser that skips the block loses the icon and the name together. For icon-only controls, use the class fallback or put the name on the element with aria-label.
Safari, and the other fine print
Verou documents CSS workarounds for three WebKit bugs: #301609 (icons not appearing until the user interacts with them), #320220 (fixed by the same workaround) and #325900 (inherit on non-inherited registered properties being unreliable). She is open that these workarounds push everyone toward less tidy code.
Her other caveats:
- Masks restrict you to monochrome icons. Varied opacity is fine.
- Because the whole pseudo-element is masked, it cannot have its own background or border. That needs a real element.
- Arbitrary animation still requires inline SVG.
- A relative icon URL resolves against the CSS file that uses it, so she recommends root-relative URLs or registering the token with
@propertyand aninitial-value.
She has packaged the system as @nudeui/icons, which ships the system and no icons.
My verdict
This is the icon-font idea without the font's accessibility failures. Icons are silent by default, the name stays on the control, and forced colors are handled. Before it goes into your next PR:
- Every icon-only control has an accessible name, from
--icon-style-altoraria-label. - Icon-only controls use the class fallback rather than relying on the style query alone.
- Icons next to visible text add no alternative text of their own.
- Test your icons in forced-colors mode.
Source: Lea Verou (lea.verou.me)