EzeIcons
← Back to Blog

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

6 min read

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

Icons are everywhere in modern interfaces. Navigation bars, toolbars, buttons, status indicators, empty states — icons carry a significant portion of the communicative load in most digital products. For the estimated 7-8 million people in the UK who use assistive technologies, and the roughly 300 million globally, how you implement icons determines whether your interface is navigable or a series of unlabelled mysteries.

This guide covers every accessibility technique relevant to icons: when to use each approach, the exact markup, how to test it, and the common mistakes that ship in production.

The Two Fundamental Categories

Every icon in your interface falls into one of two categories, and the category determines everything about the correct implementation:

Decorative icons add visual interest or reinforce meaning that is already expressed in nearby text. A search button that says "Search" with a magnifying glass icon — the icon is decorative. A list item with a chevron arrow beside text that already links to the next step — the arrow is decorative.

Informative icons convey meaning that is not expressed anywhere else on the page. A toolbar button with only a trash can icon and no label — the icon is informative. A status indicator that uses colour and a check/cross icon to communicate success or failure — the icon is informative.

The implementation differs entirely between these two categories. Getting the category wrong in either direction creates real accessibility problems: unnecessary noise for screen reader users (decorative icons that get announced) or complete information loss (informative icons with no alternative text).

Implementing Decorative Icons

Decorative icons should be hidden from assistive technologies. There are two reliable patterns:

Pattern 1: aria-hidden="true" on 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.197A7.5 7.5 0 1 0 5.197 15.803L0 21"/>
  </svg>
  Search
</button>

The aria-hidden="true" attribute tells screen readers to skip this element entirely. The focusable="false" attribute is necessary for IE11 compatibility — without it, IE11 makes SVG elements focusable by default, creating phantom tab stops.

Pattern 2: Empty alt on <img> with SVG source

<button>
  <img src="/icons/search.svg" alt="" aria-hidden="true" />
  Search
</button>

An empty alt="" attribute signals to assistive technologies that the image is decorative. Using alt without a value (no attribute at all) is different — it causes some screen readers to read the filename. Always include alt="" explicitly.

Important: do not add role="presentation" on SVG elements. It is redundant when aria-hidden="true" is present and confusing without it. Stick to aria-hidden.

Implementing Informative Icons

Informative icons need a text alternative that conveys the same meaning the icon conveys visually. There are several patterns, each appropriate for different contexts:

Pattern 1: aria-label on the interactive element

When an icon is the only content inside an interactive element (button, link), add aria-label to the interactive element itself:

<!-- Icon-only button with aria-label -->
<button aria-label="Delete item">
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
    <!-- trash icon paths -->
  </svg>
</button>

The aria-label on the button overrides the accessible name computation for the entire button. The icon SVG itself gets aria-hidden="true" because the button's label already provides the accessible name — we do not want the SVG to add anything.

Pattern 2: <title> element inside SVG

The <title> element gives an SVG an accessible name without hiding it from the accessibility tree:

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

Use role="img" to ensure the SVG is treated as an image element, and aria-labelledby pointing to the <title> element's id to explicitly wire the label. Without these attributes, screen reader support for SVG <title> is inconsistent across browsers.

Pattern 3: Visually hidden text

Add off-screen text adjacent to the icon using a class that positions it outside the viewport without using display:none (which would hide it from screen readers too):

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

The sr-only pattern (or Tailwind's sr-only utility class) uses CSS to clip the element to a 1px area that is invisible but still in the accessibility tree:

.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 flexible because it works with any element type and is not limited to SVG.

Standalone SVG Icons in Non-Interactive Contexts

Sometimes icons appear as standalone informative elements — not wrapped in a button or link — such as a status icon beside text that is not repeated elsewhere:

<!-- Status icon with no accompanying text -->
<span class="flex items-center gap-2">
  <svg role="img" aria-label="Error" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
    <title>Error</title>
    <!-- error icon paths -->
  </svg>
  Your payment could not be processed.
</span>

Here, role="img" and aria-label together give the SVG a proper accessible role and name. Note that in this particular example, "Your payment could not be processed" already communicates the error state — so you might argue the icon is actually decorative and should be aria-hidden. The judgement call depends on whether the icon conveys additional urgency or semantic information beyond what the text provides.

When in doubt, ask: if the icon disappeared and the user could only read the text, would they lose any information? If yes, the icon is informative. If no, it is decorative.

Focus Management for Icon Buttons

Icon-only buttons that are interactive need visible focus indicators for keyboard users. The browser's default outline is often insufficient — too thin, wrong colour for the background, or stripped by CSS resets.

/* Visible, high-contrast focus indicator */
button:focus-visible {
  outline: 2px solid var(--color-accent-brand);
  outline-offset: 2px;
  border-radius: 4px;
}

Use :focus-visible rather than :focus — it shows the focus ring only for keyboard navigation, not after mouse clicks, which is the behaviour most users expect.

Touch target size also matters for icon buttons on mobile. The minimum recommended touch target size is 44×44px (WCAG 2.5.5 Level AAA recommends 24×24 for the target area, but 44×44 for the total interactive zone including padding). An icon-only button with a 20px icon needs 12px of padding on all sides to meet this target:

.icon-button {
  padding: 12px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

Colour and Contrast for Icons

Icons used to convey information — status indicators, directional indicators, data visualisation markers — must meet colour contrast requirements. WCAG 2.1 Success Criterion 1.4.11 (Non-text Contrast) requires a minimum contrast ratio of 3:1 for UI components and graphical objects.

In practice, this means:

  • A grey icon on a white background needs to be at least mid-grey (approximately #767676 on white)
  • Coloured icons (red error, green success) need sufficient contrast against the background, not just against each other
  • Icons that use both colour and shape to convey meaning (a red X vs a green checkmark) are more accessible than those that rely on colour alone

Never use colour as the only means of conveying information (WCAG 1.4.1). A traffic-light status system where red/yellow/green circles are the only indicator needs to include a shape difference (X/dash/check) or text label so that colour-blind users can understand the states.

Screen Reader Testing

Accessibility implementation is only as reliable as your testing. The minimum test setup should cover:

NVDA + Firefox (Windows): The most widely-used screen reader and browser combination. NVDA is free and represents a large portion of the screen reader user population.

JAWS + Chrome (Windows): Common in enterprise and accessibility-first contexts.

VoiceOver + Safari (macOS/iOS): Essential for any product with mobile users, since VoiceOver is built into every Apple device.

TalkBack + Chrome (Android): For Android mobile coverage.

Testing process for each icon in your UI:

  1. Navigate to the icon using the Tab key (keyboard navigation) or the screen reader's virtual cursor
  2. Confirm the announcement: the screen reader should announce the icon's accessible name if informative, and skip it silently if decorative
  3. For icon buttons, confirm the button role is announced: "Delete item, button" not just "Delete item"
  4. Confirm no garbled announcements from uncombined aria-label and <title> elements

Common Mistakes to Avoid

Using role="presentation" instead of aria-hidden: role="presentation" removes the role but not the element from the accessibility tree. Screen readers may still encounter the element. Use aria-hidden="true" for icons you want fully hidden.

Omitting focusable="false" on inline SVG: In IE11 (still used in enterprise contexts), inline SVG is focusable by default. Add focusable="false" to prevent phantom tab stops.

Using the filename as alt text: <img src="trash-icon.svg"> without an alt attribute causes some screen readers to read "trash dash icon dot svg". Always include alt="" or alt="Delete item" explicitly.

Forgetting to test with actual screen readers: Automated accessibility checkers (axe, Lighthouse, WAVE) catch structural issues but cannot reliably verify the quality of accessible names or the actual screen reader experience. Manual testing is essential.

Over-describing icons: "This is a magnifying glass icon that represents search functionality" is worse than "Search". Give screen reader users concise, actionable labels.

Integration With SVG Icon Libraries

If you are using SVG icons from a library like EzeIcons or Heroicons, the accessible implementation lives in your component code, not in the icon file itself. A reusable icon component in React might look like:

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>
  )
}

This pattern makes the accessibility decision explicit at the call site. When label is undefined, the icon is decorative. When label is a string, the icon is informative. The pattern prevents the common mistake of forgetting to handle either case.

For icons in interactive elements, let the button or link handle the accessible name:

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

Explore the technology icons and other categories at EzeIcons with these patterns in mind. Every SVG icon is clean, semantic, and ready to integrate with the accessible component patterns described here — no extraneous paths, no title elements that conflict with your component labels, consistent viewBox dimensions that make sizing straightforward.

Accessibility is not a final step to bolt on before launch. It is a design and implementation decision made at every icon in your interface. Apply these patterns consistently and your icons will serve every user who encounters them.