Native Dialoge und die Popover API — Das solltet ihr wissen

Das moderne Web ist großartig! Wir können leicht barrierefreie, robuste Modal-Dialoge mit dem nativen <dialog>-Element erstellen. Ihr möchtet ein Menü oder einen Tooltip über dem anderen Seiteninhalt öffnen? Kein Problem! Das HTML-Attribut popover verwandelt jedes Element in ein Popup.

In den letzten Monaten habe ich einige Dialoge mit dem nativen HTML-Element erstellt. Und im April, als die Popover API endlich browserübergreifend unterstützt wurde, begann ich mit Popover in verschiedenen Projekten zu experimentieren. Mir wurde schnell klar: Diese Web-Features erleichtern uns Web-Entwickler:innen das Leben sehr, aber es gibt auch einige Herausforderungen. Vor allem, wenn man beide Features miteinander kombiniert!

Mehrere übereinander gestapelte Pfannkuchen. Foto: © Rama Khandkar / pexels.com

Ich werde kurz darauf eingehen, wie Dialoge und Popovers miteinander interagieren. Dann werde ich meine hart erarbeiteten Erkenntnisse mit euch teilen. Wenn ihr mit den Features noch nicht vertraut seid, empfehle ich euch, meine Blogbeiträge „Warum du das native Dialog-Element nutzen solltest“ und „Einfach herausragend! Die neue Popover API und CSS Anchor Positioning“ zu lesen.

Die Grundlagen: Wie Dialog- und Popover-Elemente interagieren

Wenn ihr einen Modal-Dialog mit der Methode showModal() öffnet, wird das Dialogfeld zum Top-Layer hinzugefügt und über anderen Seiteninhalten gerendert. Dasselbe geschieht, wenn ihr Popover-Inhalte mit der Methode showPopover() anzeigt.

Was passiert nun, wenn ein Element im Top-Layer bereits geöffnet ist und ihr ein weiteres Element hinzufügt? Die Elemente werden in der Reihenfolge übereinander gelegt, in der sie dem Top-Layer hinzugefügt wurden. Das zuletzt hinzugefügte Element erscheint immer oben. Mit der CSS-Eigenschaft z-index könnt ihr diese Reihenfolge auch nicht verändern. Es zählt allein, wann ein Element dem Top-Layer hinzugefügt wurde.

Um ein Beispiel zu geben: Eine Schaltfläche öffnet ein Menüfeld als Popover. Dieses Menü enthält eine Option, die einen Modal-Dialog öffnet, der über dem Menü-Panel gerendert wird. Dieses Dialogfeld enthält eine Schaltfläche, die ein Tooltip-Popover über dem Dialogfeld öffnet. Und so weiter, und so fort. 😉

Was ihr über Dialoge und Popovers wissen solltet

Animation als Progressive Enhancement

Alles sieht besser aus mit flüssigen Animationen. Es gibt nur ein Problem: Dialoge und Popovers haben display: none gesetzt, wenn sie ausgeblendet sind, und display: block, wenn sie angezeigt werden. Und wir alle wissen, dass man Inhalte nicht von oder zu display: none animieren kann. Richtig? Falsch!

Die neue @starting-style Regel zusammen mit der CSS-Eigenschaft transition-behavior ermöglicht die Animation von Dialogen und Popovers. Ich habe beide Features in meinem Blogbeitrag „Barrierefreie Alerts leicht gemacht mit der Popover API“ ausführlich beschrieben.

Seht euch meine Demo für einen Modal-Dialog mit sanfter Ein- und Ausblend-Animation an. Zum Zeitpunkt des Verfassens dieses Artikels funktioniert das nur in Chrome und Edge:

Ihr könnt Transitionen für das Dialogfeld (oder Popover) selbst sowie für den Hintergrund definieren. Hier ist der CSS-Code für die Animation des Dialogs:

dialog { --duration: 150ms; --start-opacity: 0.5; --start-scale: scale(0.8); /* End values for fade out. */ opacity: var(--start-opacity); transform: var(--start-scale); transition: opacity var(--duration) ease-out, transform var(--duration) cubic-bezier(0, 0, 0.2, 1), overlay var(--duration) allow-discrete, display var(--duration) allow-discrete; } dialog[open] { /* End values for fade in; start values for fade out. */ opacity: 1; transform: scale(1); @starting-style { /* Start values vor fade in. */ opacity: var(--start-opacity); transform: var(--start-scale); } }

Und so animiert ihr den Hintergrund des Dialogs:

/* Styling for backdrop behind the dialog */ dialog::backdrop { background: rgb(0 0 0 / 0.32); /* End value for fade out. */ opacity: 0; transition: opacity var(--duration), overlay var(--duration) allow-discrete, display var(--duration) allow-discrete; } dialog[open]::backdrop { /* End value for fade in; start value for fade out. */ opacity: 1; } /* This starting-style rule cannot be nested inside the above selector because the nesting selector cannot represent pseudo-elements. */ @starting-style { dialog[open]::backdrop { /* Start value vor fade in. */ opacity: 0; } }

Aber was ist mit der Browser-Unterstützung? Keine Sorge! Die @starting-style Regel und die Eigenschaft transition-behavior sind perfekte Beispiele für Progressive Enhancement. Wenn ein Browser die neuen Features nicht unterstützt, wird das Dialog- oder Popover-Element trotzdem gerendert, allerdings ohne die Animation.

Den Dialog automatisch schließen bei Klick auf den Hintergrund

Das native <dialog>-Element hat einige tolle Funktionen eingebaut:

  • Wenn ein Modal-Dialog geöffnet wird, setzt der Browser den Fokus auf das erste interaktive Element innerhalb des Dialogs.
  • Beim Schließen des Modal-Dialogs wird der Fokus wieder auf das Element gesetzt, das den Dialog geöffnet hat.
  • Benutzer können den Modal-Dialog mit der ESC-Taste schließen.

Eine wichtige Funktion wird jedoch standardmäßig nicht unterstützt: Automatisches Schließen des Dialogs, wenn die Nutzer:innen auf den Hintergrund klicken. In meinem ersten Blogbeitrag über Dialoge habe ich eine benutzerdefinierte Implementierung dieser Funktion gezeigt: Man ermittelt die Koordinaten des Klicks und vergleicht sie mit dem Rechteck des Dialogs.

Vor kurzem bin ich auf eine elegantere Lösung gestoßen: Ihr definiert einen Klick-Event-Listener für den Dialog und überprüft dann den Tag-Namen des Ereignis-Ziels. Hier ist der JavaScript-Code für die Funktion:

function onDialogClick(event) { event.stopPropagation(); if (event.target.tagName === "DIALOG") { dialogElementRef.close(); } }

Ein Klick auf den Dialog-Hintergrund wird als Klick auf das Dialogelement registriert. Damit dies funktioniert, muss der Inhalt des Dialogs von einem zusätzlichen Element umschlossen werden. Andernfalls würde ein Klick in bestimmte Bereiche des Dialogs diesen auch schließen.

Schaut euch die CodePen-Demo oben an, in der ich dieses benutzerdefinierte Verhalten ebenfalls implementiert habe.

Wie ihr Popover-Inhalte korrekt in Modal-Dialogen verschachtelt

Unlängst habe ich für einen Kunden an einem Webprojekt gearbeitet, das eine Liste von Elementen enthält. Die Nutzer:innen können auf eines der Elemente klicken, um einen Modal-Dialog mit weiteren Details zu öffnen. Dieser Dialog enthält eine Schaltfläche, mit der man das Element zur Favoritenliste hinzufügen kann. Dann wird am unteren Rand des Bildschirms eine Snackbar angezeigt, die auch eine Rückgängig-Schaltfläche enthält.

Als hipper, moderner Webentwickler wollte ich natürlich das <dialog>-Element in Kombination mit einem popover-Element für die Snackbar verwenden. Nachdem ich alles implementiert hatte, begann ich, die Rückgängig-Funktion zu testen und war verblüfft: Obwohl die Snackbar mit der Rückgängig-Schaltfläche sichtbar war und über dem Modal-Dialog angezeigt wurde, konnte ich nicht mit ihr interagieren. Ich so: „Was zum Teufel ist hier los?!“

Nach etwas Recherche habe ich die Ursache des Problems gefunden. Aber bevor ich euch die Lösung verrate, seht euch diese Demo mit einer Minimalversion des oben beschriebenen Webprojekts an. Der Dialog enthält zwei Schaltflächen: Die erste öffnet eine Snackbar, mit der ihr nicht interagieren könnt. Die zweite Schaltfläche öffnet eine Snackbar mit einer funktionierenden Rückgängig-Taste. Könnt ihr den Unterschied erkennen?

Ich erkläre euch was los ist: Im Allgemeinen hat die Position eines popover-Elements im DOM keinen Einfluss auf seine Sichtbarkeit. Wenn ihr das Popover einblendet, fügt der Browser es dem Top-Layer hinzu und es erscheint über allen anderen Inhalten. Aber nur weil ihr etwas sehen könnt, heißt das nicht, dass ihr damit interagieren könnt. Die Antwort findet sich in der Beschreibung der Methode showModal():

The showModal() method of the HTMLDialogElement interface displays the dialog as a modal [...]. Interaction outside the dialog is blocked and the content outside it is rendered inert.

Das heißt, wenn ihr das popover-Element für die Snackbar außerhalb des Dialogs platziert, wird es inaktiv gemacht. Alle Eingabe-Events der Nutzer:innen für das Element und seine Inhalte werden ignoriert. Was könnt ihr also tun? Platziert das Popover-Element einfach innerhalb des Dialogelements. Jetzt betrachtet der Browser es als Teil des Dialoginhalts und macht es nicht mehr inaktiv.

Manuelle Popovers innerhalb von Dialogen solltet ihr immer schließen

Eine letzte Bemerkung zu manuellen Popovers, die in einem Modal-Dialog eingebettet sind. Wenn ihr den Dialog schließt, dann schließt auch immer das Popover mit der Methode hidePopover(). Andernfalls wäre das Popover zwar nicht sichtbar, aber immer noch offen und würde im Top-Layer verbleiben.

Wenn ihr nun den Dialog erneut öffnet, wird er über dem Popover im Top-Layer platziert. Selbst wenn ihr also versucht, das Popover erneut mit der Methode showPopover() zu öffnen, wird nichts passieren! Das Popover ist bereits geöffnet und befindet sich im Top-Layer unterhalb des Modal-Dialogs. Was für ein Schlamassel!

Fazit

Das native <dialog>-Element und das popover-Attribut sind sehr mächtige und praktische Features. Aber es gibt auch einige Besonderheiten. Ich hoffe, meine Erkenntnisse helfen euch dabei, diese großartigen Features in euren eigenen Projekten zu nutzen. Viel Spaß beim Coden! 😊

Erstellt am