SVG-Icons barrierefrei einsetzen
A11Y
WCAG
Barrierefreiheit · SVG · ARIA · Hyvä Theme
SVG-Icons barrierefrei einsetzen
Bedeutungstragende und dekorative Icons richtig auszeichnen

SVG-Icons ersetzen zunehmend Icon-Fonts, bringen aber ohne bewusste Auszeichnung keinerlei Text für Screenreader mit. Wer bedeutungstragende Icons wie einen Wishlist-Herz-Button nicht mit einem klaren Namen versieht und dekorative Icons nicht mit aria-hidden versteckt, baut unsichtbare oder verwirrende Bedienelemente. Dieser Artikel zeigt die richtige Technik für beide Fälle, mit einem vollständigen Praxisbeispiel.

13 Min. Lesezeit aria-hidden · role=img · title-Element Magento 2.4.8 · Hyvä Theme · Alpine.js

1. Warum SVG-Icons ein eigenes Accessibility-Problem sind

SVG-Icons haben Icon-Fonts in den meisten modernen Frontends abgelöst, weil sie schärfer skalieren, sich mit CSS einfärben lassen und kein Symbol-Font-Nachladen mehr brauchen. Bei der Barrierefreiheit bringt dieser Wechsel aber eine neue Falle mit sich: Ein <svg>-Element ist im Gegensatz zu einem <img> kein Element mit einer erzwungenen alt-Pflicht, und ohne explizite Auszeichnung liefert es für assistive Technologien schlicht gar keinen Text. Icon-Fonts wurden von Screenreadern zumindest als Textknoten erkannt, wenn auch oft mit merkwürdigen Ansagen. Ein unbeschriftetes SVG dagegen wird je nach Browser und Screenreader entweder komplett ignoriert oder inkonsistent als „Grafik" ohne jede Bedeutung vorgelesen.

In einem typischen Hyvä-Header stecken gleich mehrere Icon-Only-Elemente: das Lupensymbol für die Suche, der Warenkorb, das Wishlist-Herz und das Hamburger-Menü für die mobile Navigation. Ist keines davon korrekt beschriftet, kann ein blinder Nutzer die zentrale Navigation und den Checkout gar nicht bedienen. Das betrifft direkt zwei Erfolgskriterien der Konformitätsstufe A: WCAG 1.1.1 Non-text Content und WCAG 4.1.2 Name, Role, Value, beide Teil der gesetzlichen Mindestanforderung nach BFSG und EN 301 549.

2. Bedeutungstragende und dekorative Icons unterscheiden

Der wichtigste Schritt vor jeder technischen Umsetzung ist eine einfache Frage: Geht Information verloren, wenn das Icon entfernt wird, ohne dass etwas anderes an seine Stelle tritt? Steht neben dem Icon bereits sichtbarer Text, etwa „Zur Wunschliste hinzufügen" direkt neben einem Herzsymbol, ist das Icon rein dekorativ. Der Text trägt die gesamte Bedeutung, das Icon liefert nur einen visuellen Beschleuniger für sehende Nutzer. Steht das Icon dagegen allein, etwa als einziger Inhalt eines Buttons, ist es bedeutungstragend und muss selbst einen zugänglichen Namen liefern.

Auch rein gestalterische Elemente wie Aufzählungspunkte in Icon-Form, Trennlinien oder Hintergrundmuster zählen zu den dekorativen Fällen, ebenso Icons, die eine bereits per <h2> oder Badge-Text vermittelte Information nur wiederholen. Eine einfache Checkliste hilft in der Praxis: Gibt es sichtbaren Text mit derselben Bedeutung in unmittelbarer Nähe? Dann dekorativ. Ist das Icon der einzige Inhalt eines interaktiven Elements oder vermittelt es eine Information, die nirgends sonst als Text steht, etwa ein Warnsymbol für niedrigen Lagerbestand? Dann bedeutungstragend, mit eigenem Namen.

3. Dekorative Icons korrekt verstecken: aria-hidden und focusable

Für dekorative Icons lautet die Regel aria-hidden="true", damit der Screenreader das Element komplett aus dem Accessibility-Baum entfernt und nicht doppelt ansagt, was der begleitende Text bereits vorliest. Zusätzlich gehört focusable="false" auf jedes SVG, das in einem interaktiven Element wie <button> oder <a> steckt: Ältere Internet-Explorer- und teils Edge-Versionen behandeln <svg> standardmäßig als fokussierbar, wodurch die Tastaturnavigation pro Icon einen zusätzlichen, funktionslosen Tab-Stopp erzeugt. Moderne Browser sind davon zwar nicht mehr betroffen, das Attribut kostet aber nichts und schadet nicht.

Ein häufiger Fehler ist, in einem dekorativen SVG trotzdem ein <title>-Element stehen zu lassen, etwa aus einer Icon-Bibliothek kopiert. Auch wenn aria-hidden="true" gesetzt ist, sollte das <title>-Element dann entfernt oder zumindest inhaltlich sinnfrei sein, weil manche Browser-Erweiterungen und Tooltip-Skripte den title-Inhalt trotzdem als Hover-Text anzeigen und damit widersprüchliche Informationen liefern.


<!-- WRONG: decorative icon is exposed to assistive technology, announced twice -->
<button type="button" class="inline-flex items-center gap-2">
  <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
  </svg>
  Zum Warenkorb hinzufügen
</button>

<!-- RIGHT: icon hidden from assistive technology, button label carries the meaning -->
<button type="button" class="inline-flex items-center gap-2">
  <svg class="w-4 h-4" aria-hidden="true" focusable="false" fill="none" stroke="currentColor" viewBox="0 0 24 24">
    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
  </svg>
  Zum Warenkorb hinzufügen
</button>

4. Bedeutungstragende Icons benennen: title, aria-label, role=img

Steht ein SVG-Icon allein im Markup, ohne begleitenden Text, gibt es drei zusammenwirkende Techniken. Erstens ein <title>-Element als erstes Kind des <svg>, verknüpft über aria-labelledby mit der ID des Titels. Zweitens role="img" auf dem <svg>-Element selbst, weil der implizite Rollenstatus von SVGs zwischen Browsern uneinheitlich ist und ohne explizite Rolle mancher Screenreader das Element als reines Grafikcontainer-Element ohne Ansage behandelt. Drittens, als Alternative zum <title>-Element, ein direktes aria-label auf dem <svg>, wenn kein sichtbares Titel-Tooltip gewünscht ist.

Wichtig ist, diese drei Techniken nicht wahllos zu kombinieren: aria-label hat in der Accessible-Name-Berechnung Vorrang vor aria-labelledby, welches wiederum Vorrang vor dem <title>-Element hat. Wer alle drei gleichzeitig mit unterschiedlichem Text setzt, erzeugt inkonsistente Ansagen zwischen Screenreadern. Am robustesten ist entweder <title> plus aria-labelledby plus role="img", oder allein aria-label plus role="img", aber nicht beide Varianten gemischt mit widersprüchlichem Text.


<!-- Standalone meaningful icon: warning triangle without accompanying text -->
<svg
    role="img"
    aria-labelledby="warning-icon-title"
    class="w-5 h-5 text-amber-600"
    fill="currentColor"
    viewBox="0 0 20 20"
>
  <title id="warning-icon-title">Warnung: Lagerbestand niedrig</title>
  <path d="M8.257 3.099c.765-1.36 2.72-1.36 3.486 0l6.516 11.59c.75 1.334-.213 3.011-1.743 3.011H3.485c-1.53 0-2.493-1.677-1.743-3.011l6.516-11.59zM10 13a1 1 0 100-2 1 1 0 000 2zm-.75-6.5a.75.75 0 011.5 0v3a.75.75 0 01-1.5 0v-3z"/>
</svg>

<!-- Alternative without a visible <title>: aria-label directly on the svg -->
<svg role="img" aria-label="Warnung: Lagerbestand niedrig" class="w-5 h-5 text-amber-600" fill="currentColor" viewBox="0 0 20 20">
  <path d="M8.257 3.099c.765-1.36 2.72-1.36 3.486 0l6.516 11.59c.75 1.334-.213 3.011-1.743 3.011H3.485c-1.53 0-2.493-1.677-1.743-3.011l6.516-11.59z"/>
</svg>

5. Praxisbeispiel: Icon-Only-Button mit korrektem Accessible Name

Der Wishlist-Herz-Button ist der klassische Fall eines Icon-Only-Buttons: kein sichtbarer Text, nur ein Herzsymbol, das je nach Zustand gefüllt oder als Umriss dargestellt wird. Der entscheidende Grundsatz lautet: Wenn ein SVG innerhalb eines interaktiven Elements wie <button> steckt, sitzt der zugängliche Name idealerweise nicht auf dem SVG selbst, sondern auf dem umschließenden Button, etwa über aria-label. Das SVG bekommt dann konsequent aria-hidden="true", weil sonst je nach Screenreader sowohl der Button-Name als auch ein eventuell vorhandener SVG-Titel angesagt werden und die Ausgabe unnötig lang oder widersprüchlich wird.

Genauso wichtig wie die initiale Beschriftung ist, dass sich der Name mit dem Zustand ändert. Ein reiner Toggle-Button, der immer „Wunschliste" heißt, verrät weder sehenden noch blinden Nutzern, ob ein Produkt bereits gemerkt ist. Mit Alpine.js lässt sich der zugängliche Name direkt an den reaktiven Zustand binden, sodass aria-label und aria-pressed immer synchron mit der sichtbaren Herzfarbe wechseln, statt unabhängig voneinander gepflegt zu werden und dabei auseinanderzulaufen.


<!-- Hyva phtml: wishlist icon-only button, accessible name lives on the button -->
<button
    type="button"
    x-data="{ inWishlist: <?= $inWishlist ? 'true' : 'false' ?> }"
    x-on:click="inWishlist = !inWishlist; $dispatch('wishlist:toggle', { productId: <?= (int) $productId ?> })"
    x-bind:aria-pressed="inWishlist.toString()"
    x-bind:aria-label="inWishlist ? 'Von der Wunschliste entfernen' : 'Zur Wunschliste hinzufügen'"
    class="inline-flex items-center justify-center w-11 h-11 rounded-full hover:bg-slate-100 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-zinc-800"
>
    <svg
        class="w-5 h-5"
        x-bind:fill="inWishlist ? 'currentColor' : 'none'"
        aria-hidden="true"
        focusable="false"
        stroke="currentColor"
        viewBox="0 0 24 24"
    >
        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
              d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"/>
    </svg>
</button>

6. Inline SVG vs. SVG-Sprite: Unterschiede bei der Barrierefreiheit

SVG-Sprites, bei denen ein zentrales Sprite-Sheet mehrere <symbol>-Definitionen bündelt und einzelne Icons per <use xlink:href="#icon-heart"> referenziert werden, sind aus Performance-Sicht attraktiv: eine einzige Datei statt vieler einzelner Inline-SVGs im Markup. Bei der Barrierefreiheit gibt es dabei eine Besonderheit, die häufig übersehen wird: Ein <title>-Element innerhalb eines <symbol> in der Sprite-Datei wird von manchen Screenreadern und Browsern beim Instanziieren über <use> nicht zuverlässig als Accessible Name übernommen, besonders wenn das Sprite als externe Datei eingebunden ist statt inline im Dokument zu stehen.

Die robuste Lösung ist, die Beschriftung nicht in der <symbol>-Definition zu pflegen, sondern pro Verwendungsstelle direkt auf dem <svg>- oder <use>-Element, das die Sprite-Referenz einbindet. Jede Instanz eines Icons kann so einen eigenen, kontextabhängigen Namen bekommen, etwa „Zur Wunschliste hinzufügen" an einer Stelle und „Bereits auf der Wunschliste" an einer anderen, obwohl beide auf dasselbe <symbol> verweisen. Diese Instanz-basierte Beschriftung funktioniert zuverlässig über alle relevanten Browser-Screenreader-Kombinationen hinweg, während sprite-interne Titel das nicht garantieren.

7. Fokus-Sichtbarkeit und Farbkontrast bei Icons

Icon-Only-Buttons sind meist kleiner als textbasierte Buttons und brauchen deshalb besondere Aufmerksamkeit bei zwei Kriterien: Zielgröße und Farbkontrast. WCAG 2.5.8 Target Size (Minimum) verlangt eine Klickfläche von mindestens 24 mal 24 CSS-Pixeln, in der Praxis empfiehlt sich für Touch-Bedienung deutlich mehr, üblich sind 44 mal 44 Pixel als Innenabstand um das eigentlich kleinere visuelle Icon herum. So bleibt der Button auch bei zittriger Hand oder mit Zeigegeräten statt Fingern zuverlässig treffbar, ohne dass das Icon selbst optisch größer werden muss.

Für bedeutungstragende Icons gilt zusätzlich WCAG 1.4.11 Non-text Contrast: Das Icon muss einen Kontrast von mindestens 3:1 zu seinem Hintergrund erreichen, genau wie ein Formularrahmen oder ein Fokusindikator. fill="currentColor" beziehungsweise stroke="currentColor" ist dabei die verlässlichste Technik, weil das Icon so automatisch die Textfarbe des umgebenden Kontexts übernimmt, inklusive Dark-Mode-Anpassungen, statt eine hartkodierte Farbe zu tragen, die im Theme-Wechsel plötzlich zu wenig Kontrast liefert. Der Fokusring selbst darf ebenfalls niemals nur über eine Farbänderung sichtbar werden, sondern braucht einen echten outline oder box-shadow.


/* Icon color follows the surrounding text color, including dark mode */
.icon-button svg {
  color: currentColor;
}

/* Minimum 44x44px hit area even though the visual icon itself is smaller */
.icon-button {
  min-width: 44px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* Visible focus ring, never rely on a color change alone for focus state */
.icon-button:focus-visible {
  outline: 2px solid #18181b;
  outline-offset: 2px;
}

/* Non-text contrast: meaningful icons need at least 3:1 against their background */
.icon-button svg {
  color: #3f3f46; /* passes 3:1 against a white background */
}

8. Icons testen: Screenreader, axe-core und Tastatur

Automatisierte Tools wie axe-core oder Lighthouse erkennen zuverlässig fehlende Accessible Names auf Buttons und Links, prüfen also im Wesentlichen, ob überhaupt irgendein Name berechnet werden kann. Was sie nicht bewerten können, ist, ob dieser Name inhaltlich sinnvoll ist oder sich korrekt mit dem Zustand ändert, etwa beim Wishlist-Toggle. Genau deshalb gehört zu jedem Icon-Test zusätzlich eine manuelle Prüfung: mit der Tastatur zum Button tabben, per NVDA unter Firefox oder VoiceOver unter Safari die Ansage abhören und kontrollieren, ob Name, Rolle und Zustand (etwa „gedrückt" bei aria-pressed="true") korrekt und verständlich vorgelesen werden.

Für CI-Pipelines lohnt sich, axe-core über Playwright oder Cypress automatisiert gegen zentrale Seitentypen laufen zu lassen und den Build bei Verstößen fehlschlagen zu lassen, ergänzt um gezielte Assertions für dynamische Zustände, die axe allein nicht prüfen kann. So werden Regressionen, etwa ein versehentlich entferntes aria-hidden nach einem Icon-Bibliothek-Update, schon vor dem Deployment sichtbar statt erst im Live-Betrieb durch Nutzerbeschwerden.


// Playwright + axe-core: fail the build on icon accessibility violations
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';

test('wishlist icon buttons have an accessible name', async ({ page }) => {
  await page.goto('/catalog/product/view/id/123');

  const results = await new AxeBuilder({ page })
    .include('.icon-button')
    .withTags(['wcag2a', 'wcag2aa'])
    .analyze();

  expect(results.violations).toEqual([]);

  // Manual assertion: accessible name must change together with the state
  const button = page.locator('button[aria-pressed]').first();
  await expect(button).toHaveAttribute('aria-label', 'Zur Wunschliste hinzufügen');
  await button.click();
  await expect(button).toHaveAttribute('aria-label', 'Von der Wunschliste entfernen');
});

9. Häufige Fehler bei SVG-Icons im Vergleich

Die meisten SVG-Icon-Probleme lassen sich auf eine Handvoll wiederkehrender Muster zurückführen. Die folgende Übersicht zeigt die häufigsten Fehler und die jeweils korrekte Umsetzung nebeneinander.

Fall Fehlerhaft Korrekt Wirkung
Dekoratives Icon neben Text <svg> ohne aria-hidden aria-hidden="true" focusable="false" Keine doppelte Ansage durch Screenreader
Icon-Only-Button ohne Label <button><svg></svg></button> aria-label auf dem Button, svg aria-hidden Button überhaupt bedienbar für Screenreader
Eigenständiges Status-Icon <svg> ohne title/role title + role="img" + aria-labelledby Bedeutung wird korrekt vorgelesen
SVG-Sprite-Instanz title nur im <symbol> der Sprite-Datei aria-label direkt auf svg/use pro Instanz Zuverlässig über Browser hinweg
Zustand nur über Farbe (Herz gefüllt/leer) Nur Fill-Farbe unterscheidet den Zustand aria-pressed + wechselnder aria-label-Text Zustand auch ohne Farbwahrnehmung erkennbar

Auffällig an der Tabelle: In fast jedem Fall liegt der Fehler nicht am SVG-Markup selbst, sondern an der fehlenden oder falsch platzierten Beschriftung. Ein einziges konsequent angewendetes Muster, Icon dekorativ verstecken oder Icon mit eigenem Namen versehen, deckt die überwiegende Mehrheit der Praxisfälle ab, ohne dass jede Icon-Verwendung individuell neu durchdacht werden muss.

Mironsoft

Barrierefreiheit, WCAG-Audits und Hyvä-Umsetzung für Magento-Shops

Icons, die für alle Nutzer bedienbar sind?

Wir prüfen alle Icon-Only-Elemente eures Shops auf fehlende oder falsche Accessible Names, bringen aria-hidden, title und role=img konsequent an die richtige Stelle und statten Wishlist-, Warenkorb- und Such-Icons mit zustandsabhängigen Labels aus.

Icon-Audit

Systematische Prüfung aller SVG-Icons auf Accessible Names

Hyvä-Komponenten

Wiederverwendbare Icon-Button-Komponenten mit Alpine.js-Zustand

Testautomatisierung

axe-core-Checks für Icon-Buttons in der CI-Pipeline verankern

10. Zusammenfassung

SVG-Icons barrierefrei einzusetzen beginnt immer mit derselben Entscheidung: Ist das Icon dekorativ, weil sichtbarer Text die Bedeutung bereits trägt, oder ist es bedeutungstragend, weil es allein steht? Dekorative Icons gehören konsequent mit aria-hidden="true" und focusable="false" aus dem Accessibility-Baum entfernt. Bedeutungstragende Icons brauchen einen eigenen Namen über <title> plus aria-labelledby, ein direktes aria-label, und in beiden Fällen role="img", damit die Rolle browserübergreifend konsistent ist.

Bei Icon-Only-Buttons wie dem Wishlist-Herz sitzt der zugängliche Name idealerweise auf dem Button selbst, nicht auf dem SVG, und ändert sich zusammen mit aria-pressed dynamisch mit dem Zustand. SVG-Sprites brauchen Beschriftung pro Verwendungsstelle statt im geteilten <symbol>. Fokus-Sichtbarkeit, ausreichender Farbkontrast über currentColor und eine Mindestzielgröße von 44 mal 44 Pixeln runden ein wirklich barrierefreies Icon-System ab, das sich mit axe-core, Tastatur- und Screenreader-Tests zuverlässig überprüfen lässt.

SVG-Icons barrierefrei einsetzen, das Wichtigste auf einen Blick

Dekorativ vs. bedeutungstragend

Steht bereits Text daneben, ist das Icon dekorativ. Steht es allein, braucht es einen eigenen Namen.

Dekorative Icons

aria-hidden="true" + focusable="false", keine doppelte Ansage durch den Screenreader.

Bedeutungstragende Icons

<title> + aria-labelledby oder aria-label, jeweils zusammen mit role="img".

Icon-Only-Button

Name auf dem Button, SVG mit aria-hidden, aria-label ändert sich mit aria-pressed.

11. FAQ: SVG-Icons barrierefrei einsetzen

1Was ist der Unterschied zwischen bedeutungstragenden und dekorativen SVG-Icons?
Ein dekoratives Icon steht neben Text mit derselben Bedeutung, ein bedeutungstragendes Icon steht allein und braucht deshalb einen eigenen zugänglichen Namen.
2Wann brauche ich aria-hidden bei einem SVG-Icon?
Immer bei dekorativen Icons neben sichtbarem Text. aria-hidden entfernt das Icon aus dem Accessibility-Baum und verhindert eine doppelte Ansage.
3Wie gebe ich einem eigenständigen SVG-Icon einen zugänglichen Namen?
Mit title plus aria-labelledby oder direktem aria-label auf dem svg, jeweils zusammen mit role=img für eine konsistente Rolle.
4Warum sollte das title-Element nicht die einzige Beschriftung sein?
aria-label hat Vorrang vor aria-labelledby, das wiederum Vorrang vor title hat. Unterschiedlicher Text in allen dreien erzeugt inkonsistente Ansagen.
5Wie beschrifte ich einen Icon-Only-Button wie einen Wishlist-Herz-Button richtig?
Name über aria-label auf dem Button selbst, SVG mit aria-hidden. aria-label und aria-pressed ändern sich dynamisch mit dem Wishlist-Zustand.
6Was ist der Unterschied zwischen role=img und aria-label allein?
role=img sorgt dafür, dass svg browserübergreifend konsistent als Grafik mit Namen behandelt wird, statt je nach Browser unterschiedlich interpretiert zu werden.
7Sind SVG-Sprites mit use genauso barrierefrei wie Inline-SVGs?
Nicht automatisch. title im symbol wird nicht immer zuverlässig übernommen, robuster ist die Beschriftung direkt auf svg oder use pro Verwendungsstelle.
8Worauf muss ich bei Farbkontrast und Fokus bei Icon-Buttons achten?
Mindestens 3:1 Kontrast nach WCAG 1.4.11, currentColor für Konsistenz, und ein sichtbarer Fokusring, der niemals nur über Farbe funktioniert.
9Wie teste ich SVG-Icons auf Barrierefreiheit?
Automatisiert mit axe-core oder Lighthouse, manuell mit Tastatur und Screenreader wie NVDA oder VoiceOver zur Prüfung von Name, Rolle und Zustand.
10Was ist der häufigste Fehler bei SVG-Icons in der Praxis?
Icon-Only-Buttons ohne jeden Namen, ohne aria-label auf dem Button und ohne aria-hidden auf dem SVG, sodass Screenreader-Nutzer nichts oder nur "Grafik" hören.