ESC

Type to search the knowledge base.

Accessible SVGs

Decorative vs informative SVG icons — aria-hidden, title/desc, role=img, and pairing icons with accessible names on controls.

intermediate3 min read
  • accessibility
  • accessible-svgs

SVGs are graphics. Accessibility depends on purpose: decorative flourish vs information-carrying icon vs complex chart. Wrong markup either hides meaning or forces screen readers to dump path garbage.

Docs: MDN SVG accessibility, WAI images.

Decorative SVG (most UI icons inside labeled buttons)

<button type="button">
  <svg aria-hidden="true" focusable="false" width="16" height="16">
    <!-- paths -->
  </svg>
  Save
</button>

aria-hidden="true" and focusable="false" keep the icon out of the tree; the button text is the name.

Icon-only control

<button type="button" aria-label="Close">
  <svg aria-hidden="true" focusable="false" viewBox="0 0 24 24">…</svg>
</button>

Visible text is best when design allows; otherwise aria-label / aria-labelledby.

Informative standalone SVG

<svg role="img" aria-labelledby="logo-title" width="120" height="40">
  <title id="logo-title">Frontend Beauty</title>
  <!-- graphics -->
</svg>

role="img" + <title> (and optional <desc>) exposes a name.

<a href="/">
  <svg role="img" aria-label="Frontend Beauty home">…</svg>
</a>

Or wrap visually hidden text + aria-hidden on the svg.

Complex graphics / charts

  • Provide a text alternative: data table, summary, downloadable CSV.
  • Don’t rely on color alone for series — do not rely on color alone.
  • For interactive charts, keyboard paths and descriptions need product design — often a simplified accessible view.

Inline vs <img src="file.svg">

Form Alt approach
<img src="icon.svg" alt=""> Decorative
<img src="chart.svg" alt="Sales up 12%"> Informative
Inline SVG aria-hidden or role="img" + title

React / icon systems

function Icon({ title, ...props }: { title?: string } & SVGProps<SVGSVGElement>) {
  if (!title) {
    return <svg aria-hidden focusable="false" {...props} />;
  }
  return (
    <svg role="img" aria-label={title} focusable="false" {...props}>
      {props.children}
    </svg>
  );
}

Default decorative; pass title only when the SVG itself carries meaning.

Footguns

  1. Focusable SVG inside a button creating extra tab stops (focusable="false").
  2. SR reading every <text> node unintentionally.
  3. Empty aria-label on icons.
  4. Using color-only status icons without text.

Interview out-loud answer

“Decorative SVGs get aria-hidden inside named controls. Informative SVGs use role=img and title/aria-label. Icon-only buttons need an accessible name on the button. Charts need a text alternative beyond the graphic.”

Sprites and <use>

SVG sprite systems (<use href="#icon-x">) can orphan titles. Prefer putting the accessible name on the host control (button/link) and marking the svg aria-hidden="true" unless the graphic itself is the content.

Extra practice

Write a minimal demo in a scratch file or the playground: one happy path, one failure path, and one boundary input. If you cannot exhibit a bug that the pattern prevents, you do not own the concept yet — re-read the primary docs linked below and tighten the example until the failure is obvious.

Notes from real codebases

Teams that succeed here keep the rules mechanical: lint where possible, CI for the rest, and a short human checklist for what automation cannot see. Document exceptions with an owner name and a removal date so “temporary” escapes do not become permanent architecture.

Implementation notes

Wire this into real code on the next feature, not only a demo. Prefer the smallest change that encodes the rule — a shared helper, a lint rule, or a checklist item in the PR template. Revisit after a week of production traffic: if users or tests still hit the failure mode, the documentation (and the abstraction) are not sharp enough yet.

Further reading

Related guides