Accessibility & semantics

Icons injected by CSS stay silent for screen readers, so icon-only buttons still need a name

Icons injected by CSS stay silent for screen readers, so icon-only buttons still need a name

Every 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 @property and an initial-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-alt or aria-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)

Related
Accessibility & semantics

The right fix for double-tap zoom is one CSS line, not a viewport lock

Andy Bell shows that setting touch-action: manipulation on a button stops iOS Safari's double-tap-to-zoom without disabling page-wide zoom.

September 26, 2026
Accessibility & semantics

The custom checkbox stays hand-rolled until `::checkmark` actually ships

Andy Bell's new Piccalilli piece previews the ::checkmark pseudo-element and appearance: base, then quietly concedes no browser is working on it. Until that changes, the accessible checkbox is still a label wrapping a real input — not a div.

September 17, 2026
Browser releases

Safari Technology Preview 254 lets WebKit animate to auto and trims the radius longhands

WebKit's 8 October preview adds the interpolate-size property and the calc-size() function for working with intrinsic sizes, the size, min-size and max-size shorthands, border-*-radius side shorthands such as border-top-radius, symbols() and CSS linked parameters, alongside a long run of Grid, line-clamp and VoiceOver fixes.

October 9, 2026