EzeIcons
← Back to Blog

Making Icons Accessible: A Developer's Guide to ARIA and Semantic SVG

How to implement accessible icons with ARIA labels, decorative vs informative patterns, focus management, colour contrast, and screen reader testing across NVDA, JAWS, and VoiceOver.

JE

Jacob Edwards-Bytom

Founder & Lead Designer at EzeIcons · · 9 min read

Icons carry a surprising amount of communicative weight in modern interfaces. Navigation bars, toolbars, action buttons, status indicators — in some apps, icons outnumber text labels three to one. For the roughly 16% of the global population living with some form of disability (that's 1.3 billion people, according to the WHO), how you implement those icons determines whether your interface is usable or a series of unlabelled mysteries.

I've audited dozens of production codebases for icon accessibility. The same mistakes appear in almost every one: decorative icons that screen readers announce as gibberish, informative icons with no text alternative, SVG elements creating phantom focus traps. Every issue has a clean fix. This guide covers all of them.

Do All Icons Need Alt Text?

No. Icons fall into exactly two categories, and each requires a different implementation. Decorative icons — those that reinforce meaning already expressed by adjacent text — should be hidden from assistive technology entirely using aria-hidden="true". Informative icons — those that convey meaning not expressed elsewhere on the page — need a text alternative via aria-label, <title>, or visually-hidden text.

Getting the category wrong in either direction creates real problems. A decorative icon with a redundant label creates noise ("Search Search" announced by a screen reader). An informative icon with no label creates an information gap (a trash can button that announces as just "button").

The Decorative vs. Informative Decision

Every icon in your interface falls into one category. Ask yourself: if this icon disappeared and only the text remained, would the user lose any information?

If no, the icon is decorative. A search button labelled "Search" with a magnifying glass icon — the icon adds visual polish but zero information. A list item with a chevron arrow next to link text — the arrow is decorative.

If yes, the icon is informative. A toolbar button with only a trash can and no text — the icon is the only way to understand the button's function. A coloured status dot (green check, red X) — the icon communicates state that text doesn't.

This distinction drives everything else. Get it right and the implementation follows logically.

How to Implement Decorative Icons

Decorative icons should be invisible to assistive technology. Two patterns work reliably across all browser and screen reader combinations:

Pattern 1: aria-hidden on inline SVG

<button>
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
       aria-hidden="true" focusable="false">
    <path d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z"
          fill="none" stroke="currentColor" stroke-width="1.5"/>
  </svg>
  Search
</button>

The aria-hidden="true" tells screen readers to skip the element entirely. The focusable="false" prevents Internet Explorer and some older Edge versions from making the SVG element a tab stop — without it, keyboard users hit a phantom focus target inside the button.

Pattern 2: Empty alt on <img> SVGs

<button>
  <img src="/icons/search.svg" alt="" />
  Search
</button>

An empty alt="" explicitly marks the image as decorative. Omitting the alt attribute entirely is different — some screen readers will read the filename ("search dot svg"), which is worse than silence.

What not to do: Don't add role="presentation" to SVG elements. It's redundant when aria-hidden="true" is present and confusing without it. Stick to aria-hidden.

How to Implement Informative Icons

Informative icons need a text alternative that conveys the same meaning the icon communicates visually. Three patterns, each suited to different contexts:

Pattern 1: aria-label on the interactive parent

When an icon is the sole content of a button or link, label the interactive element — not the SVG:

<button aria-label="Delete item">
  <svg aria-hidden="true" focusable="false" viewBox="0 0 24 24">
    <!-- trash icon paths -->
  </svg>
</button>

Screen readers announce: "Delete item, button." The SVG itself is hidden because the button's label already provides the accessible name. Labelling both the button and the SVG creates double announcements.

Pattern 2: <title> inside SVG with role="img"

For standalone SVGs (not wrapped in a button or link), the <title> element provides an accessible name:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
     role="img" aria-labelledby="error-title">
  <title id="error-title">Error</title>
  <!-- error icon paths -->
</svg>

The role="img" ensures the SVG is treated as an image in the accessibility tree. The aria-labelledby explicitly wires the <title> element as the accessible name. Without both attributes, screen reader support for SVG <title> is inconsistent — Chrome handles it differently from Firefox, and Safari has its own quirks.

Pattern 3: Visually-hidden text

Add off-screen text adjacent to the icon. The text is invisible to sighted users but present in the accessibility tree:

<button>
  <svg aria-hidden="true" focusable="false" viewBox="0 0 24 24">
    <!-- trash icon paths -->
  </svg>
  <span class="sr-only">Delete item</span>
</button>

The sr-only class (Tailwind ships this as a utility) clips the element to 1px — invisible on screen, readable by assistive technology:

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border-width: 0;
}

This pattern is the most portable — it works with any element type, any framework, and doesn't depend on SVG-specific attributes.

Focus Management for Icon-Only Buttons

Icon-only buttons create two focus challenges that text buttons don't:

1. Visible focus indicators

The browser's default focus outline is often too thin, the wrong colour, or stripped by CSS resets. Icon-only buttons need a custom focus indicator:

.icon-button:focus-visible {
  outline: 2px solid var(--color-accent-brand);
  outline-offset: 2px;
  border-radius: 4px;
}

Use :focus-visible instead of :focus. The :focus-visible pseudo-class only activates for keyboard navigation, not mouse clicks — so the focus ring appears when a keyboard user tabs to the button, but not when a mouse user clicks it.

2. Touch target sizing

WCAG 2.5.8 (Target Size — Minimum) requires interactive elements to be at least 24x24 CSS pixels. WCAG 2.5.5 (Target Size — Enhanced, Level AAA) recommends 44x44px. A 20px icon inside a button with no padding fails both criteria.

The fix: add padding so the total interactive area meets the minimum:

.icon-button {
  padding: 12px; /* 20px icon + 12px padding on each side = 44px total */
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

Colour and Contrast Requirements

Icons used to convey information must meet WCAG 2.1 Success Criterion 1.4.11 (Non-text Contrast): a minimum 3:1 contrast ratio against their background.

In practice:

  • A grey icon on white needs to be at least #767676 (4.48:1 ratio)
  • A grey icon on a dark background (#1a1a1a) needs to be at least #949494
  • Brand-coloured icons (red error, green success) must hit 3:1 against the background individually, not just against each other

Never rely on colour alone to convey meaning. A traffic-light status system with red/amber/green circles must include shape differences (X, dash, tick) or text labels. Roughly 8% of men and 0.5% of women have some form of colour vision deficiency — a red circle and a green circle look identical to someone with deuteranopia.

The safest pattern combines colour, shape, and text:

<span class="flex items-center gap-2 text-red-600">
  <svg aria-hidden="true" viewBox="0 0 24 24"><!-- X icon --></svg>
  Payment failed
</span>

Three signals. Any two of them are sufficient. Colour-blind users see shape + text. Screen reader users hear text. Sighted users see all three.

Building an Accessible Icon Component

If you're using a component-based framework (React, Vue, Svelte), wrap your accessibility logic in a reusable component so developers make the right choice at every call site:

interface IconProps {
  svg: React.ReactNode
  label?: string   // undefined = decorative, string = informative
  className?: string
}

export function Icon({ svg, label, className }: IconProps) {
  if (!label) {
    return (
      <span className={className} aria-hidden="true">
        {svg}
      </span>
    )
  }

  return (
    <span className={className} role="img" aria-label={label}>
      {svg}
    </span>
  )
}

When label is omitted, the icon is decorative. When label is a string, the icon is informative. The API makes the accessibility decision explicit and impossible to forget.

For interactive contexts, the button handles the label:

<button aria-label="Delete item" onClick={handleDelete}>
  <Icon svg={<TrashSvg />} />  {/* decorative — button provides the name */}
</button>

Screen Reader Testing: The Minimum Test Matrix

Automated tools (axe, Lighthouse, WAVE) catch structural issues — missing labels, empty buttons, invalid ARIA. But they can't verify the actual screen reader experience. A button labelled "X" passes automated checks but fails usability. Manual testing is non-negotiable.

The minimum test matrix for UK/US products:

| Screen reader | Browser | Platform | User share | |---|---|---|---| | NVDA | Firefox | Windows | ~40% of screen reader users | | JAWS | Chrome | Windows | ~30% | | VoiceOver | Safari | macOS/iOS | ~25% | | TalkBack | Chrome | Android | ~5% |

NVDA is free. JAWS requires a licence but has a 40-minute demo mode. VoiceOver ships with every Apple device. TalkBack ships with every Android device. You have no excuse not to test with at least two of these.

Testing process per icon:

  1. Navigate to the icon using Tab (keyboard) or the screen reader's virtual cursor
  2. Decorative icons should produce no announcement — silence is correct
  3. Informative icons should announce their label: "Delete item" not "trash-icon dot svg"
  4. Icon buttons should announce role: "Delete item, button" not just "Delete item"
  5. Confirm no double announcements from both aria-label and <title> on the same element

Common Mistakes I See in Production

Filename as alt text. An <img src="trash-icon.svg"> with no alt attribute causes screen readers to read "trash dash icon dot svg." Always include alt="" (decorative) or alt="Delete" (informative).

Over-describing. "This is a magnifying glass icon that represents the search functionality of this application" is worse than "Search." Screen reader users want concise, actionable labels. Describe the function, not the appearance.

Duplicating labels. A button with aria-label="Delete" containing an SVG with <title>Delete</title> and a visually-hidden <span>Delete</span> produces triple announcements in some screen readers. Pick one labelling mechanism per element.

Forgetting focusable="false". IE11 is declining but still present in enterprise. Without focusable="false" on inline SVGs, keyboard users encounter phantom tab stops inside every icon.

Treating accessibility as a final step. Bolting accessibility onto finished icons is expensive and error-prone. Building it into your icon component from the start costs nothing extra.

Applying This to Your Icon Library

Every SVG icon in the EzeIcons library — across outlined, filled, duotone, and colour styles — ships as clean, semantic SVG with no extraneous <title> elements, no invisible paths, and consistent viewBox="0 0 24 24" dimensions. This means the SVGs slot directly into the accessible component patterns described above without conflicting markup.

The accessibility decision lives in your component code, not in the icon file. Your icon library provides the visual asset. Your component wraps it with the correct ARIA attributes based on context. Keeping those concerns separate makes both easier to maintain.

For more on SVG implementation specifics, see our guide on SVG vs PNG icons. For icon design patterns that support accessibility (clear metaphors, sufficient contrast, distinct shapes), see icon design principles.

More from the Blog