BFSG 18 Min. Lesezeit

BFSG technisch umsetzen: Semantisches HTML, ARIA und Tastatur-Navigation in Astro

Schritt-für-Schritt-Anleitung zur technischen BFSG-Umsetzung in Astro-Projekten: Semantische HTML-Struktur, ARIA-Rollen, Fokus-Management und reduced-motion.

Das Barrierefreiheitsstärkungsgesetz (BFSG) verpflichtet private Unternehmen ab dem 28. Juni 2025 zur technischen Barrierefreiheit ihrer digitalen Produkte. Die rechtliche Grundlage ist die europäische Accessibility-Richtlinie EN 301 549 — operationalisiert über WCAG 2.1 Level AA. Dieser Artikel zeigt, wie man diese Anforderungen in Astro-Projekten technisch umsetzt, nicht nur formal erfüllt.

Semantisches HTML: die Basis, die man nicht überspringen kann

Barrierefreiheit beginnt nicht mit ARIA — sie beginnt mit semantischem HTML. Screenreader, Braille-Ausgaben und assistive Technologien nutzen die native Semantik des HTMLs, bevor sie ARIA-Attribute verarbeiten.

Seitenstruktur mit Landmark-Elementen

---
// layouts/Layout.astro
---
<html lang="de">
  <head><!-- ... --></head>
  <body>
    <a href="#main-content" class="sr-only focus:not-sr-only focus:fixed focus:top-4 focus:left-4 focus:z-50 focus:px-4 focus:py-2 focus:bg-primary-600 focus:text-white focus:rounded-lg">
      Zum Hauptinhalt springen
    </a>
    <header role="banner">
      <nav aria-label="Hauptnavigation"><!-- ... --></nav>
    </header>
    <main id="main-content" tabindex="-1">
      <slot />
    </main>
    <footer role="contentinfo"><!-- ... --></footer>
  </body>
</html>

Der Skip-Link ist Pflicht nach WCAG 2.4.1. Das tabindex="-1" auf <main> ermöglicht programmatisches Fokussieren nach der Navigation — ohne es im Tab-Reihenfolge zu erscheinen.

Überschriften-Hierarchie

Jede Seite darf genau eine <h1> haben. Die Hierarchie muss logisch sein — <h3> darf nicht direkt auf <h1> folgen, wenn <h2> fehlt.

---
// Eine typisch fehlerhafte Pattern:
// <h1>Seitentitel</h1>
// <h3>Abschnitt</h3>  ← FEHLER: h2 fehlt

// Korrekt:
// <h1>Seitentitel</h1>
// <h2>Hauptabschnitt</h2>
// <h3>Unterabschnitt</h3>
---

ARIA: wann einsetzen, wann weglassen

ARIA (Accessible Rich Internet Applications) ergänzt semantisches HTML — es ersetzt es nicht. Die erste Regel der ARIA-Nutzung lautet: “Wenn Sie ein natives HTML-Element oder -Attribut mit der benötigten Semantik und dem gewünschten Verhalten verwenden können, tun Sie das, anstatt ein Element umzunutzen und eine ARIA-Rolle hinzuzufügen.”

ARIA korrekt einsetzen

---
interface Props {
  isOpen: boolean;
  onClose: () => void;
}
---

<!-- Dialog/Modal — natives <dialog> bevorzugen -->
<dialog
  aria-labelledby="dialog-title"
  aria-describedby="dialog-desc"
  aria-modal="true"
>
  <h2 id="dialog-title">Bestätigung erforderlich</h2>
  <p id="dialog-desc">Möchten Sie diese Aktion wirklich ausführen?</p>
  <button type="button" autofocus>Bestätigen</button>
  <button type="button">Abbrechen</button>
</dialog>
<!-- Tabs — aria-selected + role="tabpanel" -->
<div role="tablist" aria-label="Produktkategorien">
  <button
    role="tab"
    aria-selected="true"
    aria-controls="panel-seo"
    id="tab-seo"
  >SEO</button>
  <button
    role="tab"
    aria-selected="false"
    aria-controls="panel-webdesign"
    id="tab-webdesign"
    tabindex="-1"
  >Webdesign</button>
</div>

<div role="tabpanel" id="panel-seo" aria-labelledby="tab-seo">
  <!-- Inhalt SEO -->
</div>

ARIA-Live-Regionen für dynamische Inhalte

Wenn Inhalte sich dynamisch ändern — Suchergebnisse, Formularbeststätigungen, Statusmeldungen — muss das Screenreader mitbekommen:

<!-- Statusmeldungen -->
<div
  role="status"
  aria-live="polite"
  aria-atomic="true"
  class="sr-only"
  id="form-status"
>
  <!-- Wird via JS befüllt: "Formular erfolgreich abgesendet" -->
</div>

<!-- Fehlermeldungen mit höherer Priorität -->
<div
  role="alert"
  aria-live="assertive"
  class="sr-only"
  id="form-error"
>
</div>

Tastatur-Navigation: vollständige Unterstützung

Alle interaktiven Elemente müssen per Tastatur erreichbar und bedienbar sein. Das betrifft nicht nur offensichtliche Elemente wie Buttons und Links, sondern auch Custom-Komponenten.

Fokus-Management in Astro-Inseln

// components/molecules/Dropdown.jsx
import { useState, useRef, useEffect } from 'react';

export function Dropdown({ label, items }) {
  const [open, setOpen] = useState(false);
  const triggerRef = useRef(null);
  const menuRef = useRef(null);

  useEffect(() => {
    if (open && menuRef.current) {
      // Fokus auf erstes Menü-Item beim Öffnen
      const firstItem = menuRef.current.querySelector('[role="menuitem"]');
      firstItem?.focus();
    }
  }, [open]);

  function handleKeyDown(e) {
    if (e.key === 'Escape') {
      setOpen(false);
      // Fokus zurück auf Trigger
      triggerRef.current?.focus();
    }
    if (e.key === 'ArrowDown') {
      e.preventDefault();
      const items = menuRef.current?.querySelectorAll('[role="menuitem"]');
      const current = document.activeElement;
      const idx = Array.from(items ?? []).indexOf(current);
      items?.[idx + 1]?.focus();
    }
    if (e.key === 'ArrowUp') {
      e.preventDefault();
      const items = menuRef.current?.querySelectorAll('[role="menuitem"]');
      const current = document.activeElement;
      const idx = Array.from(items ?? []).indexOf(current);
      items?.[idx - 1]?.focus();
    }
  }

  return (
    <div onKeyDown={handleKeyDown}>
      <button
        ref={triggerRef}
        aria-haspopup="menu"
        aria-expanded={open}
        onClick={() => setOpen(!open)}
      >
        {label}
      </button>
      {open && (
        <ul role="menu" ref={menuRef}>
          {items.map((item) => (
            <li key={item.href} role="none">
              <a role="menuitem" href={item.href}>{item.label}</a>
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

Fokus-Sichtbarkeit

WCAG 2.4.11 (Level AA ab WCAG 2.2) fordert sichtbare Fokus-Indikatoren. Die outline: none-Praxis aus den 2010er-Jahren ist nach BFSG nicht akzeptabel:

/* In global.css — niemals :focus-visible unterdrücken */
:focus-visible {
  outline: 2px solid var(--color-primary-500);
  outline-offset: 2px;
  border-radius: 4px;
}

/* Eigene Fokus-Stile für spezifische Elemente */
.btn:focus-visible {
  outline: 2px solid var(--color-primary-500);
  outline-offset: 3px;
  box-shadow: 0 0 0 4px var(--color-primary-100);
}

Farbkontrast: Mindestanforderungen

WCAG 1.4.3 fordert ein Kontrastverhältnis von mindestens 4,5:1 für normalen Text und 3:1 für großen Text (18pt / 14pt bold). Für nicht-textliche Elemente (Buttons, Icons, Formularrahmen) gilt 3:1.

/* Primärfarbe prüfen — bei Wender Media Blau auf weiß: */
/* #0072BB auf #FFFFFF → Kontrastverhältnis: 5.74:1 ✓ */
/* #0072BB auf #f3f4f6 → Kontrastverhältnis: 5.33:1 ✓ */

/* Problematisch: hellgraues Placeholder-Text */
/* color: #9ca3af auf #FFFFFF → 2.32:1 ✗ */
/* Lösung: mindestens #6b7280 verwenden → 4.48:1 ✓ */

Für interaktive Elemente im Hover-Zustand muss der Kontrast ebenfalls eingehalten werden — nicht nur im Ausgangszustand.

prefers-reduced-motion: Respekt vor vestibulären Störungen

Nutzer, die in ihrem Betriebssystem “Bewegung reduzieren” aktiviert haben, leiden oft unter vestibulären Störungen, bei denen Animationen Schwindel oder Übelkeit auslösen können. CSS und JavaScript müssen diesen Wunsch respektieren.

/* Globale Regel in global.css */
@media (prefers-reduced-motion: reduce) {
  *,
  ::before,
  ::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

In React-/Preact-Inseln:

import { useEffect, useState } from 'react';

function useReducedMotion() {
  const [reduced, setReduced] = useState(false);

  useEffect(() => {
    const mq = window.matchMedia('(prefers-reduced-motion: reduce)');
    setReduced(mq.matches);
    const handler = (e) => setReduced(e.matches);
    mq.addEventListener('change', handler);
    return () => mq.removeEventListener('change', handler);
  }, []);

  return reduced;
}

export function AnimatedHero() {
  const reduced = useReducedMotion();

  return (
    <div
      style={{
        transition: reduced ? 'none' : 'transform 0.4s ease',
      }}
    >
      {/* ... */}
    </div>
  );
}

Formulare: vollständige BFSG-Anforderungen

Formulare sind der häufigste Fehlerbereich bei BFSG-Audits. Die wichtigsten Anforderungen:

<form novalidate>
  <!-- Pflichtfeld: label ist mit input assoziiert -->
  <div class="field-group">
    <label for="email" class="block text-sm font-medium text-gray-700">
      E-Mail-Adresse
      <span aria-hidden="true" class="text-red-500 ml-1">*</span>
    </label>
    <input
      type="email"
      id="email"
      name="email"
      required
      aria-required="true"
      aria-describedby="email-hint email-error"
      autocomplete="email"
      class="mt-1 block w-full rounded-lg border border-gray-300 px-3 py-2"
    />
    <p id="email-hint" class="mt-1 text-sm text-gray-500">
      Format: name@beispiel.de
    </p>
    <!-- Fehlermeldung — nur sichtbar wenn ungültig -->
    <p id="email-error" role="alert" class="mt-1 text-sm text-red-600 hidden">
      Bitte geben Sie eine gültige E-Mail-Adresse ein.
    </p>
  </div>

  <!-- Submit-Button: immer type="submit" -->
  <button type="submit" class="btn-primary">
    Anfrage senden
  </button>
</form>

Bilder: alt-Text-Strategie

<!-- Informatives Bild: alt beschreibt Inhalt und Funktion -->
<img
  src="/images/bfsg-checkliste.webp"
  alt="BFSG-Checkliste mit 12 Prüfpunkten für barrierefreie Websites"
  width="800"
  height="450"
/>

<!-- Dekoratives Bild: alt="" verhindert, dass Screenreader die URL vorliest -->
<img src="/images/decoration-wave.svg" alt="" aria-hidden="true" />

<!-- Icon mit Bedeutung: aria-label am übergeordneten Element -->
<button type="button" aria-label="Suche öffnen">
  <svg aria-hidden="true" focusable="false"><!-- ... --></svg>
</button>

Testmethoden

Automatisierte Tools erkennen ca. 30–40% aller WCAG-Verstöße. Der Rest erfordert manuelle Prüfung:

  • axe DevTools (Browser-Extension): automatische Analyse
  • Lighthouse Accessibility Audit: schnelle Ersteinschätzung
  • NVDA + Firefox (Windows) oder VoiceOver + Safari (macOS): manuelle Screenreader-Tests
  • Tastatur-Navigation: Tab, Shift+Tab, Enter, Space, Pfeiltasten — ohne Maus navigieren

Weiterführende Artikel


BFSG-Audit für Ihr Projekt? Wender Media führt technische Accessibility-Audits durch und behebt Barrieren systematisch — info@wendermedia.info.

Individuelle Beratung gewünscht?

Wender Media unterstützt Sie bei der praktischen Umsetzung — von der technischen Konzeption bis zum Launch. Schreiben Sie uns, wir antworten innerhalb von 24 Stunden.

Jetzt Beratung anfragen

Kostenlos & unverbindlich — info@wendermedia.info

Verwandte Artikel