Warum du das native Dialog-Element nutzen solltest

Wir sind alle mit Dialogboxen im Web vertraut. Von einfachen Aufforderungen, eine Aktion zu bestätigen, bis hin zu Fenstern mit komplexen Inhalten — Dialoge sind ein integraler Bestandteil moderner Benutzeroberflächen im Web.

Leider gab es lange Zeit kein natives Dialog-Element in HTML, weshalb zahlreiche eigene Implementierungen mit schweren Barrierefreiheits-Problemen entwickelt wurden. Das änderte sich mit der Einführung des <dialog> Elements in HTML 5.2 vor ein paar Jahren. Nachdem auch Safari endlich mit Version 15.4 das Element unterstützt, können nun alle modernen Browser damit umgehen.

Zwei weiße Sprechblasen auf pinkem Hintergrund. Foto: © Miguel Á. Padriñán / pexels.com

Ich zeige euch, wie einfach ihr einen barrierefreien Modal-Dialog mit dem nativen Element erstellen könnt. Bei gewissen Browsern und Screenreadern gibt es noch kleinere Probleme, worauf ich am Ende des Artikels eingehen werde.

Was macht das <dialog> Element?

Das Dialog-Element erzeugt eine Popup-Box auf der Webseite, welche die Aufmerksamkeit der Nutzer:innen auf sich zieht. Die HTML-Spezifikation beschreibt das Element so:

The dialog element represents a part of an application that a user interacts with to perform a task, for example a dialog box, inspector, or window.

Ein typischer Anwendungsfall ist ein Modal-Dialog, der den Rest der Seite verdeckt und die Nutzer:innen zur Eingabe von Daten auffordert. Ich habe eine Demo mit dem React-Framework erstellt (Quellcode):

Aufbau und Interaktion mit dem Dialog

Der grundlegende JSX-Code meines Modal-Dialogs sieht so aus:

<dialog aria-labelledby='dialog-personal-info-heading' ref={formDialogRef} onClick={onFormDialogContainerClick} > <h3 id="dialog-personal-info-heading"> Personal Information </h3> <p>...</p> <form method="dialog" onClick={event => event.stopPropagation()} > // form elements </form> </dialog>

Standardmäßig rendert der Browser den Dialog erst dann, wenn ihr die Eigenschaft open setzt, um den Dialog sichtbar zu machen. Empfohlen wird jedoch, die Methode .show() oder .showModal() zu verwenden, um den Dialog anzuzeigen.

In meinem Fall will ich einen Modal-Dialog anzeigen, der den Rest der Seite verdeckt. In meiner funktionellen React-Komponente erzeuge ich eine Referenz auf das HTML-Element mithilfe des useRef Hooks und öffne den Dialog beim Klick auf eine Schaltfläche:

const formDialogRef = useRef<HTMLDialogElement>(null); const onOpenFormDialogClick = () => { formDialogRef.current?.showModal(); }

Um den Dialog zu schließen, verwende ich die Methode .close(). Wie meine Demo zeigt, kann auch ein <form> Element den Dialog mithilfe des Attributs method="dialog" schließen. Beim Abschicken des Formulars wird der Dialog geschlossen und für die Eigenschaft returnValue der Wert der Schaltfläche gesetzt, mit der das Formular abgeschickt wurde.

<form method="dialog"> <div className={styles.formField}> <label htmlFor="favMovie">Favorite movie:</label> <input id="favMovie" type="text" /> </div> <button type="submit" value={DIALOG_CONFIRM_VALUE} > Confirm </button> </form>

Ihr könnt einen Event Listener für das close Ereignis des Dialogs hinzufügen und somit eine Aktion abhängig vom returnValue ausführen.

Den Dialog und seinen Hintergrund stylen

Der Modal-Dialog wird standardmäßig zentriert und über dem restlichen Inhalt der Seite platziert. Browser wenden jeweils ein eigenes Styling auf das dialog Element an, meist einen dicken schwarzen Rahmen. Mit CSS könnt ihr das Aussehen des Dialogs individuell gestalten. Definiert zum Beispiel einen Schattenwurf und runde Ecken:

dialog { border: 0.125rem solid var(--border-color); border-radius: 4px; box-shadow: 0 11px 15px -7px #0003, 0 24px 38px 3px #00000024, 0 9px 46px 8px #0000001f; font-size: 1rem; max-width: min(18rem, 90vw); }

Ein geniales Feature des nativen Dialogs ist das CSS Pseudo-Element ::backdrop. Damit könnt ihr den Bereich hinter dem Modal-Dialog gestalten, etwa indem ihr die restliche Seite abdunkelt und verschwommen macht:

dialog::backdrop { background: rgba(36, 32, 20, 0.5); backdrop-filter: blur(0.25rem); }

Tastatur- und Mausbedienung

Beim Öffnen des Modal-Dialogs setzt der Browser automatisch den Fokus auf das erste, interaktive Element im Dialog. Diese Verhaltensweise ist für die meisten Anwendungsfälle passend, etwa bei meinem Formular-Dialog. In manchen Fällen wäre es jedoch besser, wenn man den Fokus auf den gesamten Dialog oder ein Textelement am Anfang setzen könnte. Mehr dazu im Vorschlag für initiale Fokus-Platzierung

Solange der Modal-Dialog geöffnet ist, können Nutzer:innen mit dem restlichen Inhalt der Seite nicht interagieren. Das bedeutet, dass Tastaturnutzer:innen den Dialog nicht mit der TAB Taste verlassen können. Auch der virtuelle Cursor des Screenreaders (Pfeiltasten oder Swiping) kann sich nur innerhalb des Dialogs bewegen.

Der Modal-Dialog kann standardmäßig mit der ESC Taste geschlossen werden. Nach dem Schließen wird der Fokus automatisch zurück auf das Bedienelement gesetzt, mit dem der Dialog geöffnet wurde. Davon profitieren Tastatur- und Screenreader-Nutzer:innen, die sofort an der Stelle weitermachen können, wo sie aufgehört hatten.

Leider wird das dialog Element nicht automatisch geschlossen, wenn Nutzer:innen außerhalb des Dialogs klicken. Um dieses Verhalten zu implementieren, können wir die Koordinaten des Klicks mit dem gerenderten Rechteck des Dialogs vergleichen (danke für die tolle Idee, Amit Merchant):

const isClickOutsideOfDialog = (dialogEl: HTMLDialogElement, event: React.MouseEvent): boolean => { const rect = dialogEl.getBoundingClientRect(); return (event.clientY < rect.top || event.clientY > rect.bottom || event.clientX < rect.left || event.clientX > rect.right); } const onFormDialogContainerClick = (event: React.MouseEvent) => { const formDialogEl = formDialogRef.current; if (formDialogEl && isClickOutsideOfDialog(formDialogEl, event) ){ formDialogEl.close(DIALOG_CANCEL_VALUE); } }

Das funktioniert meistens sehr gut. Ein Problem habe ich bei meiner Demo festgestellt: Wenn Nutzer:innen die native Auswahlliste öffnen, würde der Algorithmus dies als Klick außerhalb des Dialogs werten. Aus diesem Grund habe ich einen Click Event Listener für das form Element registriert und nutze die Methode .stopPropagation().

Probleme mit der Barrierefreiheit bei manchen Screenreadern

Das native dialog Element funktioniert super mit den meisten Screenreadern und Browsern. Leider gibt es noch ein paar kleine Probleme, wie die Barrierefreiheits-Tests meiner Demo aufzeigen:

  • Windows 10, Google Chrome 103.0.5060.114, NVDA 2022.1: Wenn der Dialog geöffnet wird und Fokus erhält, liest der Screenreader die Dialog-Rolle, die Überschrift (dank aria-labelledby), den ersten Textabsatz und die fokussierte Auswahlliste vor. Die Fokus-Reihenfolge und der virtuelle Cursor des Screenreaders wird auf die Inhalte im Dialog beschränkt.
  • Windows 10, Firefox 102.0.1, NVDA 2022.1: Identisch zu Google Chrome, mit der Ausnahme dass die Schaltfläche, mit welcher der Dialog geöffnet wurde, Teil der Fokus-Reihenfolge ist. Wahrscheinlich ein Firefox-Bug, der in Zukunft behoben wird.
  • Samsung Galaxy S20, Android 12, Google Chrome 103.0.5060.71, TalkBack: Der Screenreader liest nur die fokussierte Auswahlliste vor. Der virtuelle Cursor (z.B. nach rechts wischen) wird auf die Inhalte im Dialog beschränkt.
  • Samsung Galaxy S20, Android 12, Firefox 102.2.1, TalkBack: Der Screenreader liest nur die fokussierte Auswahlliste vor. Der virtuelle Cursor wird nicht auf die Inhalte im Dialog beschränkt. Das hat wahrscheinlich damit zu tun, dass Firefox immer noch nicht die Eigenschaft aria-modal unterstützt.
  • iPhone 8, iOS 15.5, Safari, VoiceOver: Der Screenreader liest nur die fokussierte Auswahlliste vor. Der virtuelle Cursor kann auf die Inhalte im Header gesetzt werden, jedoch nicht auf die Inhalte im Hauptinhaltsbereich, der direkt unter dem Dialog liegt.

Fazit

Das <dialog> Element ist einfach zu handhaben und standardmäßig barrierefrei, abgesehen von kleineren Problemen. Für manche Anwendungsfälle dürfte daher aktuell ein robuster, selbst gebauter Dialog wie a11y-dialog besser geeignet sind. Ich bin aber optimistisch was die Zukunft des nativen Elements betrifft.

Nützliche Links

Update am 07.03.2023

Die HTML-Spezifikation hat eine wichtige Aktualisierung hinsichtlich des initialen Fokus-Managements erhalten. Es wird möglich sein, dem dialog Element selbst den Fokus zu geben, wenn es das Attribut autofocus gesetzt hat.

Sicher, es wird einige Zeit dauern, bis die Browser-Hersteller diese Änderungen umsetzen. Aber es gibt keinen Grund, noch länger zu warten! Ich sehe es wie Scott O'Hara: Es ist an der Zeit, von benutzerdefinierten Dialogen wegzukommen und stattdessen das native Dialog-Element zu verwenden.

Erstellt am