Tastatur, Live-Regionen und Fokus-Management für Shop-Filter
Produktfilter, die nur mit der Maus bedienbar sind oder nach jeder Auswahl den Fokus verlieren, schließen Nutzer mit Tastatur oder Screenreader faktisch aus. Dieser Artikel zeigt, wie Checkboxen, Radio-Buttons und Preis-Regler in einer Layered Navigation semantisch korrekt ausgezeichnet, per Tastatur bedienbar gemacht und mit Live-Regionen für aktualisierte Ergebniszahlen kombiniert werden, ohne den Fokus beim Neuladen des Produktrasters zu verlieren.
Inhaltsverzeichnis
- 1. Warum barrierefreie Produktfilter über Conversion und Rechtskonformität entscheiden
- 2. Semantische Struktur: Fieldset, Legend und Label für Filtergruppen
- 3. Tastaturbedienung: Checkboxen, Radio-Buttons und Fokus-Sichtbarkeit
- 4. Live-Regionen: Aktualisierte Ergebniszahl nach dem Filtern ankündigen
- 5. Fokus-Management: Keinen Fokus verlieren, wenn das Produktraster neu rendert
- 6. ARIA-Patterns für Multiselect-Dropdowns und Filter-Chips
- 7. Preis-Regler (Range-Slider) barrierefrei umsetzen
- 8. Praktisches Pattern: Layered Navigation mit Alpine.js in Hyvä
- 9. Testing und Vergleich: Screenreader, axe-core und typische Fehler
- 10. Zusammenfassung
- 11. FAQ
1. Warum barrierefreie Produktfilter über Conversion und Rechtskonformität entscheiden
Produktfilter sind in nahezu jedem Onlineshop die zentrale Steuerung zwischen hunderten Produkten und den wenigen passenden Artikeln. Wenn diese Steuerung nur mit der Maus bedienbar ist, schließt sie Tastaturnutzer, Screenreader-Nutzer und motorisch eingeschränkte Menschen faktisch von der Produktsuche aus. Das ist nicht nur ein rechtliches Risiko unter dem Barrierefreiheitsstärkungsgesetz (BFSG), das seit Juni 2025 auch für B2C-Onlineshops gilt, sondern auch ein handfestes Conversion-Problem: Wer einen Filter nicht bedienen kann, verlässt die Seite, statt zu kaufen.
In der Praxis scheitern Produktfilter an denselben immer wiederkehrenden Stellen: Checkboxen ohne sichtbares Label, Custom-Dropdowns ohne Tastatursteuerung, Ergebniszahlen, die sich unbemerkt ändern, und ein Produktraster, das nach jedem Klick den Fokus zurück auf das Seiten-Body-Element wirft. Jeder dieser Fehler lässt sich mit etablierten WAI-ARIA-Patterns und wenigen Zeilen semantischem HTML beheben, wie die folgenden Abschnitte am Beispiel einer Layered Navigation für Magento und Hyvä zeigen.
2. Semantische Struktur: Fieldset, Legend und Label für Filtergruppen
Jede Filtergruppe, etwa Farbe oder Größe, gehört in ein natives <fieldset>-Element mit einem <legend> als Gruppentitel. Screenreader kündigen die Legende automatisch an, sobald der Fokus die erste Checkbox der Gruppe erreicht, sodass Nutzer wissen, in welchem Kontext sich die Option befindet, ohne jedes einzelne Label erneut vorlesen zu müssen. Ein reines <div> mit einer optisch fett gedruckten Überschrift bietet diese Zuordnung nicht, weil die Gruppierung nur visuell, nicht aber programmatisch existiert.
Jede einzelne Checkbox braucht ein <label>, das über das for-Attribut mit der id der Checkbox verknüpft ist, niemals nur ein umgebendes <span> mit CSS-Styling. Der sichtbare Text sollte die Trefferzahl enthalten, etwa Rot (12), damit Screenreader-Nutzer vor der Auswahl wissen, wie viele Produkte zu erwarten sind. aria-describedby kann zusätzliche Hinweise wie Mehrfachauswahl möglich ergänzen, ohne den primären Label-Text zu verlängern.
<!-- Filter group: fieldset + legend, each checkbox with a bound label -->
<fieldset class="border-0 p-0 m-0 mb-6">
<legend class="font-bold text-slate-800 text-sm mb-3">Farbe</legend>
<ul class="space-y-2 list-none m-0 p-0">
<li>
<input
type="checkbox"
id="filter-color-red"
name="color[]"
value="red"
class="w-4 h-4"
aria-describedby="filter-color-red-hint"
>
<label for="filter-color-red" class="ml-2 text-sm text-slate-700">Rot (12)</label>
<span id="filter-color-red-hint" class="sr-only">Mehrfachauswahl möglich</span>
</li>
<li>
<input type="checkbox" id="filter-color-blue" name="color[]" value="blue" class="w-4 h-4">
<label for="filter-color-blue" class="ml-2 text-sm text-slate-700">Blau (7)</label>
</li>
</ul>
</fieldset>
3. Tastaturbedienung: Checkboxen, Radio-Buttons und Fokus-Sichtbarkeit
Checkboxen und Radio-Buttons sind von Haus aus mit der Tab-Taste erreichbar und mit der Leertaste beziehungsweise den Pfeiltasten bedienbar, sofern sie native HTML-Elemente bleiben und nicht durch <div>- oder <span>-Konstrukte mit Klick-Handlern ersetzt werden. Das Problem entsteht meist erst, wenn Designer die native Checkbox optisch komplett verstecken (display: none) und durch ein rein visuelles Ersatzelement ersetzen, das keinerlei Tastatur-Interaktion mehr besitzt.
Der sichere Weg ist, die native Checkbox mit einer sr-only-ähnlichen Klasse unsichtbar, aber fokussierbar zu halten und das visuelle Ersatzelement über die :checked- und :focus-visible-Selektoren zu steuern. So bleibt die komplette Tastatur- und Screenreader-Logik der nativen Checkbox erhalten, während das Design frei gestaltbar bleibt. Ein deutlich sichtbarer Fokusring mit ausreichendem Kontrast, mindestens 3:1 laut WCAG 2.2 Kriterium 2.4.11, ist Pflicht, gerade weil viele Shop-Themes den Standard-Browser-Fokusring per CSS entfernen, ohne ihn zu ersetzen.
/* Real checkbox stays in the DOM and keyboard-focusable, only visually hidden */
.filter-checkbox {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
/* Custom visual box driven by the real checkbox state */
.filter-checkbox + .filter-checkbox-box {
width: 1.25rem;
height: 1.25rem;
border: 2px solid #71717a;
border-radius: 0.25rem;
}
.filter-checkbox:checked + .filter-checkbox-box {
background-color: #18181b;
border-color: #18181b;
}
/* Visible, high-contrast focus ring, only on keyboard focus */
.filter-checkbox:focus-visible + .filter-checkbox-box {
outline: 3px solid #3f3f46;
outline-offset: 2px;
}
4. Live-Regionen: Aktualisierte Ergebniszahl nach dem Filtern ankündigen
Wenn ein Filter aktiviert wird und sich das Produktraster per AJAX aktualisiert, ändert sich die Ergebniszahl visuell sofort, aber ohne aria-live bleibt diese Änderung für Screenreader-Nutzer unsichtbar. Die Lösung ist eine dedizierte Live-Region, ein Element mit aria-live="polite" und role="status", das ausschließlich die aktuelle Trefferzahl enthält und bei jeder Filteränderung per JavaScript aktualisiert wird. Polite sorgt dafür, dass die Ansage wartet, bis der Screenreader die aktuelle Sprachausgabe beendet hat, statt sie zu unterbrechen.
Wichtig ist, die Live-Region beim initialen Seitenaufbau leer im DOM zu belassen und den Text erst nach der ersten Nutzerinteraktion zu setzen, sonst kündigt der Screenreader die Trefferzahl bereits beim Laden der Seite an, obwohl noch kein Filter aktiv war. Die Region sollte visuell nicht komplett unsichtbar sein, sondern die reguläre Trefferzahl-Anzeige selbst als Live-Region markiert werden, damit sehende und blinde Nutzer dieselbe Information zur gleichen Zeit erhalten.
<!-- Visible result count doubles as the live region, no separate hidden element -->
<p
id="filter-result-count"
role="status"
aria-live="polite"
aria-atomic="true"
class="text-sm font-semibold text-slate-700 mb-4"
>
128 Produkte
</p>
<script>
function updateResultCount(newCount) {
const region = document.getElementById('filter-result-count');
// aria-atomic ensures the full sentence is re-announced, not just the diff
region.textContent = newCount + ' Produkte gefunden';
}
</script>
5. Fokus-Management: Keinen Fokus verlieren, wenn das Produktraster neu rendert
Der wohl größte reale UX-Fehler bei AJAX-Filtern: Nach jedem Klick wird der gesamte Produktraster-Container per innerHTML ersetzt, wodurch der zuvor fokussierte Checkbox-Knoten aus dem DOM entfernt wird. Der Browser wirft den Fokus dann automatisch auf das <body>-Element zurück, und Tastaturnutzer müssen sich mit der Tab-Taste komplett von vorn durch die Seite navigieren, um zum nächsten Filter zu gelangen. Für Screenreader-Nutzer fühlt sich das an, als würde die gesamte Seite neu geladen.
Das korrekte Pattern hält den Filter-Container, der den Fokus besitzt, unangetastet und ersetzt ausschließlich den Produktraster-Bereich. Zusätzlich merkt sich das Skript vor dem Re-Render, welches Element fokussiert war, und stellt den Fokus danach explizit per element.focus() wieder her, falls das ursprüngliche Element doch neu gerendert werden musste. Für den Fall, dass der fokussierte Knoten tatsächlich ersetzt wird, ist ein tabindex="-1" auf einem stabilen Ankerelement wie der Ergebnis-Überschrift ein bewährtes Fallback, um den Fokus programmatisch dorthin zu verschieben.
// Preserve keyboard focus across an AJAX re-render of the product grid
async function applyFilter(url) {
const activeElement = document.activeElement;
const activeId = activeElement && activeElement.id;
const response = await fetch(url, { headers: { 'X-Requested-With': 'XMLHttpRequest' } });
const html = await response.text();
// Only the grid is replaced, the filter fieldset stays untouched in the DOM
document.getElementById('product-grid').innerHTML = html;
updateResultCount(document.querySelectorAll('.product-card').length);
// Restore focus if the original node still exists after the swap
const restored = activeId && document.getElementById(activeId);
if (restored) {
restored.focus();
return;
}
// Fallback: move focus to a stable, always-present anchor heading
const anchor = document.getElementById('filter-result-heading');
anchor.setAttribute('tabindex', '-1');
anchor.focus();
}
6. ARIA-Patterns für Multiselect-Dropdowns und Filter-Chips
Custom-Dropdowns für Filter, etwa ein Mehrfachauswahl-Menü für Marken, benötigen das WAI-ARIA Listbox- oder Combobox-Pattern, nicht ein selbst gebautes div-Konstrukt ohne Rollen. Ein Button mit aria-haspopup="listbox" und aria-expanded öffnet die Liste, die als ul mit role="listbox" und jedes Element als li mit role="option" sowie aria-selected ausgezeichnet wird. Pfeiltasten navigieren zwischen den Optionen, die Leertaste oder Eingabetaste wählt aus, Escape schließt das Menü und gibt den Fokus an den auslösenden Button zurück.
Aktive Filter, die als entfernbare Chips oberhalb des Produktrasters angezeigt werden, brauchen jeweils einen eigenen Button mit einem eindeutigen aria-label wie Filter Farbe Rot entfernen, statt eines generischen Kreuz-Icons ohne Text-Alternative. Nach dem Entfernen eines Chips sollte der Fokus nicht ins Leere fallen, sondern auf den nächsten verbleibenden Chip oder, falls keiner mehr vorhanden ist, zurück auf die entsprechende Filter-Checkbox in der Fieldset-Gruppe wandern.
7. Preis-Regler (Range-Slider) barrierefrei umsetzen
Ein Preisfilter mit zwei Reglern für Minimum und Maximum lässt sich entweder mit zwei nativen input[type=range]-Elementen oder mit dem WAI-ARIA Slider-Pattern (role="slider") umsetzen. Die native Variante ist deutlich robuster, weil Tastatursteuerung mit Pfeiltasten, Bild-auf, Bild-ab, Pos1 und Ende automatisch funktioniert, ohne dass eigener JavaScript-Code für jede Taste geschrieben werden muss. Zwei überlappende Range-Inputs mit unterschiedlichem z-index sind das gängige Pattern für einen Doppel-Slider, wobei jeder Regler einen eigenen aria-label wie Mindestpreis beziehungsweise Höchstpreis trägt.
Wird stattdessen eine komplett individuell gestylte Slider-Komponente gebaut, müssen aria-valuemin, aria-valuemax und aria-valuenow bei jeder Bewegung aktuell gehalten werden, zusätzlich zu einem aria-valuetext, das den Wert lesbar formatiert, etwa 49 Euro statt nur der rohen Zahl 49. Ohne aria-valuetext liest mancher Screenreader nur die reine Ziffer vor, was bei Währungs- oder Prozentwerten missverständlich ist. Der Fokusring des Reglers muss bei jeder Tastatureingabe sichtbar bleiben, auch während sich der Wert in Echtzeit ändert.
8. Praktisches Pattern: Layered Navigation mit Alpine.js in Hyvä
In Hyvä lässt sich eine barrierefreie Layered Navigation vollständig mit einer Alpine.js-Komponente umsetzen, ohne zusätzliche JavaScript-Bibliotheken zu laden. Die Komponente hält den Ladezustand, die aktuelle Trefferzahl und eine Referenz auf das zuletzt fokussierte Element in ihrem lokalen State und aktualisiert nach jedem AJAX-Request sowohl den Produktraster als auch die Live-Region mit der neuen Trefferzahl, ohne den restlichen Seitenbereich neu zu rendern.
Der entscheidende Kniff im folgenden Beispiel: Der x-init-Hook merkt sich das aktive Element vor dem Fetch-Aufruf, und nach dem Einfügen der neuen Produktkarten prüft die Komponente, ob dieses Element noch im DOM existiert. Ist das der Fall, bleibt der Fokus unverändert. Ist es verschwunden, wandert der Fokus explizit auf die Trefferzahl-Überschrift, die dafür ein tabindex="-1" trägt und so programmatisch fokussierbar, aber nicht Teil der normalen Tab-Reihenfolge ist.
// Hyva_Theme::product/layered-nav.phtml - Alpine.js component
document.addEventListener('alpine:init', () => {
Alpine.data('layeredNav', () => ({
loading: false,
resultCount: 0,
async toggleFilter(event, url) {
this.loading = true;
const lastFocusedId = document.activeElement.id;
try {
const response = await fetch(url, {
headers: { 'X-Requested-With': 'XMLHttpRequest' }
});
const data = await response.json();
// Replace only the grid, the fieldset filters stay mounted
document.getElementById('product-grid').innerHTML = data.gridHtml;
this.resultCount = data.count;
// Announce the new count through the existing live region
this.$refs.liveRegion.textContent = data.count + ' Produkte gefunden';
// Restore focus to the same control if it still exists
this.$nextTick(() => {
const restored = document.getElementById(lastFocusedId);
if (restored) {
restored.focus();
} else {
this.$refs.resultHeading.focus();
}
});
} finally {
this.loading = false;
}
}
}));
});
9. Testing und Vergleich: Screenreader, axe-core und typische Fehler
Automatisierte Tools wie axe-core oder Lighthouse-Accessibility-Audits finden zuverlässig fehlende Labels, unzureichende Kontraste und fehlende ARIA-Attribute, aber sie erkennen keinen Fokusverlust nach einem AJAX-Update und keine falsch formulierten Live-Region-Ansagen. Deshalb braucht jede Layered Navigation zusätzlich einen manuellen Test mit einem echten Screenreader: NVDA mit Firefox unter Windows oder VoiceOver mit Safari unter macOS sind die gängigsten Kombinationen für den deutschen Markt.
Beim manuellen Test wird ausschließlich mit der Tastatur gefiltert, ganz ohne Maus, und geprüft, ob jede Ansage der Live-Region inhaltlich korrekt und rechtzeitig erfolgt, ob der Fokus nach jeder Interaktion an einer sinnvollen Stelle bleibt, und ob sich Custom-Dropdowns mit Pfeiltasten, Escape und Eingabetaste wie erwartet verhalten. Die folgende Tabelle vergleicht typische Implementierungsfehler mit dem barrierefreien Gegenstück.
| Bereich | Nicht barrierefrei | Barrierefreies Pattern | Vorteil |
|---|---|---|---|
| Filtergruppe | div mit fett gedruckter Überschrift | fieldset + legend | Gruppenkontext wird programmatisch angesagt |
| Checkbox-Ersatz | div mit onclick, display: none Checkbox | Native Checkbox, sr-only + :checked | Tastatur- und Screenreader-Logik bleibt erhalten |
| Ergebniszahl | Nur visuelle Textänderung | role="status" aria-live="polite" | Screenreader kündigt neue Trefferzahl an |
| Grid-Update | innerHTML des ganzen Containers | Fokus merken, gezielt wiederherstellen | Kein Zurückfallen des Fokus auf body |
| Preis-Regler | Eigenbau ohne aria-valuenow | input[type=range] oder role="slider" + aria-valuetext | Wert wird korrekt und verständlich vorgelesen |
Mironsoft
Barrierefreiheits-Audits und WCAG-konforme Umsetzung für Magento- und Hyvä-Shops
Produktfilter, die wirklich für alle bedienbar sind?
Wir prüfen eure Layered Navigation auf Tastaturbedienbarkeit, Live-Regionen und Fokus-Management und setzen die notwendigen Anpassungen direkt im Hyvä-Theme um, damit euer Shop das BFSG erfüllt und für alle Kunden nutzbar bleibt.
Barrierefreiheits-Audit
Tastatur-, Screenreader- und axe-core-Prüfung eurer Filter und Formulare
Hyvä-Umsetzung
ARIA-Patterns, Live-Regionen und Fokus-Management direkt im Theme implementieren
BFSG-Beratung
Rechtliche Anforderungen einordnen und priorisierten Umsetzungsplan erstellen
10. Zusammenfassung
Barrierefreie Produktfilter lösen immer dasselbe Grundproblem: Nutzer, die nicht mit der Maus arbeiten können oder wollen, dürfen nicht von der Produktsuche ausgeschlossen werden. Semantisches HTML mit fieldset und legend, native statt nachgebauter Checkboxen mit sichtbarem Fokusring, eine aria-live-Region für die Trefferzahl und ein Fokus-Management, das den zuletzt fokussierten Knoten über AJAX-Updates hinweg erhält, bilden zusammen ein vollständiges, barrierefreies Pattern für die Layered Navigation.
Der größte Hebel liegt darin, diese Patterns konsequent für jede Filterkomponente anzuwenden, nicht nur für die einfachen Checkbox-Listen. Custom-Dropdowns, Filter-Chips und Preis-Regler brauchen jeweils eigene ARIA-Rollen und eine eigene Tastatursteuerung. Automatisierte Tools wie axe-core finden einen Großteil der offensichtlichen Fehler, aber erst der manuelle Test mit einem echten Screenreader zeigt zuverlässig, ob Live-Regionen und Fokus-Management im Zusammenspiel tatsächlich funktionieren.
Barrierefreie Produktfilter, das Wichtigste auf einen Blick
Semantische Struktur
fieldset + legend pro Filtergruppe, jede Checkbox mit gebundenem label statt reinem CSS-Styling.
Live-Region
role="status" mit aria-live="polite" für die Trefferzahl, aktualisiert bei jeder Filteränderung.
Fokus-Management
Aktives Element vor dem Re-Render merken, danach wiederherstellen oder gezielt auf eine stabile Überschrift verschieben.
Testing
axe-core für automatisierte Basisprüfung, NVDA/VoiceOver für manuellen Test von Live-Regionen und Fokus.