SVGFlow
Open editor
guides / accessible-icons

Making icons accessible without a title on every path

The common advice — "add a <title> to your SVG" — is wrong more often than it's right. Most icons on a page are decorative, and announcing them makes the page worse. The useful question is which icons carry meaning that isn't already in the text.

1. Decorative icons should be hidden

A trash icon next to the word "Delete" is decoration. A screen reader that announces both reads "trash, Delete". Hide it:

<button>
  <svg aria-hidden="true" focusable="false" ></svg>
  Delete
</button>

focusable="false" is there for older Edge, which put SVG elements in the tab order.

2. Icon-only controls need a name

An icon-only button has no text to fall back on, so the accessible name has to come from somewhere. Put it on the control, not the graphic:

<button aria-label="Delete file">
  <svg aria-hidden="true"></svg>
</button>

This survives the icon being swapped, and it works whether the SVG is inline, an <img>, or a background image.

Try this in the editor
Load the sample icon, select a path, and change its fill — the source panel updates as you go.

3. When title is the right answer

Use <title> when the SVG is a standalone document — loaded through <img>, opened directly, or embedded as an <object> — because there is no wrapping control to carry the label. Pair it with role="img" and reference it with aria-labelledby so the mapping is explicit rather than inferred.

4. Contrast applies to icons too

Meaningful graphics need 3:1 against their background under WCAG 1.4.11. Thin-stroke icon sets fail this routinely, especially the mid-grey "secondary action" state. If an icon is the only indicator of state — a validation tick, a status dot — it needs to clear the threshold, or carry a text label alongside it.

Note what the rule does and does not cover. Purely decorative icons are exempt, because nothing is lost if they are invisible. The threshold applies to the parts needed to understand the graphic, not to every pixel — a chart line must clear it, its drop shadow need not. And 3:1 is lower than the 4.5:1 body-text rule, on the reasoning that a shape is easier to resolve than a letterform.

5. Checking what is actually announced

Every rule above is about one computed value: the accessible name of the control. You can read it directly rather than inferring it from the markup.

In Chrome or Edge DevTools, select the element and open the Accessibility pane. It shows the computed name, the role, and — most usefully — which attribute won when several could have supplied the name. Firefox has the same thing under its Accessibility tab.

Two checks catch nearly everything. Tab through the interface: every stop should announce a name and a role, and any stop that announces only "button" is a missing label. Then look for stops that should not exist — a focusable decorative graphic is a keyboard user pressing Tab for nothing.

Automated scanners are worth running but will not finish the job: they reliably detect a missing name and cannot detect a wrong one. Nothing flags a delete button labelled "icon-trash-24" — only reading the accessibility tree, or listening with VoiceOver (Cmd+F5 on macOS) or NVDA, will.

6. Mistakes that survive code review

  • aria-label on the <svg> instead of the button. Support varies, and several screen readers ignore it entirely unless the element also carries role="img". The wrapping control is the reliable place — it is what receives focus and what gets announced.
  • A visible text label and an aria-label that disagree. A button reading "Delete" with aria-label="Remove file" breaks voice control: the user says "click Delete" and nothing happens, because the accessible name is something else. WCAG 2.5.3 requires the visible text to appear in the accessible name.
  • aria-hidden="true" on something focusable. This creates a keyboard stop that announces nothing at all — strictly worse than either hiding it properly or naming it. It is why focusable="false" accompanies aria-hidden on inline SVG.
  • Exporter <title> left in the file. Illustrator and Figma happily write <title>Layer 1</title> or <title>Artboard 3</title>. That is not a neutral leftover: it becomes the announced name, so a screen reader reads "Artboard 3" where the user expected "Delete file". Worth checking before you strip metadata, since a real <title> and a junk one look identical to an optimizer — see what not to strip.

The through-line: put the name on the thing the user operates, keep it identical to what they can see, and make sure the graphic itself is either properly hidden or properly described — never halfway between.

7. The four cases, in short

Almost every icon on a page is one of four things. Identify which, and the markup follows:

  • Icon beside its own text label — decoration. aria-hidden="true" focusable="false" on the SVG, nothing else. The text is already the name.
  • Icon-only button or link — the control needs the name. aria-label on the <button> or <a>, and the SVG stays hidden. Name it for what it does, not what it depicts: "Delete file", not "Trash can".
  • Icon carrying meaning with no text — a status dot, a validation tick, a severity marker. Needs a name and needs to clear 3:1 contrast, because it is the only thing communicating that state. Strongly consider a text label instead; colour and shape alone exclude more people than they save space.
  • Standalone image — loaded through <img>, or opened as its own document. No wrapping control exists, so the label goes inside the file: role="img" plus <title> referenced by aria-labelledby, or simply the alt attribute if it is an <img>.

If an icon does not obviously fit one of these, that is usually the finding — an icon whose purpose is ambiguous to you will be ambiguous to everyone, and the fix is editorial rather than technical.