Encodera

Thanks for reading!

Donate to the site — we work hard for you.

How to animate opening a dialog or popover

Short answer

Describe the open state, the starting state with @starting-style, and allow display to animate:

<style>
  dialog { opacity: 0; translate: 0 20px;
    transition: opacity 0.3s, translate 0.3s, display 0.3s allow-discrete, overlay 0.3s allow-discrete; }
  dialog[open] { opacity: 1; translate: 0 0; }
  @starting-style { dialog[open] { opacity: 0; translate: 0 20px; } }
</style>
 
<button onclick="hello.showModal()">Open</button>
<dialog id="hello">
  <p>Hello! This window fades in.</p>
  <form method="dialog"><button>Close</button></form>
</dialog>

How it works:

  • @starting-style sets the style the element starts its transition from when it appears. Without it the browser has nothing to animate from: the element was display: none.
  • allow-discrete on display delays hiding until the transition ends, so closing is smooth too.
  • overlay keeps the dialog in the top layer while the closing animation runs.

A popover

For popover everything is the same, only the open state is the :popover-open pseudo-class:

<style>
  [popover] { opacity: 0; scale: 0.9;
    transition: opacity 0.25s, scale 0.25s, display 0.25s allow-discrete, overlay 0.25s allow-discrete; }
  [popover]:popover-open { opacity: 1; scale: 1; }
  @starting-style { [popover]:popover-open { opacity: 0; scale: 0.9; } }
</style>
 
<button popovertarget="menu">Menu</button>
<div id="menu" popover>Settings · Profile · Log out</div>

The backdrop

::backdrop is animated the same way: give it a transparent background by default, a semi-transparent one for dialog[open]::backdrop, the same transition and a starting state in @starting-style.

Common mistake

Putting @starting-style before the open-state rule with the same specificity. The @starting-style block does not raise specificity, and the rule below it wins — the entry animation disappears. Write @starting-style after the main styles, as in the examples. In browsers without support the window simply opens without animation.

Functions used in this recipe

  • @starting-style — Sets the styles an element starts from when it first appears, so a `transition` can animate its entrance.
  • transition-behavior — Allows transitions of discrete properties such as `display` and `overlay`, so an element can fade out before it disappears.
  • <dialog> — A dialog box; a modal one is opened with `showModal()` and blocks the rest of the page.
  • popover — Turns an element into a layer that pops up over the page — without JavaScript.
  • transition — Animates the change of a property from the old value to the new one.

Thanks for reading!

Donate to the site — we work hard for you.