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.
- 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.
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.
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>
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.
- show-modal Öffnet einen Dialog modal, wie showModal: oberste Ebene, Hintergrund, Rest der Seite gesperrt.
- close Schließt einen Dialog sofort, ohne Rückfrage.
- request-close Bittet den Dialog, sich zu schließen. Vorher kommt ein cancel-Ereignis, das ein Skript abfangen kann.
- show-popover, hide-popover, toggle-popover Dasselbe für Elemente mit dem popover-Attribut, als Ersatz für popovertarget und popovertargetaction.
- --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();});
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>
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();});
- 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.
- 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.
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(); }); }}
Erst das Markup, das in drei Engines trägt. Dann die Zeilen für die vierte.






