Accessible SVG & icons

Inline SVG is everywhere — chevrons, status dots, social glyphs, the little magnifier inside a search button. Each one is either decoration, which a screen reader should skip, or information, which it must convey in words. The trouble is that a raw <svg> sits in an awkward middle ground: some browsers expose its child shapes as a meaningless cluster of nodes, others announce nothing, and a stray <title> or <text> inside it can leak partial labels. The fix is always to decide which kind of graphic you have, then make the markup say so explicitly.

This lesson works through the three SVG and icon defects that turn up most often in audits. Each is a one- or two-attribute change, and each maps to the same root criterion — 1.1.1 Non-text Content — so once you internalise the decision, every icon on the page follows the same rule.

What you’ll learn

How to hide a purely decorative SVG from assistive technology with aria-hidden="true" (and why focusable="false" still matters); how to give an informative icon a real accessible name with role="img" and a <title> or aria-label; and how to make an icon-only link or button announce its purpose so it isn’t read as an empty control.

Standards this lesson maps to
Standard Criterion Level What it requires
WCAG 2.2 1.1.1 Non-text Content A Every non-text element either has a text alternative that serves the same purpose, or — if purely decorative — is hidden from assistive technology.
WCAG 2.2 4.1.2 Name, Role, Value A An interactive control built from an icon exposes a name and role to assistive technology.
WCAG 2.2 2.4.4 Link Purpose (In Context) A The purpose of each link can be determined from its accessible name — an icon-only link still needs one.
WCAG 2.2 1.4.1 Use of Color A Where an icon conveys meaning by colour alone (a status dot), the meaning is also available in text.
EN 301 549 9.1.1.1 (incorporates WCAG) European harmonised standard; references the WCAG A/AA set including non-text content.
Section 508 502.3 / 504 (incorporates WCAG A & AA) US federal ICT must meet WCAG 2.0 Level A and AA, including text alternatives for graphics.
ADA Title II WCAG 2.1 AA (DOJ rule) AA US state/local government web content must conform to WCAG 2.1 AA.

The three problems we’ll fix

Each card below isolates one common SVG or icon defect. For every issue you get a plain-language statement of the problem, a Bad example (shown as escaped, non-running code so it can’t affect this page), a Good example, the copyable Code, and an ordered fix.

Decorative inline SVG not hidden from assistive tech

WCAG 2.2 · 1.1.1 A EN 301 549 Section 508

A chevron beside a link, a flourish next to a heading, a glyph that simply repeats the text next to it — these add nothing for a screen reader user, but a bare <svg> isn’t silent. Depending on the browser it may expose its <path> and <g> children as anonymous nodes, announce a stray <title>, or land in the tab order in older Internet Explorer because SVG is focusable by default there. The result is noise: extra stops, “graphic” with no name, or a label that duplicates the adjacent text. Decorative graphics must be removed from the accessibility tree (1.1.1).

Bad

The icon purely repeats the visible word “Settings”, yet it’s left exposed. Assistive tech may announce a nameless graphic or, worse, leak its inner <title>, doubling the label.

bad-decorative.html
<a href="/settings">
  <svg viewBox="0 0 24 24">
    <title>Gear</title>
    <path d="M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8Z"/>
  </svg>
  Settings
</a>

Good

The SVG is marked aria-hidden="true" so it’s dropped from the accessibility tree, and focusable="false" keeps it out of the tab order. The link’s name comes entirely from the visible text “Settings”.

good-decorative.html
<a href="/settings">
  <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
    <path d="M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8Z"/>
  </svg>
  Settings
</a>

Code

For a CSS background image or an icon font glyph, the same idea applies: keep it out of the tree. A background image is already ignored; a pseudo-element glyph should be hidden with aria-hidden="true" on its host or set via CSS content with an empty alt equivalent.

decorative-variants.html
<!-- Inline SVG used as decoration -->
<svg aria-hidden="true" focusable="false" viewBox="0 0 24 24">…</svg>

<!-- <img> used as decoration: empty alt removes it from the tree -->
<img src="divider.svg" alt="">

<!-- Icon-font glyph in a span, hidden from AT -->
<span class="icon-star" aria-hidden="true"></span>

How to fix

  1. Decide the icon is decorative — it adds no meaning the surrounding text doesn’t already carry.
  2. Add aria-hidden="true" to the <svg> so it’s removed from the accessibility tree.
  3. Add focusable="false" as well, to keep legacy browsers from putting the SVG in the tab order.
  4. Remove any <title> from inside a decorative SVG so it can’t leak a label.
  5. For decorative <img> use alt=""; verify the graphic no longer appears in the accessibility tree.

Informative icon with no accessible name

WCAG 2.2 · 1.1.1 A 4.1.2 A 1.4.1 A EN 301 549

Sometimes the icon is the information: a green tick that means “verified”, a red dot that means “offline”, a warning triangle in a table cell with no accompanying word. When that meaning lives only in the shape or the colour, a screen reader user gets nothing — and a user who can’t distinguish the colour gets nothing either (1.4.1). An informative SVG needs a programmatic name. Give it role="img" so it’s exposed as a single image, then name it with a child <title> (referenced via aria-labelledby for the widest support) or an aria-label.

Bad

The tick’s meaning, “Verified”, is carried only by its shape and colour. There is no role and no name, so assistive tech announces nothing useful (1.1.1) and colour alone carries the status (1.4.1).

bad-informative.html
<svg viewBox="0 0 24 24" class="status-ok">
  <path d="m10 16-4-4 1.4-1.4 2.6 2.6 5.6-5.6L17 9l-7 7Z"/>
</svg>

Good

The SVG is given role="img" and named by a <title> through aria-labelledby. It now announces as “Verified, image”, and the meaning no longer depends on colour.

good-informative.html
<svg role="img" viewBox="0 0 24 24"
     aria-labelledby="ok-title" focusable="false">
  <title id="ok-title">Verified</title>
  <path d="m10 16-4-4 1.4-1.4 2.6 2.6 5.6-5.6L17 9l-7 7Z"/>
</svg>

Code

aria-label is a shorter alternative when no inline title is wanted. For a status conveyed by colour, the safest pattern is real adjacent text plus a decorative icon — that satisfies 1.1.1, 4.1.2 and 1.4.1 with no ARIA at all.

informative-variants.html
<!-- aria-label instead of a child title -->
<svg role="img" aria-label="Verified" focusable="false" viewBox="0 0 24 24">
  <path d="m10 16-4-4 1.4-1.4 2.6 2.6 5.6-5.6L17 9l-7 7Z"/>
</svg>

<!-- Best for status: visible text + decorative icon -->
<span class="status">
  <svg aria-hidden="true" focusable="false" viewBox="0 0 24 24">…</svg>
  Verified
</span>

How to fix

  1. Confirm the icon carries meaning the surrounding text doesn’t — otherwise treat it as decorative instead.
  2. Add role="img" to the <svg> so it’s exposed as one image, not a bag of shapes.
  3. Name it with a child <title> referenced by aria-labelledby, or with aria-label.
  4. Write the name as the meaning (“Verified”, “Offline”), not the picture (“green tick”).
  5. If the status is shown by colour, also expose it in text so 1.4.1 is met — often easiest with visible text and a decorative icon.

Icon-only link or button with no text

WCAG 2.2 · 1.1.1 A 4.1.2 A 2.4.4 A EN 301 549 Section 508

A magnifier that submits search, a hamburger that opens the menu, an “×” that closes a dialog — interactive, but with no visible words. If the only thing inside is an SVG with no name, the control is announced as “button” or “link” with nothing more, and the user can’t tell what it does (4.1.2, 2.4.4). The control itself must carry an accessible name. Put the name on the <button> or <a> — with visually-hidden text or aria-label — and hide the inner SVG so it can’t compete with or override that name.

Bad

The button contains only an unnamed icon. Screen readers announce “button” with no purpose; voice-control users have no label to speak.

bad-icon-button.html
<button type="submit">
  <svg viewBox="0 0 24 24">
    <path d="M10 4a6 6 0 1 0 3.5 10.9l4.3 4.3 1.4-1.4-4.3-4.3A6 6 0 0 0 10 4Z"/>
  </svg>
</button>

Good

The button gets a real name from visually-hidden text, and the icon is hidden with aria-hidden="true". It announces as “Search, button” and stays a clean icon visually.

good-icon-button.html
<button type="submit">
  <svg aria-hidden="true" focusable="false" viewBox="0 0 24 24">
    <path d="M10 4a6 6 0 1 0 3.5 10.9l4.3 4.3 1.4-1.4-4.3-4.3A6 6 0 0 0 10 4Z"/>
  </svg>
  <span class="visually-hidden">Search</span>
</button>

Code

aria-label on the control is an equally valid way to name it, and works the same on an icon-only link. Keep the inner SVG aria-hidden="true" either way so a stray title can’t override the label.

icon-control-variants.html
<!-- Icon-only button named with aria-label -->
<button type="button" aria-label="Close dialog">
  <svg aria-hidden="true" focusable="false" viewBox="0 0 24 24">
    <path d="M6 6l12 12M18 6L6 18"/>
  </svg>
</button>

<!-- Icon-only link named with aria-label (2.4.4) -->
<a href="/cart" aria-label="View cart">
  <svg aria-hidden="true" focusable="false" viewBox="0 0 24 24">…</svg>
</a>

How to fix

  1. Put the accessible name on the control itself — the <button> or <a>, not the SVG.
  2. Use a visually-hidden span (preferred — it survives translation) or aria-label to supply that name.
  3. Name the action, not the picture: “Search”, “Open menu”, “Close dialog”, “View cart”.
  4. Mark the inner SVG aria-hidden="true" and focusable="false" so it doesn’t add a second name or a tab stop.
  5. Check the rendered control announces as “name + role”, and that its target is at least 24×24 CSS px (2.5.8).

Recap

  • Decide first: is the SVG decoration or information? Decoration gets aria-hidden="true" and focusable="false" so it’s skipped entirely (1.1.1).
  • An informative icon needs a real accessible name — role="img" with a <title> or an aria-label — so its meaning reaches everyone (1.1.1, 4.1.2).
  • An icon-only link or button must carry its own text: a visually-hidden label or aria-label on the control, with the inner SVG hidden (1.1.1, 2.4.4, 4.1.2).
  • Never let colour alone carry meaning — pair a status icon with text (1.4.1).

The same fixes satisfy WCAG, EN 301 549, Section 508, and ADA Title II at once — classify each graphic correctly and you meet them all.