Closing a dialog with a required form input

When a form inside a dialog has a required input, the user agent will only let you close the dialog once you provide a value for the required input. To close such dialog, either use the formnovalidate attribute on the close button or call the close() method on the dialog object when the close button is clicked.

html

  
    

JavaScript

js
const showBtn = document.getElementById("show-dialog");
const dialog = document.getElementById("dialog");
const jsCloseBtn = dialog.querySelector("#js-close");

showBtn.addEventListener("click", () => {
  dialog.showModal();
});

jsCloseBtn.addEventListener("click", (e) => {
  e.preventDefault();
  dialog.close();
});

Result

From the output, we see it is impossible to close the dialog using the Normal close button. But the dialog can be closed if we bypass the form validation using the formnovalidate attribute on the Cancel button. Programmatically, dialog.close() will also close such dialog.

Comparison of different closedby behaviors

This example demonstrates the difference in behavior between different values of the closedby attribute.

HTML

We provide three

closedby="none"

Only closable using a specific provided mechanism, which in this case is pressing the "Close" button below.

closedby="closerequest"

Closable using the "Close" button or the Esc key.

closedby="any"

Closable using the "Close" button, the Esc key, or by clicking outside the dialog. "Light dismiss" behavior.

JavaScript

Here we assign different variables to reference the main control

Animating dialogs

s are set to display: none; when hidden and display: block; when shown, as well as being removed from / added to the top layer and the accessibility tree. Therefore, for elements to be animated the display property needs to be animatable. Supporting browsers animate display with a variation on the discrete animation type. Specifically, the browser will flip between none and another value of display so that the animated content is shown for the entire animation duration.

So for example:

  • When animating display from none to block (or another visible display value), the value will flip to block at 0% of the animation duration so it is visible throughout.
  • When animating display from block (or another visible display value) to none, the value will flip to none at 100% of the animation duration so it is visible throughout.

Note: When animating using CSS transitions, transition-behavior: allow-discrete needs to be set to enable the above behavior. This behavior is available by default when animating with CSS animations; an equivalent step is not required.

Transitioning dialog elements

When animating

s with CSS transitions, the following features are required:

@starting-style at-rule

Provides a set of starting values for properties set on the

that you want to transition from every time it is opened. This is needed to avoid unexpected behavior. By default, CSS transitions only occur when a property changes from one value to another on a visible element; they are not triggered on elements' first style updates, or when the display type changes from none to another type.

display property

Add display to the transitions list so that the

will remain as display: block (or another visible display value set on the dialog's open state) for the duration of the transition, ensuring the other transitions are visible.

overlay property

Include overlay in the transitions list to ensure the removal of the

from the top layer is deferred until the transition completes, again ensuring the transition is visible.

transition-behavior property

Set transition-behavior: allow-discrete on the display and overlay transitions (or on the transition shorthand) to enable discrete transitions on these two properties that are not by default animatable.

Here is a quick example to show what this might look like.

HTML

The HTML contains a

element, plus a button to show the dialog. Additionally, the element contains another button to close itself.

html

  Content here
  



CSS

In the CSS, we include a @starting-style block that defines the transition starting styles for the opacity and transform properties, transition end styles on the dialog:open state, and default styles on the default dialog state to transition back to once the

has appeared. Note how the 's transition list includes not only these properties, but also the display and overlay properties, each with allow-discrete set on them.

We also set a starting style value for the background-color property on the ::backdrop that appears behind the

when it opens, to provide a nice darkening animation. The dialog:open::backdrop selector selects only the backdrops of elements when the dialog is open.

css
/* Open state of the dialog  */
dialog:open {
  opacity: 1;
  transform: scaleY(1);
}

/* Closed state of the dialog   */
dialog {
  opacity: 0;
  transform: scaleY(0);
  transition:
    opacity 0.7s ease-out,
    transform 0.7s ease-out,
    overlay 0.7s ease-out allow-discrete,
    display 0.7s ease-out allow-discrete;
  /* Equivalent to
  transition: all 0.7s allow-discrete; */
}

/* Before open state  */
/* Needs to be after the previous dialog:open rule to take effect,
    as the specificity is the same */
@starting-style {
  dialog:open {
    opacity: 0;
    transform: scaleY(0);
  }
}

/* Transition the :backdrop when the dialog modal is promoted to the top layer */
dialog::backdrop {
  background-color: rgb(0 0 0 / 0%);
  transition:
    display 0.7s allow-discrete,
    overlay 0.7s allow-discrete,
    background-color 0.7s;
  /* Equivalent to
  transition: all 0.7s allow-discrete; */
}

dialog:open::backdrop {
  background-color: rgb(0 0 0 / 25%);
}

/* This starting-style rule cannot be nested inside the above selector
because the nesting selector cannot represent pseudo-elements. */

@starting-style {
  dialog:open::backdrop {
    background-color: rgb(0 0 0 / 0%);
  }
}

Note: In browsers that don't support the :open pseudo-class, you can use the attribute selector dialog[open] to style the

element when it is in the open state.

JavaScript

The JavaScript adds event handlers to the show and close buttons causing them to show and close the

when they are clicked:

js
const dialogElem = document.getElementById("dialog");
const showBtn = document.querySelector(".show");
const closeBtn = document.querySelector(".close");

showBtn.addEventListener("click", () => {
  dialogElem.showModal();
});

closeBtn.addEventListener("click", () => {
  dialogElem.close();
});
Result

The code renders as follows:

Note: Because

s change from display: none to display: block each time they are shown, the transitions from its @starting-style styles to its dialog:open styles every time the entry transition occurs. When the closes, it transitions from its dialog:open state to the default dialog state.

It is possible for the style transition on entry and exit to be different in such cases. See our Demonstration of when starting styles are used example for a proof of this.

dialog keyframe animations

When animating a

with CSS keyframe animations, there are some differences to note from transitions:

  • You don't provide a @starting-style.
  • You include the display value in a keyframe; this will be the display value for the entirety of the animation, or until another non-none display value is encountered.
  • You don't need to explicitly enable discrete animations; there is no equivalent to allow-discrete inside keyframes.
  • You don't need to set overlay inside keyframes either; the display animation handles the animation of the from shown to hidden.

Let's have a look at an example so you can see what this looks like.

HTML

First, the HTML contains a

element, plus a button to show the dialog. Additionally, the element contains another button to close itself.

html

  Content here
  



CSS

The CSS defines keyframes to animate between the closed and shown states of the

, plus the fade-in animation for the 's backdrop. The animations include animating display to make sure the actual visible animation effects remain visible for the whole duration. Note that it wasn't possible to animate the backdrop fade out — the backdrop is immediately removed from the DOM when the is closed, so there is nothing to animate.

css
dialog {
  animation: fade-out 0.7s ease-out;
}

dialog:open {
  animation: fade-in 0.7s ease-out;
}

dialog:open::backdrop {
  animation: backdrop-fade-in 0.7s ease-out forwards;
}

/* Animation keyframes */

@keyframes fade-in {
  0% {
    opacity: 0;
    transform: scaleY(0);
    display: none;
  }

  100% {
    opacity: 1;
    transform: scaleY(1);
    display: block;
  }
}

@keyframes fade-out {
  0% {
    opacity: 1;
    transform: scaleY(1);
    display: block;
  }

  100% {
    opacity: 0;
    transform: scaleY(0);
    display: none;
  }
}

@keyframes backdrop-fade-in {
  0% {
    background-color: rgb(0 0 0 / 0%);
  }

  100% {
    background-color: rgb(0 0 0 / 25%);
  }
}

body,
button {
  font-family: system-ui;
}
JavaScript

Finally, the JavaScript adds event handlers to the buttons to enable showing and closing the

:

js
const dialogElem = document.getElementById("dialog");
const showBtn = document.querySelector(".show");
const closeBtn = document.querySelector(".close");

showBtn.addEventListener("click", () => {
  dialogElem.showModal();
});

closeBtn.addEventListener("click", () => {
  dialogElem.close();
});
Result

The code renders as follows:

Technical summary

Content categories Flow content, sectioning root
Permitted content Flow content
Tag omission None, both the starting and ending tag are mandatory.
Permitted parents Any element that accepts flow content
Implicit ARIA role dialog
Permitted ARIA roles alertdialog
DOM interface HTMLDialogElement

Specifications

Specification
HTML
# the-dialog-element

Browser compatibility

See also