Design wechseln

Dialog, Sheet, Popover: Barrierefreiheit und Fokusmanagement bei Overlay-Komponenten

Eine Kunden-E-Mail: „Wenn wir ein Overlay öffnen, springt Tab in den Hintergrund – Tastaturnutzer kommen gar nicht mehr zurecht.”

Peinlich – denn dieses Overlay hatte ich erst letzte Woche geschrieben.

Im Code war das Problem klar: Nach dem Öffnen des Dialogs blieb der Fokus auf einem Hintergrund-Button. Tab führte natürlich auf die Seite dahinter. Für Screenreader-Nutzer war es noch schlimmer – sie wussten nicht, dass ein Overlay geöffnet war, weil der Fokus nicht hineinwanderte und ARIA-Attribute fehlten.

In diesem Artikel geht es um Barrierefreiheit und Fokusmanagement bei Dialog, Sheet und Popover. Die Fallen, die ich selbst erlebt habe – damit Sie sie umgehen können.


Zuerst klären: Die Kernunterschiede der drei Komponenten

Ehrlich gesagt war mir der Unterschied zwischen diesen drei Komponenten lange nicht klar – inklusive mir selbst früher. Overlays sind doch alle gleich, oder? Tatsächlich bestimmen die Kernunterschiede, wie Barrierefreiheit umgesetzt wird.

Dialog (modaler Dialog)

Dialog ist ein modales Overlay, das die Hintergrundinteraktion vollständig blockiert.

Beispiel: Sie klicken auf „Bestellung löschen“, ein Bestätigungsdialog erscheint. Die Seite dahinter ist durch eine Overlay-Maske abgedeckt – kein Klick auf Hintergrundelemente möglich. Das ist das Kernmerkmal: Der Nutzer muss die aktuelle Aufgabe bearbeiten.

Einsatzszenarien:

  • Wichtige Hinweise (Löschbestätigung, Warnungen)
  • Formulare (Login, Registrierung)
  • Aktionen, die sofortige Reaktion erfordern

Barrierefreiheit-Schlüssel: aria-modal="true" ist Pflicht, Fokusfalle muss implementiert sein.

Sheet (Seitenleiste)

Sheet ist ein seitlich einschiebbares Panel. Im Wesentlichen ist es wie Dialog ein modales Overlay – blockiert den Hintergrund, braucht eine Fokusfalle. Der einzige Unterschied ist die Position: Sheet gleitet von der Seite herein, Dialog erscheint zentriert.

Einsatzszenarien:

  • Navigationsmenü (mobile Sidebar)
  • Einstellungspanel (Präferenzen, Theme-Wechsel)
  • Detailansicht (Produktdetails, Artikelvorschau)

„Sheet” kannte ich anfangs auch kaum. Später wurde klar: Es ist ein anderer Name für Drawer – manche UI-Bibliotheken sagen Drawer, Radix UI und shadcn/ui sagen Sheet.

Barrierefreiheit-Schlüssel: Wie bei Dialog – aria-modal="true", Fokusfalle, Esc zum Schließen.

Popover (Pop-up)

Popover ist ein nicht-modales Overlay – blockiert die Hintergrundinteraktion nicht.

Das ist entscheidend: Nach dem Öffnen können Nutzer weiterhin Hintergrundelemente anklicken, der Fokus wird nicht im Popover festgehalten.

Beispiel: Klick auf „Weitere Aktionen” öffnet ein kleines Panel mit „Bearbeiten”, „Kopieren”, „Löschen”. Das ist Popover. Klicken Sie woanders hin, schließt es sich.

Einsatzszenarien:

  • Dropdown-Menüs (Aktionsmenüs, Optionslisten)
  • Tooltips (Rich-Text-Hinweise, Anleitungen)
  • Schnellaktionen (Bearbeiten, Kopieren, Löschen)

Barrierefreiheit-Schlüssel: aria-modal="false" (oder weglassen), keine erzwungene Fokusfalle, Schließen bei Klick außerhalb.

Unterschiede auf einen Blick

Diese Tabelle habe ich selbst beim Schreiben nachgeschlagen – manche Begriffe waren mir vorher tatsächlich unscharf.

MerkmalDialogSheetPopover
Hintergrund blockieren✅ Pflicht✅ Pflicht❌ Nein
FokusfallePflichtPflichtOptional (empfohlen: nicht erzwingen)
Esc schließenPflichtPflichtEmpfohlen
Klick außerhalb schließenOptionalOptionalStandardverhalten
ARIA-Rolledialogdialogpopover
aria-modal"true""true""false" oder weglassen
PositionZentriertSeitlich einschiebendRelativ zum Auslöser

Kurz gesagt: Dialog und Sheet sind modale Overlays, Popover ist nicht-modal. Modale Overlays brauchen eine Fokusfalle, nicht-modale nicht zwingend.


WCAG-Barrierefreiheitsstandards im Detail

WCAG wirkt anfangs trocken – viele englische Fachbegriffe, fast wie Gesetzestext. In der Praxis merkt man aber schnell: Diese Standards helfen wirklich – nicht für Audits, sondern damit Nutzer bedienen können.

Pflicht-ARIA-Attribute

Drei ARIA-Attribute sind für Overlay-Komponenten Pflicht:

1. role="dialog"

Teilt Hilfstechnologien (Screenreader) mit: Das ist ein Dialog.

<div role="dialog">
  <!-- Overlay-Inhalt -->
&lt;/div>

2. aria-labelledby

Verknüpft den Titel. Beim Öffnen liest der Screenreader zuerst den Titel vor.

&lt;div role="dialog" aria-labelledby="dialog-title">
  &lt;h2 id="dialog-title">Löschen bestätigen&lt;/h2>
  &lt;p>Diese Aktion kann nicht rückgängig gemacht werden.&lt;/p>
&lt;/div>

3. aria-modal="true" (nur modale Overlays)

Teilt dem Screenreader mit: Hintergrundinhalte sind nicht erreichbar.

&lt;div role="dialog" aria-modal="true">
  <!-- Modaler Overlay-Inhalt -->
&lt;/div>

aria-labelledby habe ich früher oft vergessen. Erst Screenreader-Tests zeigten: Ohne dieses Attribut hören Nutzer beim Öffnen nur Stille – sie wissen nicht, was das Overlay ist.

Anforderungen an Tastaturnavigation

WCAG definiert klare Regeln für Overlays:

Tab-Taste: Fokus innerhalb des Overlays zirkulieren
Bei Tab soll der Fokus zwischen interaktiven Elementen im Overlay wechseln – nicht in den Hintergrund springen.

Shift+Tab: Rückwärts zirkulieren
Shift+Tab kehrt die Fokusreihenfolge um.

Esc-Taste: Overlay schließen
Esc muss das Overlay schließen. Manche Nutzer erwarten das – ohne Esc-Unterstützung sitzen sie fest.

Enter/Space: Buttons auslösen
Diese Tasten aktivieren Buttons oder Links.

Die Tab-Schleife war mein Anfangsfall – Fokus blieb im Hintergrund, Tab sprang auf die Seite dahinter. Genau das beschwerte sich der Kunde.

Fokusmanagement

Fokusmanagement ist der am leichtesten übersehene Teil der Overlay-Barrierefreiheit. WCAG verlangt:

Beim Öffnen:
Fokus auf das erste interaktive Element im Overlay (meist Schließen-Button oder erstes Eingabefeld).

Beim Schließen:
Fokus zurück zum Auslöser (den Button, der das Overlay geöffnet hat).

Die Fokus-Wiederherstellung nach dem Schließen war mir lange nicht bewusst. Bei Tastaturtests merkte ich: Nach dem Schließen war der Fokus weg – Tastaturnutzer mussten von vorn suchen. Schlechte UX.

Sonderfall:
Bei wichtigen Hinweistexten (z. B. Anleitungen) soll der Fokus zuerst auf dem Container landen, damit der Screenreader den Hinweis vorliest, bevor der Nutzer handelt.

Dafür dem Container tabindex="0" geben:

&lt;div role="dialog" aria-modal="true" tabindex="0">
  &lt;h2>Anleitung&lt;/h2>
  &lt;p>Bitte lesen Sie den folgenden Text sorgfältig, bevor Sie fortfahren …&lt;/p>
  &lt;button>Bestätigen&lt;/button>
&lt;/div>

Beim Öffnen landet der Fokus auf dem Container, der Screenreader liest alles vor, dann Tab zum Button.


Fokusfalle: Implementierungsprinzip

Fokusfalle klingt komplex – das Prinzip ist einfach: Tab soll im Overlay zirkulieren.

Was ist eine Fokusfalle?

Definition: Tab-Navigation auf einen bestimmten Bereich begrenzen.

Beispiel: Overlay geöffnet, Tab von „Schließen” zu „Bestätigen”, nochmal Tab zurück zu „Schließen” – das ist die Fokusfalle.

Warum nötig: Verhindert versehentliche Hintergrundaktionen. Springt der Fokus auf die Seite dahinter, können unbeabsichtigte Klicks passieren.

JavaScript-Implementierung

Kernlogik: Alle interaktiven Elemente finden, Tab abfangen, zwischen erstem und letztem Element zirkulieren.

function trapFocus(modal) {
  // Alle interaktiven Elemente finden
  const focusableElements = modal.querySelectorAll(
    'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
  );

  const firstElement = focusableElements[0];
  const lastElement = focusableElements[focusableElements.length - 1];

  // Tastaturereignisse abfangen
  modal.addEventListener('keydown', (e) => {
    if (e.key === 'Tab') {
      // Shift+Tab: Am ersten Element zum letzten springen
      if (e.shiftKey && document.activeElement === firstElement) {
        e.preventDefault();
        lastElement.focus();
      }
      // Tab: Am letzten Element zum ersten springen
      else if (!e.shiftKey && document.activeElement === lastElement) {
        e.preventDefault();
        firstElement.focus();
      }
    }

    // Esc schließt Overlay
    if (e.key === 'Escape') {
      closeModal();
    }
  });
}

Diesen Code habe ich mehrmals geschrieben, bis er lief. Hauptfallen:

  • Selektor für focusableElements muss vollständig sein – sonst entweicht der Fokus
  • e.preventDefault() ist Pflicht – sonst übernimmt der Browser das Standardverhalten

focus-trap-Bibliothek

Wer selbst implementieren will, kann focus-trap-react nutzen:

import FocusTrap from 'focus-trap-react';

&lt;FocusTrap>
  &lt;div className="modal">
    &lt;button>Schließen&lt;/button>
    &lt;button>Bestätigen&lt;/button>
  &lt;/div>
&lt;/FocusTrap>

Die Bibliothek übernimmt Fokus-Schleife, Esc-Schließen und verschachtelte Overlays.

Mittlerweile nutze ich sie kaum – shadcn/ui integriert Fokusmanagement intern. Radix UI (die Basis von shadcn/ui) erledigt die gesamte Fokusfallen-Logik ohne Extra-Bibliothek.


shadcn/ui in der Praxis: Dialog

Seit shadcn/ui schreibe ich Overlays nicht mehr von Hand. Nicht aus Faulheit – selbst gebaute Overlays haben fast immer Barrierefreiheitslücken, shadcn/ui auf Radix UI-Basis übernimmt alles.

Installation und Grundnutzung

npx shadcn@latest add dialog

Nach der Installation liegt components/ui/dialog.tsx bereit.

Vollständiges Codebeispiel

import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@/components/ui/dialog"
import { Button } from "@/components/ui/button"

export function DeleteConfirmDialog() {
  return (
    &lt;Dialog>
      &lt;DialogTrigger asChild>
        &lt;Button variant="outline">Bestellung löschen&lt;/Button>
      &lt;/DialogTrigger>
      &lt;DialogContent>
        &lt;DialogHeader>
          &lt;DialogTitle>Löschen bestätigen&lt;/DialogTitle>
          &lt;DialogDescription>
            Diese Aktion kann nicht rückgängig gemacht werden. Bestellung wirklich löschen?
          &lt;/DialogDescription>
        &lt;/DialogHeader>
        &lt;div className="flex justify-end gap-2 mt-4">
          &lt;Button variant="outline">Abbrechen&lt;/Button>
          &lt;Button variant="destructive">Löschen&lt;/Button>
        &lt;/div>
      &lt;/DialogContent>
    &lt;/Dialog>
  )
}

Der Code wirkt simpel – Radix UI erledigt im Hintergrund viel:

  • Beim Öffnen Fokus auf den ersten Button („Abbrechen”)
  • Beim Schließen Fokus zurück auf „Bestellung löschen”
  • Tab zirkuliert im Overlay
  • Esc schließt das Overlay
  • aria-labelledby verknüpft DialogTitle automatisch
  • aria-describedby verknüpft DialogDescription automatisch

Wichtige Barrierefreiheitsmerkmale

1. Automatisches Fokusmanagement

Radix UI Dialog verschiebt beim Öffnen den Fokus auf das erste interaktive Element, beim Schließen zurück zum Auslöser.

2. Automatische ARIA-Verknüpfung

DialogTitle verknüpft aria-labelledby, DialogDescription verknüpft aria-describedby.

<!-- Von Radix UI generiertes HTML -->
&lt;div role="dialog" aria-modal="true" aria-labelledby="radix-:r1:" aria-describedby="radix-:r2:">
  &lt;h2 id="radix-:r1:">Löschen bestätigen&lt;/h2>
  &lt;p id="radix-:r2:">Diese Aktion kann nicht rückgängig gemacht werden …&lt;/p>
&lt;/div>

Diese Details übersieht man beim Handcodieren leicht. Mit shadcn/ui kein Problem.

3. Esc schließt automatisch

Esc schließt das Overlay, Fokus kehrt zum Auslöser zurück.

4. Klick auf Overlay-Maske schließt

Klick auf die graue Maske außerhalb schließt ebenfalls. Über onInteractOutside an DialogContent lässt sich das verhindern:

&lt;DialogContent onInteractOutside={(e) => e.preventDefault()}>
  <!-- Klick auf Maske schließt nicht -->
&lt;/DialogContent>

shadcn/ui in der Praxis: Sheet

Sheet teilt alle Barrierefreiheitsmerkmale mit Dialog – der Unterschied ist nur die visuelle Position (seitlich statt zentriert).

Installation und Grundnutzung

npx shadcn@latest add sheet

Vollständiges Codebeispiel

import {
  Sheet,
  SheetContent,
  SheetDescription,
  SheetHeader,
  SheetTitle,
  SheetTrigger,
} from "@/components/ui/sheet"
import { Button } from "@/components/ui/button"

export function NavigationSheet() {
  return (
    &lt;Sheet>
      &lt;SheetTrigger asChild>
        &lt;Button variant="outline">Menü öffnen&lt;/Button>
      &lt;/SheetTrigger>
      &lt;SheetContent side="left">
        &lt;SheetHeader>
          &lt;SheetTitle>Navigationsmenü&lt;/SheetTitle>
          &lt;SheetDescription>
            Wählen Sie die Seite, die Sie besuchen möchten
          &lt;/SheetDescription>
        &lt;/SheetHeader>
        &lt;nav className="flex flex-col gap-4 mt-4">
          &lt;a href="/" className="hover:underline">Startseite&lt;/a>
          &lt;a href="/about" className="hover:underline">Über uns&lt;/a>
          &lt;a href="/contact" className="hover:underline">Kontakt&lt;/a>
        &lt;/nav>
      &lt;/SheetContent>
    &lt;/Sheet>
  )
}

Unterschiede zu Dialog

Sheet und Dialog sind im Code fast identisch – nur andere Komponentennamen. Hauptunterschiede:

1. Seitliche Slide-Animation

Sheet gleitet standardmäßig von rechts herein, Richtung über side:

&lt;SheetContent side="left">   <!-- von links -->
&lt;SheetContent side="right">  <!-- von rechts (Standard) -->
&lt;SheetContent side="top">    <!-- von oben -->
&lt;SheetContent side="bottom"> <!-- von unten -->

2. Gleiche Barrierefreiheit

Sheet bietet dieselben Merkmale wie Dialog:

  • role="dialog"
  • aria-modal="true"
  • Fokusfalle, Esc schließen, Fokus-Wiederherstellung

Sheet nutze ich vor allem für mobile Navigationsmenüs – seitliches Einschieben passt besser zur mobilen Interaktion.


shadcn/ui in der Praxis: Popover

Popover ist nicht-modal – der Kernunterschied zu Dialog und Sheet: Hintergrundinteraktion bleibt möglich.

Installation und Grundnutzung

npx shadcn@latest add popover

Vollständiges Codebeispiel

import {
  Popover,
  PopoverContent,
  PopoverHeader,
  PopoverTitle,
  PopoverDescription,
  PopoverTrigger,
} from "@/components/ui/popover"
import { Button } from "@/components/ui/button"

export function ActionPopover() {
  return (
    &lt;Popover>
      &lt;PopoverTrigger asChild>
        &lt;Button variant="outline">Weitere Aktionen&lt;/Button>
      &lt;/PopoverTrigger>
      &lt;PopoverContent>
        &lt;PopoverHeader>
          &lt;PopoverTitle>Schnellaktionen&lt;/PopoverTitle>
          &lt;PopoverDescription>
            Wählen Sie eine Aktion
          &lt;/PopoverDescription>
        &lt;/PopoverHeader>
        &lt;div className="flex flex-col gap-2 mt-2">
          &lt;Button size="sm">Bearbeiten&lt;/Button>
          &lt;Button size="sm">Kopieren&lt;/Button>
          &lt;Button size="sm" variant="destructive">Löschen&lt;/Button>
        &lt;/div>
      &lt;/PopoverContent>
    &lt;/Popover>
  )
}

Wichtige Unterschiede

Popover sieht im Code ähnlich aus, verhält sich aber grundlegend anders:

1. Nicht-modal

Nach dem Öffnen können Nutzer Hintergrundelemente anklicken. Der Fokus wird nicht im Popover festgehalten.

2. Keine erzwungene Fokusfalle

Tab kann vom Popover in den Hintergrund springen – ganz anders als bei Dialog.

3. Schließen bei Klick außerhalb

Klick außerhalb schließt standardmäßig. Über onInteractOutside verhinderbar:

&lt;PopoverContent onInteractOutside={(e) => e.preventDefault()}>
  <!-- Klick außerhalb schließt nicht -->
&lt;/PopoverContent>

4. Flexible Positionierung

Horizontale Ausrichtung über align:

&lt;PopoverContent align="start">  <!-- links -->
&lt;PopoverContent align="center"> <!-- zentriert (Standard) -->
&lt;PopoverContent align="end">    <!-- rechts -->

Popover nutze ich vor allem für Aktionsmenüs – ein Klick, ein paar Schnelloptionen. Kein Hintergrund-Block nötig, Popover passt perfekt.


Fortgeschrittene Tipps und typische Fallen

Viele Fallen habe ich selbst erlebt. Hier die häufigsten.

Fokus-Wiederherstellung: Auslöser wurde gelöscht

Szenario: Overlay geöffnet, Auslöser-Button wird gelöscht. Beim Schließen gibt es kein Fokusziel.

Lösungen:

  1. Auslöser nicht löschen, nur ausblenden
  2. Alternatives Fokusziel merken
const [triggerElement, setTriggerElement] = useState&lt;HTMLElement | null>(null);

// Auslöser beim Öffnen merken
const handleOpen = (e: React.MouseEvent&lt;HTMLButtonElement>) => {
  setTriggerElement(e.currentTarget);
  setOpen(true);
};

// Fokus beim Schließen wiederherstellen
const handleClose = () => {
  setOpen(false);
  triggerElement?.focus();
};

Diese Falle kannte ich aus eigener Erfahrung: Nach dem Löschen eines Eintrags wusste der Fokus nicht, wohin. Lösung: Fokus auf den vorherigen Listeneintrag.

Screenreader: Overlay-Inhalt wird nicht vorgelesen

Szenario: Overlay geöffnet, Screenreader liest nichts vor – Nutzer wissen nicht, was drin ist.

Ursachen:

  1. aria-labelledby fehlt
  2. Fokus ist nicht im Overlay

Lösung:
DialogTitle und DialogDescription setzen – shadcn/ui verknüpft ARIA automatisch.

&lt;DialogContent>
  &lt;DialogHeader>
    &lt;DialogTitle>Löschen bestätigen&lt;/DialogTitle>  <!-- Pflicht -->
    &lt;DialogDescription>Diese Aktion kann nicht rückgängig gemacht werden&lt;/DialogDescription>  <!-- Pflicht -->
  &lt;/DialogHeader>
&lt;/DialogContent>

DialogDescription habe ich oft vergessen. NVDA-Tests zeigten: Ohne Beschreibung kennen Nutzer nur den Titel, nicht den Inhalt.

Verschachtelte Overlays: Fokus-Chaos

Szenario: Overlay A öffnet Overlay B, nach Schließen von B ist der Fokus verloren.

Lösung:
Radix UI Dialog und Sheet unterstützen Verschachtelung. Beim Schließen des inneren Overlays kehrt der Fokus zum inneren Auslöser (oft ein Button im äußeren Overlay).

&lt;Dialog>
  &lt;DialogTrigger>Overlay A öffnen&lt;/DialogTrigger>
  &lt;DialogContent>
    &lt;DialogTitle>Overlay A&lt;/DialogTitle>

    <!-- Overlay B innerhalb von A -->
    &lt;Dialog>
      &lt;DialogTrigger>Overlay B öffnen&lt;/DialogTrigger>
      &lt;DialogContent>
        &lt;DialogTitle>Overlay B&lt;/DialogTitle>
      &lt;/DialogContent>
    &lt;/Dialog>
  &lt;/DialogContent>
&lt;/Dialog>

Verschachtelte Overlays vermeide ich wenn möglich. Wenn nötig, Radix-Verschränkung nutzen und Fokus automatisch verwalten lassen.

Animationsverzögerung: Fokus nicht im Overlay

Szenario: Overlay mit Einblend-Animation – während der Animation liegt der Fokus nicht im Overlay.

Ursache:
Zu Animationsbeginn ist das Overlay noch nicht sichtbar, Fokussetzung schlägt fehl.

Lösung:
Radix UI wartet mit der Fokussetzung bis die Animation fertig ist.

Bei eigener Implementierung auf Animationsende warten:

modal.addEventListener('animationend', () => {
  const firstFocusable = modal.querySelector('button, [href], input');
  firstFocusable?.focus();
});

Selbst gebaute Overlays hatten den Fokus noch auf dem Hintergrund-Button, weil die Fokussetzung vor Animationsende lief. animationend-Listener half.


Zusammenfassung

Kernpunkte in drei Sätzen:

1. Kernunterschiede der drei Komponenten

Dialog und Sheet sind modal – Hintergrund blockieren, Fokusfalle Pflicht.
Popover ist nicht-modal – Hintergrund bleibt bedienbar, Fokus nicht erzwungen.

2. WCAG-Dreiklang

ARIA-Attribute (role="dialog", aria-labelledby, aria-modal="true")
Tastaturnavigation (Tab-Schleife, Shift+Tab rückwärts, Esc schließen)
Fokusmanagement (beim Öffnen fokussieren, beim Schließen wiederherstellen)

3. shadcn/ui übernimmt alles

Radix UI erledigt Fokusfalle, ARIA und Tastaturnavigation. Mit shadcn/ui sind Barrierefreiheitsprobleme selten.

Nach vielen selbst gebauten Overlays ist mein Prinzip einfach: In Produktion shadcn/ui bevorzugen. Handcodierung bringt ständig neue Barrierefreiheitslücken – shadcn/ui auf Radix UI-Basis löst das.

Wichtig bleibt: Die Prinzipien verstehen. Wissen, was Radix UI im Hintergrund tut – dann finden Sie Probleme schnell.


Referenzen


FAQ

Was ist der Unterschied zwischen Dialog, Sheet und Popover?
Dialog und Sheet sind modale Overlays – nach dem Öffnen wird die Hintergrundinteraktion blockiert, eine Fokusfalle ist Pflicht. Popover ist ein nicht-modales Overlay – der Hintergrund bleibt bedienbar, der Fokus kann frei wandern. Der Kernunterschied liegt darin, ob der Hintergrund blockiert wird.
Welche Barrierefreiheitsanforderungen müssen Overlay-Komponenten erfüllen?
WCAG verlangt drei Bereiche: ARIA-Attribute (role="dialog", aria-labelledby, aria-modal="true"), Tastaturnavigation (Tab-Schleife, Shift+Tab rückwärts, Esc zum Schließen), Fokusmanagement (beim Öffnen Fokus ins Overlay, beim Schließen zurück zum Auslöser).
Was ist eine Fokusfalle und warum ist sie bei modalen Overlays Pflicht?
Eine Fokusfalle begrenzt die Tab-Navigation auf einen bestimmten Bereich. Modale Overlays müssen sie implementieren, damit Nutzer nicht versehentlich Hintergrundinhalte bedienen. Wandert der Fokus auf die Hintergrundseite, können unbeabsichtigte Aktionen ausgelöst werden.
Welche Barrierefreiheitsdetails übernimmt die shadcn/ui Dialog-Komponente automatisch?
shadcn/ui basiert auf Radix UI und übernimmt automatisch:

• Fokus wandert beim Öffnen zum ersten interaktiven Element
• Beim Schließen kehrt der Fokus zum Auslöser zurück
• Tab-Taste zirkuliert innerhalb des Overlays
• Esc schließt das Overlay automatisch
• aria-labelledby verknüpft DialogTitle automatisch
• aria-describedby verknüpft DialogDescription automatisch
Wohin soll der Fokus nach dem Schließen eines Overlays zurückkehren?
Zum Auslöser (dem Button, der das Overlay geöffnet hat) – das verlangt WCAG ausdrücklich. Wurde der Auslöser gelöscht (z. B. bei einer Löschaktion), sollte der Fokus auf das logisch nächste Element – etwa den vorherigen Listeneintrag.
Was tun, wenn die Fokussetzung bei Overlay-Animationen fehlschlägt?
Zu Animationsbeginn ist das Overlay möglicherweise noch nicht vollständig sichtbar – die Fokussetzung schlägt fehl. Lösung: Fokus erst nach Animationsende setzen, animationend-Event abwarten. Radix UI erledigt das automatisch.
Wie sorge ich dafür, dass Screenreader Overlay-Inhalte vorlesen?
DialogTitle und DialogDescription müssen gesetzt sein – shadcn/ui verknüpft aria-labelledby und aria-describedby automatisch. Bei wichtigen Hinweistexten kann der Container tabindex="0" erhalten, damit der Fokus zuerst auf dem Container landet und der Screenreader den gesamten Inhalt vorliest.

12 Min. Lesezeit · Veröffentlicht am: 29. März 2026 · Aktualisiert am: 9. Juli 2026

Kommentare

Melde dich mit GitHub an, um einen Kommentar zu hinterlassen

Easton BlogEaston Blog