Stockio

@starting-style: Animate Dialogs and Popovers in CSS

Create reliable entry and exit transitions for native dialogs and popovers, including discrete display changes, top-layer behavior, fallbacks, and motion preferences.

By Aiko Tanaka

Motion Designer ·

On this page

Native dialogs and popovers solve important platform problems, but animating them requires more than fading opacity. An element entering from display: none has no ordinary rendered previous state. An element leaving may disappear before its exit animation finishes. Modern CSS provides separate mechanisms for those two problems, letting the platform retain control of visibility and top-layer behavior.

Establish the surface and lifecycle first

Create the static surface before working on movement. Use the box shadow generator to choose a restrained elevation for a popover or dialog, then test it over both bright and dark page regions. Keep the shadow static during the transition initially; changing blur and spread adds paint work without clarifying the open state.

Choose the right primitive. A modal dialog opened with showModal participates in modal interaction behavior. A popover shown with showPopover uses the top layer but is not inherently modal. Animation does not change those semantics. A tooltip, menu, and confirmation dialog should not become interchangeable just because their surfaces look similar.

Define entry and exit separately. Entry can use a small upward settle, while exit can simply fade. Keep both short enough that repeated opening does not feel ceremonial. A starting point around 160 to 200 milliseconds is reasonable for a compact surface, but validate the feeling in the actual workflow rather than treating a number as a standard.

Understand the two CSS mechanisms

An ordinary transition needs a before-change style and an after-change style. When an element becomes rendered, there may be no useful before-change style. @starting-style supplies that initial style for entry, such as zero opacity and a small translation. It is not a permanent hidden state and does not replace the open selector.

The MDN @starting-style reference explains when starting styles participate. Source order still matters. Place the starting-style rule after the normal open rule when selectors have equal specificity, so its entry values win during the starting-style calculation.

transition-behavior: allow-discrete permits transitions for discrete properties such as display. Display has special transition behavior: when becoming visible it switches at the start, and when becoming none it switches at the end. This keeps the element rendered during its visual exit rather than gradually interpolating a property that has no intermediate display values.

The overlay property is another piece. Including it in the transition list allows removal from the top layer to be deferred during the exit. Authors do not set overlay directly here; the browser controls it. Consult MDN on transition-behavior for the discrete-transition model and supported syntax.

Build a dialog transition around native methods

This example uses a regular dialog element and a native form submission to close it. The CSS is responsible for presentation; JavaScript only asks the platform to open the dialog. Load the script after the markup or use a module.

<button id="open-settings">Open settings</button>
<dialog id="settings" aria-labelledby="settings-title">
<h2 id="settings-title">Settings</h2>
<p>Choose your preferences.</p>
<form method="dialog">
  <button value="done">Done</button>
</form>
</dialog>
const dialog = document.querySelector("#settings");
document.querySelector("#open-settings").addEventListener("click", () => {
if (!dialog.open) dialog.showModal();
});
#settings {
opacity: 0;
transform: translateY(6px);
transition: opacity 180ms ease-out, transform 180ms ease-out;
transition: opacity 180ms ease-out, transform 180ms ease-out,
            display 180ms allow-discrete,
            overlay 180ms allow-discrete;
}

#settings[open] {
opacity: 1;
transform: translateY(0);
}

@starting-style {
#settings[open] {
  opacity: 0;
  transform: translateY(6px);
}
}

#settings::backdrop {
background: rgb(0 0 0 / 0);
transition: background-color 180ms ease-out,
            display 180ms allow-discrete,
            overlay 180ms allow-discrete;
}

#settings[open]::backdrop {
background: rgb(0 0 0 / .35);
}

@starting-style {
#settings[open]::backdrop {
  background: rgb(0 0 0 / 0);
}
}

@media (prefers-reduced-motion: reduce) {
#settings,
#settings::backdrop { transition: none; }
#settings { transform: none; }
}

The first transition declaration is a fallback for engines that reject the newer shorthand values. Unsupported entry or exit animation should degrade to an immediate visibility change, not a broken control. Do not override the dialog's closed display behavior with a global display: grid rule; apply internal layout to a wrapper or restrict it to the open state.

Adapt the same lifecycle to popovers

A popover uses the :popover-open state instead of the dialog's open attribute selector. Apply the visible opacity and transform there, then repeat that selector inside starting-style. Keep display and overlay in the transition list so a closing popover can remain visually present during its exit.

Use declarative popovertarget buttons where possible. They provide a straightforward relationship between trigger and surface without creating a separate JavaScript visibility state. The MDN Popover API guide describes automatic popovers, light dismissal, and related behavior.

Do not reuse a large dialog's motion distance for a small anchored popover. A few pixels of movement near the trigger can reinforce the relationship; a long slide can make the surface appear detached. Ensure placement logic remains stable while the transform runs. A visually animated offset should not become a second source of truth for the popover's final layout coordinates.

Decide whether the backdrop needs animation at all. A nonmodal popover often has no dimming backdrop, while a modal dialog may benefit from one. Matching durations can make the surface and backdrop feel coordinated without requiring a complex sequence.

Test closing behavior and graceful degradation

Exercise the full native lifecycle: trigger activation, close button, Escape, and any applicable light dismissal. Do not intercept every close path simply to run a timeout. The point of discrete transitions is to avoid manually synchronizing DOM removal with a guessed animation duration.

Verify where focus moves when a modal closes. The visually exiting surface should not be treated as an ongoing modal interaction by application code. Avoid holding business logic until transitionend, which can fail to fire if transitions are canceled, disabled, or absent. Save data in response to the actual action, not the last painted frame.

Test reopening during exit and repeated trigger activation. Check both the surface and backdrop for flashes, stale styles, or lingering top-layer behavior in your supported browsers. Include reduced motion and a browser without the new transition features in the acceptance matrix.

Keep the broader accessible interface checklist nearby: a smooth entrance does not supply a name, a sensible initial focus target, or understandable controls. Review related interaction patterns in micro-interactions, but retain the platform's semantics as the foundation. If the polished transition cannot be supported reliably, ship the immediate native interaction rather than a complicated imitation.

Key takeaways

  • Use starting styles to provide entry values for newly rendered surfaces.
  • Allow discrete display and overlay transitions for graceful exits.
  • Keep native opening, closing, and focus behavior in charge.
  • Animate backdrops explicitly when their lifecycle needs coordination.
  • Test interruption and unsupported browsers, not just the happy-path opening.
Featured sponsor

Reach designers & developers at work

Put your product next to the tools people use every day. One sponsor per placement, no tracking pixels.

Become a sponsor

Keep reading

Related articles

More in Motion & Animation
Motion & Animation

12 Best CSS Animation Generators and Resources

Compare twelve CSS animation references, visual generators and libraries, from easing curve playgrounds to ready-made effects, and find a sensible fit for your web project.

Includes: Box shadowsAiko TanakaAiko Tanaka
Motion & Animation

Micro-Interactions in UX: Small Details That Matter

Design responsive button, form, and save-state feedback with accessible motion, clear status messages, and a repeatable micro-interaction test plan.

Includes: Box shadowsAiko TanakaAiko Tanaka
Motion & Animation

CSS Transition vs Animation: When to Use Keyframes

Choose CSS transitions or keyframes for each motion task, handle interrupted states, and build effects that stay predictable across input methods and reduced-motion settings.

Includes: Box shadowsAiko TanakaAiko Tanaka
Motion & Animation

CSS Hover Effects for Cards and Buttons: Tasteful Defaults

Build restrained hover effects that clarify clickable cards and buttons, with practical CSS timings, keyboard states, touch fallbacks, and reduced-motion rules.

Includes: Box shadowsAiko TanakaAiko Tanaka
HTML & CSS

12 Best CSS Generators That Save Hours

Create gradients, shadows, layouts, and other CSS faster with twelve practical generators, plus advice on checking the code and adapting it to your own design.

Includes: Box shadowsJonah WeissJonah Weiss
UI Design

15 Best Free UI Kits for Figma

Explore fifteen useful free Figma UI kits for websites, apps, and prototypes, with a quick guide to choosing a kit that suits your project and avoids unnecessary setup.

Includes: Box shadowsTheo OkaforTheo Okafor