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.
Inhaltsverzeichnis
- 1. Warum SVG-Icons ein eigenes Accessibility-Problem sind
- 2. Bedeutungstragende und dekorative Icons unterscheiden
- 3. Dekorative Icons korrekt verstecken: aria-hidden und focusable
- 4. Bedeutungstragende Icons benennen: title, aria-label, role=img
- 5. Praxisbeispiel: Icon-Only-Button mit korrektem Accessible Name
- 6. Inline SVG vs. SVG-Sprite: Unterschiede bei der Barrierefreiheit
- 7. Fokus-Sichtbarkeit und Farbkontrast bei Icons
- 8. Icons testen: Screenreader, axe-core und Tastatur
- 9. Häufige Fehler bei SVG-Icons im Vergleich
- 10. Zusammenfassung
- 11. FAQ
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.