Zum Inhalt springen
Interaktion

Dialoge ohne JavaScript öffnen und schließen

Wie ein Button mit command und commandfor einen Dialog öffnet, welche Befehle es gibt, wie closedby das Schließen per Klick daneben regelt, wo close und request-close sich unterscheiden und was in Safari noch fehlt.

9 Min. LesezeitVon Leonie RoesmannStand: 07. November 2026
InteraktionHTMLDialogBarrierefreiheit

Das dialog-Element gibt es seit März 2022 in allen großen Browsern. Es bringt Fokus, oberste Ebene, Hintergrund und Schließen mit Escape von selbst mit. Trotzdem steht neben fast jedem Dialog noch ein Stück Skript, denn bis vor kurzem ließ er sich nur über showModal öffnen, und dafür brauchte jeder Button einen Klick-Listener. Zwei Attribute machen dieses Skript überflüssig. command und commandfor an einem Button öffnen und schließen einen Dialog oder ein Popover allein im Markup. Chrome und Edge kennen sie seit Version 135, Firefox seit 144, Safari seit 26.2, und seit Dezember 2025 führt MDN sie als Baseline. closedby am Dialog legt fest, ob ein Klick daneben ihn schließt. Das können Chrome und Edge seit Version 134 und Firefox seit 141, Safari bisher nur in der Vorschauversion. Dieser Beitrag zeigt beide Attribute, die Befehle, den Unterschied zwischen close und request-close und den Rückfall für Safari.

Das Wichtigste in Kürze
  • Ein Button mit command="show-modal" und commandfor mit der id des Dialogs öffnet ihn als modalen Dialog, ohne eine Zeile Skript. command="close" schließt ihn wieder.
  • Es gibt sechs eingebaute Befehle: show-modal, close und request-close für Dialoge, show-popover, hide-popover und toggle-popover für Popover. Eigene Befehle beginnen mit zwei Bindestrichen und lösen ein command-Ereignis am Ziel aus.
  • closedby="any" schließt einen Dialog auch per Klick daneben. Ohne Angabe gilt bei einem modalen Dialog closerequest, also Escape, bei einem nicht modalen none.
  • Safari kennt command und commandfor seit 26.2, closedby aber noch nicht in einer fertigen Version. Für den Klick daneben bleiben dort ein paar Zeilen Skript, die man nur lädt, wenn das Attribut fehlt.
01

Warum braucht ein Dialog bisher JavaScript?

Weil das dialog-Element zwar alles kann, was ein modaler Dialog braucht, sich aber nur aus einem Skript heraus öffnen ließ. showModal legt ihn in die oberste Ebene, sperrt den Rest der Seite, setzt den Fokus hinein und schließt ihn bei Escape. Aufrufen musste man showModal aber selbst, und dafür brauchte jeder Button einen Klick-Listener.

In einem React-Projekt wurde daraus schnell mehr. Ein Ref auf das Element, eine Funktion im Klick-Handler, oft ein Zustand für offen und geschlossen, der mit dem Element synchron gehalten werden musste, und eine Komponente, die Button und Dialog zusammenhält, weil der Button den Ref kennen muss. Viele Projekte griffen deshalb gleich zu einer Bibliothek, die das alles ein zweites Mal nachbaut.

Für Popover gab es mit popovertarget schon länger einen Weg ohne Skript. Dialoge blieben außen vor, und genau diese Lücke schließen command und commandfor.

Der Dialog konnte alles. Nur aufmachen konnte er sich nicht selbst.

02

Wie öffnet man einen Dialog ohne JavaScript?

Mit einem Button, der command="show-modal" und commandfor mit der id des Dialogs trägt. Ein Klick darauf ruft intern dasselbe auf wie showModal, mit Fokus, Hintergrund und oberster Ebene. Ein zweiter Button im Dialog mit command="close" und derselben id schließt ihn wieder.

Der Button muss dafür nicht neben dem Dialog stehen. commandfor verweist über die id, Button und Dialog können also an ganz verschiedenen Stellen im Dokument liegen, auch in verschiedenen Komponenten. Wichtig ist nur, dass die id eindeutig ist und das Ziel ein Element ist, das den Befehl versteht.

Chrome und Edge kennen die Attribute seit Version 135, Firefox seit 144 und Safari seit 26.2. MDN führt sie seit Dezember 2025 als Baseline, also als neu in allen großen Browsern verfügbar.

<button command="show-modal" commandfor="invite">  Invite people</button> <dialog id="invite" aria-labelledby="invite-title">  <h2 id="invite-title">Invite people</h2>  <p>They will get an email with a link.</p>  <button command="close" commandfor="invite">Done</button></dialog>
Öffnen und Schließen im Markup. Kein Listener, kein Ref, kein Zustand.
03

Welche Befehle kennt command?

Sechs eingebaute, drei für Dialoge und drei für Popover. Dazu kommen eigene Befehle, die mit zwei Bindestrichen beginnen. Ein eigener Befehl tut von sich aus nichts, sondern löst am Ziel ein Ereignis namens command aus, in dem steht, welcher Befehl gemeint war.

Damit lassen sich eigene Komponenten genauso ansteuern wie eingebaute Elemente. Ein Bildbetrachter kann auf --next und --previous hören, ein Video auf --mute. Der Button bleibt ein Button mit Text und Fokus, und das Skript sitzt an genau einer Stelle, nämlich am Ziel.

  1. show-modal Öffnet einen Dialog modal, wie showModal: oberste Ebene, Hintergrund, Rest der Seite gesperrt.
  2. close Schließt einen Dialog sofort, ohne Rückfrage.
  3. request-close Bittet den Dialog, sich zu schließen. Vorher kommt ein cancel-Ereignis, das ein Skript abfangen kann.
  4. show-popover, hide-popover, toggle-popover Dasselbe für Elemente mit dem popover-Attribut, als Ersatz für popovertarget und popovertargetaction.
  5. --eigener-name Löst am Ziel ein command-Ereignis aus. Was dann passiert, entscheidet dein Skript.
<button command="--next" commandfor="gallery">Next image</button> gallery.addEventListener('command', (event) => {  if (event.command === '--next') showNext();});
Ein eigener Befehl. Der Button bleibt im Markup, das Skript sitzt am Ziel.
04

Wie schließt sich ein Dialog per Klick daneben?

Mit closedby="any" am Dialog. Das Attribut legt fest, auf welchen Wegen der Dialog sich schließen lässt, und kennt drei Werte. any erlaubt alles, auch den Klick auf den Hintergrund. closerequest erlaubt den Weg der Plattform, also Escape, und Schließen durch den Code, aber keinen Klick daneben. none erlaubt nur Schließen durch den Code oder einen Button.

Ohne Angabe verhält sich ein modaler Dialog wie closerequest und ein nicht modaler wie none. Das erklärt, warum ein Klick auf den Hintergrund bisher nichts tat: Es war nie vorgesehen. Für einen Dialog, der nur informiert, ist any meist richtig. Für einen, in dem jemand gerade ein Formular ausfüllt, eher nicht, denn ein versehentlicher Klick daneben wirft dann alles weg.

Chrome und Edge kennen closedby seit Version 134 und Firefox seit 141. Safari unterstützt es laut caniuse bisher nur in der Technology Preview. Das Attribut gehört zum Schwerpunkt Dialoge und Popover von Interop 2026, zusammen mit der Pseudo-Klasse :open und popover="hint".

<dialog id="notice" closedby="any">  <p>Your changes were saved.</p>  <button command="close" commandfor="notice">OK</button></dialog>
Ein Hinweisdialog, der sich auf jedem Weg schließen lässt.
Der Button zum Schließen bleibt PflichtDer Klick daneben ist eine Abkürzung, kein Ersatz. Wer mit der Tastatur oder einem Screenreader unterwegs ist, trifft keinen Hintergrund, und in Safari funktioniert der Klick daneben ohne Zusatz noch gar nicht. Jeder Dialog braucht deshalb einen sichtbaren Button mit einem Text, der sagt, was passiert, etwa Schließen oder Fertig, und kein einzelnes X ohne Beschriftung.
05

Was ist der Unterschied zwischen close und request-close?

close schließt sofort, request-close fragt vorher. Bei request-close feuert der Dialog zuerst ein cancel-Ereignis, und erst wenn niemand es verhindert, folgt das Schließen mit dem close-Ereignis. Dasselbe passiert bei Escape und beim Klick daneben. request-close ist das Gegenstück zur Methode requestClose im Skript.

Damit lässt sich die häufigste Rückfrage an einer Stelle lösen: Soll der Dialog wirklich zu, obwohl noch ungespeicherte Änderungen darin stehen? Ein Listener auf cancel prüft das und ruft bei Bedarf preventDefault auf. Weil Escape, Klick daneben und der Abbrechen-Button denselben Weg nehmen, gibt es nur eine Prüfung statt drei.

Für den Button, der etwas bestätigt, bleibt close richtig, denn dort soll nichts mehr gefragt werden. Für den, der abbricht, ist request-close die bessere Wahl.

<dialog id="edit" closedby="any">  <form method="dialog">    ...    <button type="button" command="request-close" commandfor="edit">Cancel</button>    <button>Save</button>  </form></dialog> edit.addEventListener('cancel', (event) => {  if (hasUnsavedChanges()) event.preventDefault();});
Abbrechen, Escape und Klick daneben laufen durch dieselbe Prüfung.
close überall
  • Der Abbrechen-Button schließt sofort, auch wenn im Formular noch etwas steht.
  • Escape und Klick daneben brauchen eine eigene Prüfung, die jemand vergisst.
  • Die Rückfrage steht an drei Stellen und weicht irgendwann voneinander ab.
request-close zum Abbrechen
  • Abbrechen, Escape und Klick daneben lösen alle dasselbe cancel-Ereignis aus.
  • Ein einziger Listener entscheidet, ob der Dialog zugehen darf.
  • Der Button zum Speichern schließt mit close, ohne noch einmal zu fragen.
06

Was tun in Browsern ohne Unterstützung?

Für command und commandfor meist nichts mehr, für closedby ein paar Zeilen. Die Befehle laufen seit Safari 26.2 in allen großen Engines, ein Rückfall lohnt sich nur noch, wenn ein spürbarer Teil der Besucher ältere Versionen nutzt. Dann prüft man, ob Buttons die Eigenschaft command kennen, und hängt sonst einen Klick-Listener an, der showModal oder close aufruft.

Bei closedby ist die Lage anders, weil Safari es noch nicht ausliefert. Der Rückfall ist klein: Ein Klick, dessen Ziel der Dialog selbst ist und nicht ein Element darin, kam vom Hintergrund. Dann ruft man requestClose auf, damit derselbe cancel-Weg greift wie in den anderen Browsern. Wichtig ist, dass der Dialog selbst kein Padding hat und der Inhalt in einem eigenen Element sitzt, sonst zählt auch ein Klick auf den Rand des Dialogs als Klick daneben.

Beide Prüfungen laufen nur, wenn das Attribut fehlt. In Chrome, Edge und Firefox bleibt das Markup die einzige Quelle, und wenn Safari closedby ausliefert, wird der Rückfall von selbst wirkungslos.

if (!('closedBy' in HTMLDialogElement.prototype)) {  for (const dialog of document.querySelectorAll('dialog[closedby="any"]')) {    dialog.addEventListener('click', (event) => {      if (event.target === dialog) dialog.requestClose();    });  }}
Nur aktiv, wenn der Browser closedby nicht kennt.

Erst das Markup, das in drei Engines trägt. Dann die Zeilen für die vierte.

Teilen
Geschrieben von
Leonie RoesmannDesign Engineering

Wir bauen Websites und Web-Apps bei Systra Studios in Münster und legen die Bausteine daraus hier ab. Was in den Beiträgen steht, kommt aus echten Projekten, nicht aus einem Werbeprospekt.

Systra Studios: Webdesign aus Münster

Ein Button öffnet, ein Button schließt.
Die Menüs dazu stehen bereit.

Navigationen, die sich als Fläche über die Seite legen: vom kleinen Menü aus Glas bis zum Vollbild. Kopieren und das Öffnen und Schließen auf command und commandfor umstellen, wo der Browser es kann.

  • Eine Datei je Komponente, als Quelltext im eigenen Projekt.
  • Gebaut mit React, TypeScript und Tailwind CSS v4.
  • Reduzierte Bewegung ist in jeder Komponente berücksichtigt.
Navigationen ansehen
FAQ

Häufige Fragen

Für neue Projekte ja, popovertarget funktioniert aber weiter. Als Chrome die beiden Attribute in Version 135 eingeführt hat, beschrieb das Team sie ausdrücklich als Nachfolger von popovertarget und popovertargetaction. Der Unterschied liegt in der Reichweite: popovertarget kann nur Popover steuern, commandfor jedes Element, das einen Befehl versteht, also auch Dialoge und eigene Komponenten. Für ein Popover schreibt man statt popovertarget="menu" und popovertargetaction="toggle" einfach commandfor="menu" und command="toggle-popover". Wer bestehende Popover nicht anfassen will, muss es nicht: Beide Wege bestehen nebeneinander, und ein Projekt kann sie mischen, solange ein Button nur einen davon benutzt.

Safari ignoriert das Attribut, und der Dialog verhält sich wie ohne. Laut caniuse unterstützt Safari closedby bisher nur in der Technology Preview, die Versionen 26.6 bis 27.2 nicht. Ein modaler Dialog schließt dort also weiter mit Escape, aber nicht mit einem Klick auf den Hintergrund. Das ist kein Fehler, den jemand bemerkt, solange es einen sichtbaren Button zum Schließen gibt, und den sollte jeder Dialog ohnehin haben. Wer den Klick daneben auch in Safari will, ergänzt einen Listener, der prüft, ob der Klick auf den Dialog selbst und nicht auf seinen Inhalt ging, und lädt ihn nur, wenn der Browser closedby nicht kennt. closedby gehört zum Schwerpunkt Dialoge und Popover von Interop 2026, an dem auch Apple beteiligt ist.

Ja, als gewöhnliche Attribute im JSX. React reicht unbekannte Attribute mit Zeichenketten als Wert an das DOM weiter, und weil HTML-Attribute nicht zwischen Groß- und Kleinschreibung unterscheiden, kommen commandfor und commandFor gleich an. Der Vorteil in React ist größer als anderswo: Ein Dialog, der nur über showModal aufging, brauchte einen Ref auf das Element und eine Funktion im Klick-Handler, oft dazu einen Zustand für offen oder geschlossen. Mit commandfor entfällt alles drei, und der Button kann sogar in einer anderen Komponente stehen als der Dialog, solange beide im selben Dokument liegen und die id eindeutig ist. Ob TypeScript die Attribute ohne Meldung annimmt, hängt von der Version der React-Typen ab; fehlen sie, ergänzt man sie in einer eigenen Deklaration.

Wenn der Dialog etwas tun soll, das über Öffnen und Schließen hinausgeht. Das sind vor allem drei Fälle. Erstens das Absenden eines Formulars im Dialog, wenn die Seite dabei nicht neu laden soll; ein Formular mit method="dialog" schließt den Dialog zwar von selbst, aber das Speichern übernimmt erst ein Skript. Zweitens die Rückfrage vor dem Schließen, etwa bei ungespeicherten Änderungen; dafür hört man auf das cancel-Ereignis, das request-close vor dem Schließen auslöst, und verhindert es bei Bedarf. Drittens eigene Befehle mit zwei Bindestrichen, die selbst nichts tun und nur ein command-Ereignis am Ziel auslösen. In allen drei Fällen bleibt das Öffnen im Markup, und das Skript beschränkt sich auf das, was wirklich eigen ist.

Deine Frage war nicht dabei? Wir helfen gern weiter.

Kontakt aufnehmen