Web-Barrierefreiheit in der Praxis: ARIA-Rollen und WCAG-2.2-Konformitätsleitfaden

前端工程

Die Vier Prinzipien der WCAG 2.2

Die WCAG 2.2 organisiert Konformitätsanforderungen um die POUR-Prinzipien:

Prinzip Bedeutung Kernanforderung Beispiel
Wahrnehmbar Informationen sind darstellbar Inhalt für alle Nutzer wahrnehmbar Bild-Alt, Untertitel, Kontrast
Bedienbar Schnittstelle ist bedienbar Über mehrere Eingabemethoden nutzbar Tastaturnavigation, ausreichend Zeit, kein Blinken
Verständlich Inhalt ist verständlich Inhalt und Bedienung sind klar Konsistente Navigation, Fehlerhinweise, Eingabehilfe
Robust Inhalt ist kompatibel Von Hilfstechnologien analysierbar Gültiges HTML, ARIA-Semantik

ARIA-Rollensystem

Dokumentstruktur-Rollen

<header role="banner">
  <nav role="navigation" aria-label="Main navigation">
    <ul role="menubar">
      <li role="menuitem"><a href="/home">Home</a></li>
      <li role="menuitem" aria-haspopup="true">
        Products
        <ul role="menu">
          <li role="menuitem"><a href="/saas">SaaS</a></li>
          <li role="menuitem"><a href="/api">API</a></li>
        </ul>
      </li>
    </ul>
  </nav>
</header>
<main role="main">
  <article role="article">
    <h2>Article Title</h2>
    <p>Content body</p>
  </article>
</main>
<footer role="contentinfo">Copyright info</footer>

Widget-Rollen

Rolle Zweck Erforderliche Eigenschaften Wichtige Interaktion
button Klickbarer Auslöser Keine Enter/Leertaste aktiviert
checkbox Aktiviert/deaktiviert aria-checked Leertaste schaltet um
radio Einzelauswahl aria-checked Pfeiltasten wechseln
slider Bereichswert aria-valuenow, aria-valuemin, aria-valuemax Pfeiltasten anpassen
tablist / tab Tab-Panels aria-selected Pfeiltasten wechseln
dialog Modal/nicht-modal aria-modal Esc schließt
combobox Auswahleingabe aria-expanded Pfeil auswählen/Tippfilter
tree / treeitem Baumstruktur aria-expanded Pfeil erweitern/einklappen

ARIA-Eigenschaften und -Zustände

Kurzreferenz der Kerneigenschaften

<!-- Labels and descriptions -->
<button aria-label="Close dialog">✕</button>
<input aria-labelledby="name-label" aria-describedby="name-hint">
<span id="name-label">Username</span>
<span id="name-hint">6-20 characters</span>

<!-- States -->
<div aria-expanded="true" aria-selected="false" aria-checked="mixed">
<div aria-hidden="true" aria-disabled="true" aria-busy="true">

<!-- Relationships -->
<div aria-owns="child-id" aria-controls="panel-id" aria-flowto="next-section">

<!-- Live regions -->
<div role="alert" aria-live="assertive">Error message</div>
<div aria-live="polite" aria-atomic="true" aria-relevant="additions text">Status update</div>

Erste Regel: ARIA Nicht Überbeanspruchen

Wenn ein natives HTML-Element bereits die benötigte Semantik und Interaktion bereitstellt, nicht mit ARIA neu implementieren.

<!-- ❌ Anti-pattern -->
<div role="button" tabindex="0" onclick="submit()">Submit</div>

<!-- ✅ Correct -->
<button type="submit">Submit</button>

Praxis: Barrierefreies Modal

<div class="modal-overlay" id="modalOverlay" hidden>
  <div
    role="dialog"
    aria-modal="true"
    aria-labelledby="modalTitle"
    aria-describedby="modalDesc"
    class="modal"
  >
    <h2 id="modalTitle">Confirm Deletion</h2>
    <p id="modalDesc">This action cannot be undone. Are you sure you want to delete this record?</p>
    <div class="modal-actions">
      <button class="btn-cancel">Cancel</button>
      <button class="btn-confirm">Confirm Delete</button>
    </div>
  </div>
</div>
class AccessibleModal {
  constructor(dialogEl) {
    this.dialog = dialogEl;
    this.previousFocus = null;
  }

  open() {
    this.previousFocus = document.activeElement;
    this.dialog.hidden = false;
    const firstFocusable = this.dialog.querySelector(
      'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
    );
    firstFocusable?.focus();
    document.addEventListener('keydown', this.handleKeydown);
  }

  close() {
    this.dialog.hidden = true;
    this.previousFocus?.focus();
    document.removeEventListener('keydown', this.handleKeydown);
  }

  handleKeydown = (e) => {
    if (e.key === 'Escape') this.close();
    if (e.key === 'Tab') this.trapFocus(e);
  };

  trapFocus(e) {
    const focusable = [...this.dialog.querySelectorAll(
      'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
    )];
    const first = focusable[0];
    const last = focusable[focusable.length - 1];
    if (e.shiftKey && document.activeElement === first) {
      e.preventDefault();
      last.focus();
    } else if (!e.shiftKey && document.activeElement === last) {
      e.preventDefault();
      first.focus();
    }
  }
}

Praxis: Barrierefreie Tabs

<div class="tabs">
  <div role="tablist" aria-label="Account settings">
    <button
      role="tab"
      id="tab-profile"
      aria-selected="true"
      aria-controls="panel-profile"
      tabindex="0"
    >Profile</button>
    <button
      role="tab"
      id="tab-security"
      aria-selected="false"
      aria-controls="panel-security"
      tabindex="-1"
    >Security</button>
    <button
      role="tab"
      id="tab-notify"
      aria-selected="false"
      aria-controls="panel-notify"
      tabindex="-1"
    >Notifications</button>
  </div>

  <div role="tabpanel" id="panel-profile" aria-labelledby="tab-profile">
    <p>Profile settings content</p>
  </div>
  <div role="tabpanel" id="panel-security" aria-labelledby="tab-security" hidden>
    <p>Security settings content</p>
  </div>
  <div role="tabpanel" id="panel-notify" aria-labelledby="tab-notify" hidden>
    <p>Notification preferences content</p>
  </div>
</div>
document.querySelector('[role="tablist"]').addEventListener('keydown', (e) => {
  const tabs = [...e.currentTarget.querySelectorAll('[role="tab"]')];
  const idx = tabs.indexOf(document.activeElement);
  let nextIdx;

  if (e.key === 'ArrowRight') nextIdx = (idx + 1) % tabs.length;
  else if (e.key === 'ArrowLeft') nextIdx = (idx - 1 + tabs.length) % tabs.length;
  else if (e.key === 'Home') nextIdx = 0;
  else if (e.key === 'End') nextIdx = tabs.length - 1;
  else return;

  e.preventDefault();
  tabs.forEach((t) => { t.setAttribute('aria-selected', 'false'); t.tabIndex = -1; });
  tabs[nextIdx].setAttribute('aria-selected', 'true');
  tabs[nextIdx].tabIndex = 0;
  tabs[nextIdx].focus();
});

Praxis: Barrierefreies Formular

<form aria-label="User registration">
  <div class="field">
    <label for="email">Email address <span aria-hidden="true">*</span></label>
    <input
      id="email"
      type="email"
      required
      aria-required="true"
      autocomplete="email"
      aria-invalid="false"
      aria-describedby="email-error"
    >
    <span id="email-error" role="alert" aria-live="assertive"></span>
  </div>

  <div class="field">
    <label for="password">Password</label>
    <input
      id="password"
      type="password"
      minlength="8"
      aria-describedby="pwd-rules pwd-strength"
    >
    <span id="pwd-rules">At least 8 characters, including uppercase, lowercase, and digits</span>
    <span id="pwd-strength" aria-live="polite"></span>
  </div>

  <fieldset>
    <legend>Notification method</legend>
    <label><input type="checkbox" name="notify" value="email"> Email</label>
    <label><input type="checkbox" name="notify" value="sms"> SMS</label>
  </fieldset>

  <button type="submit">Register</button>
</form>

Vergleich Automatisierter Testwerkzeuge

Werkzeug Typ WCAG-Abdeckung Integration Merkmal
axe-core Laufzeitbibliothek A/AA CI + Browser Umfassendste, null False Positives
Lighthouse Audit-Werkzeug A CLI / DevTools Kombinierte Leistung + a11y
WAVE Browser-Erweiterung A/AA Manuell Visuelle Anmerkungen
NVDA Bildschirmleser Manuell Echte Nutzererfahrung
Playwright a11y E2E-Test A CI In den Testfluss integriert
import { test } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';

test('accessibility check', async ({ page }) => {
  await page.goto('/register');
  const results = await new AxeBuilder({ page })
    .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
    .analyze();
  expect(results.violations).toEqual([]);
});

Neue Kriterien der WCAG 2.2

Neues Kriterium Stufe Beschreibung
2.4.11 Fokus-Erscheinungsbild AAA Fokusindikatorfläche ≥ 2px, Kontrast ≥ 3:1
2.4.12 Fokus Nicht Verdeckt AAA Fokussiertes Element nicht durch anderen Inhalt verdeckt
3.2.6 Konsistente Hilfe A Position des Hilfemechanismus seitenübergreifend konsistent
3.3.7 Redundante Eingabe A Keine erneute Eingabe zuvor bereitgestellter Informationen erforderlich
3.3.8 Barrierefreie Authentifizierung AA Authentifizierung basiert nicht auf kognitiven Funktionstests

Zusammenfassung der Bewährten Methoden

  1. Native Elemente zuerst: Verwenden Sie <button>, <input> usw. statt ARIA-Neuimplementierung
  2. Fokusverwaltung: Proaktiv zur logischen Position fokussieren nach Modal-/Routenänderungen
  3. Live-Regionen mit Bedacht: aria-live="assertive" nur für kritische Benachrichtigungen verwenden
  4. Automatisiert + manuell: axe-core deckt ~30-40% der Probleme ab; Bildschirmlesertests sind unerlässlich
  5. Kontinuierliche Integration: Barrierefreiheitsprüfungen in die CI-Pipeline einbinden, um Regressionen zu verhindern

Probiere diese browser-lokalen Tools aus — keine Registrierung erforderlich →

#无障碍#ARIA#WCAG#屏幕阅读器#可访问性