Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

Barrierefreiheit und Best Practices im neuen Frontend

Barrierefreiheit und Best Practices im neuen Frontend

~8 Min. Lesezeit Zuletzt aktualisiert am 9. August 2026

Block 6 schließt mit einer gezielten Durchsicht der vier Templates aus Kapitel 53 unter Barrierefreiheits-Gesichtspunkten. Kein neues Feature entsteht hier - jeder Punkt ist eine kleine, konkrete Korrektur oder Ergänzung an bereits gezeigtem Markup, mit Begründung statt bloßer Behauptung.

Semantische Tabelle statt Karten-Umbau

Kapitel 53 hat das bereits vorweggenommen: history/index.phtml nutzt eine echte <table> mit <caption class="sr-only">, <th scope="col"> pro Spaltenkopf und horizontalem Scrollen (overflow-x-auto) statt einer per CSS in Karten verwandelten Tabelle. Ein Screenreader-Nutzer, der per Tabellen-Navigation (z. B. NVDA/JAWS Tabellenmodus) durch die Historie geht, bekommt "Spalte 3 von 4, Punkte, +150" korrekt angesagt - bei einer visuell in Karten umgebauten Tabelle ohne scope-Attribute geht dieser Zusammenhang häufig verloren.

Magento\Framework\View\Element\Html\Link\Current aus Kapitel 52 setzt bereits automatisch class="current", wenn der Link zur aktuellen Seite zeigt - das allein ist aber nur eine visuelle, keine programmatisch auswertbare Markierung. Das Hyvä-Navigations-Template ergänzt dafür aria-current="page" auf Basis genau dieser Klasse:

<a href="<?= $escaper->escapeUrl($link->getHref()) ?>"
   <?= $link->isCurrent() ? 'aria-current="page"' : '' ?>
   class="<?= $link->isCurrent() ? 'font-semibold text-brand-accent' : 'text-gray-600' ?>">
    <?= $escaper->escapeHtml($link->getLabel()) ?>
</a>

So weiß nicht nur ein sehender Nutzer visuell, welcher Menüpunkt aktiv ist, sondern auch ein Screenreader kündigt "Meine Punkte, aktuelle Seite" an. Ohne aria-current bleibt die aktive Markierung ein rein optisches Signal.

Formularfelder und Fehlermeldungen

  • Das Filter-<select> aus catalog/index.phtml hat ein per for/id verknüpftes <label> statt eines reinen placeholder-artigen Texts - Pflicht, kein optionales Extra, sonst kündigt ein Screenreader das Feld ohne jede Beschriftung an.
  • Die "nicht genug Punkte"-Meldung in catalog/view.phtml trägt role="status": ein Screenreader liest sie automatisch vor, sobald sie im DOM erscheint (etwa nach einem Filter-Wechsel), ohne dass der Fokus dorthin springen muss - für eine reine Informationsmeldung angemessener als role="alert", das unterbrechender wirkt und für echte Fehler reserviert bleiben sollte.
  • Erfolgs-/Fehlermeldungen nach dem Redeem-POST (Kapitel 50, über messageManager) laufen durch Hyväs bereits vorhandene Magento_Theme::html/messages.phtml - dort ist role="alert" für Fehlermeldungen bereits korrekt gesetzt, dieses Modul muss daran nichts ändern oder duplizieren.

Fokus-Reihenfolge und der Bestätigungsdialog

Der Alpine-Dialog aus Kapitel 53 (x-trap.noscroll, role="dialog", aria-modal="true", aria-labelledby) erfüllt die vier Mindestanforderungen an ein zugängliches Modal: Fokus wird beim Öffnen in den Dialog verschoben und darin gefangen, Escape schließt ihn, der Dialog ist per aria-labelledby auf seine eigene Überschrift benannt, und aria-modal="true" teilt unterstützender Technologie mit, dass der Rest der Seite währenddessen inert ist. Ein Punkt fehlt in der Kapitel-53-Version noch bewusst als Übung: Der Fokus sollte beim Schließen (Escape oder "Abbrechen") wieder auf den ursprünglichen "Jetzt einlösen"-Button zurückspringen, statt am Seitenanfang zu landen - x-trap.noscroll übernimmt das nicht automatisch für den Rückweg, nur für das Öffnen.

<div x-data="{ confirmOpen: false }" x-id="['redeem-trigger']">
    <button type="button" :id="$id('redeem-trigger')" @click="confirmOpen = true">...</button>

    <div x-show="confirmOpen" x-trap.noscroll="confirmOpen"
         @keydown.escape.window="confirmOpen = false; $nextTick(() => document.getElementById($id('redeem-trigger')).focus())">
        ...
    </div>
</div>

Farbkontrast bei den Status-Badges

Achtung: Die Tier-Badge (bg-amber-100 text-amber-800) und die Punkte-Badge (bg-brand-accent/10 text-brand-accent-dim) aus Kapitel 53 sehen im Design-Mockup meist ausreichend kontrastreich aus - transparente Hintergründe wie /10 hängen aber vom darunterliegenden Seitenhintergrund ab. Vor dem produktiven Rollout unbedingt mit einem Kontrastprüfer (z. B. den Chrome-DevTools-Accessibility-Panel) gegen den tatsächlich gerenderten Hintergrund prüfen, ob mindestens die WCAG-2.1-AA-Mindestquote von 4,5:1 für Fließtext erreicht wird - eine reine Bewertung im Design-Tool reicht dafür nicht aus.

prefers-reduced-motion

Der Dialog nutzt bewusst nur x-show ohne x-transition-Animation - ein abruptes Ein-/Ausblenden ist für Nutzer mit vestibulären Störungen unproblematischer als eine Zoom- oder Fade-Animation. Würde eine Animation ergänzt, gehört @media (prefers-reduced-motion: reduce) zwingend dazu, entweder als Tailwind-motion-reduce:-Variante oder als eigene CSS-Regel im Theme, die transition-duration und animation-duration global auf nahezu 0 setzt.

Checkliste für Block 6

  1. Jede interaktive Fläche (Button, Link, Formularfeld) hat einen sichtbaren focus-visible-Zustand - in allen vier Templates aus Kapitel 53 bereits über focus-visible:outline-Utility-Klassen umgesetzt.
  2. Jedes Bild und jedes bedeutungstragende Icon (in diesem Block gibt es aktuell keine reinen Icon-Buttons ohne Textlabel - sollte sich das in Block 7/9 mit Widget- oder Produkttyp-Icons ändern, braucht jedes davon ein <span class="sr-only"> oder aria-label).
  3. Tabellen nutzen <th scope> statt gestylter <td>-Zellen.
  4. Modale Dialoge fangen den Fokus, schließen per Escape und bekommen ihn beim Schließen zurück.
  5. Statusmeldungen nutzen role="status" für Information, role="alert" nur für echte Fehler.
  6. Farbkontraste werden am gerenderten Ergebnis geprüft, nicht am Mockup.

Tipp: Damit ist Block 6 abgeschlossen: von der ersten sichtbaren Seite (Kapitel 45) über den eigenen Router (Kapitel 46), die Block/ViewModel-Entscheidung (Kapitel 47/48), den Prämienkatalog samt Einlösen (Kapitel 49/50) bis zur vollständigen, zugänglichen Frontend-Oberfläche (Kapitel 51-54). Block 7 baut darauf auf: ein "Meine Punkte"-Widget für CMS-Seiten und ein eigener Page-Builder-Content-Type - beide greifen auf die in diesem Block gebauten ViewModels zurück, statt sie zu duplizieren.