Website Spec
FoundationsOptionalUpdated

Entry and exit animations

Animate elements as they appear and disappear — including to and from display: none and the top layer — with @starting-style and transition-behavior: allow-discrete, no JavaScript timers.

What it is

Two CSS features let an element animate as it appears and disappears — including transitions to and from display: none and the browser top layer that popovers and <dialog> render in.

  • @starting-style declares the styles an element starts from the first time it is rendered (or re-added to the top layer). Without a “before” value the browser has nothing to transition out of, so the element simply snaps in.
  • transition-behavior: allow-discrete lets discrete properties such as display and overlay take part in a transition. The browser defers display: none until the animation finishes instead of cutting the element instantly.
[popover] {
  opacity: 0;
  transition:
    opacity 200ms ease,
    display 200ms allow-discrete;
}
[popover]:popover-open {
  opacity: 1;
}
@starting-style {
  [popover]:popover-open {
    opacity: 0;
  }
}

Why it matters

  • No JavaScript timing dance. Animating an element in from display: none, or out before removal, used to require toggling classes across requestAnimationFrame, listening for transitionend, or setTimeout to defer removal. This is all declarative now, and the browser owns the timing — fewer race conditions, no orphaned timers.
  • Exit animations work without script. The element stays visible for the duration of the transition, then the browser applies display: none. Popovers and modal dialogs animate out of the top layer the same way, via the overlay property.
  • It composes with the platform. It pairs directly with the Popover API, <dialog>, and invoker commands rather than re-implementing show/hide logic in JavaScript.

How to implement

Put the transition on the base rule, give the visible state its target values, and put the initial state inside @starting-style. Add allow-discrete to any display or overlay transition. Gate the motion behind prefers-reduced-motion so users who ask for less movement get an instant swap.

@media (prefers-reduced-motion: no-preference) {
  .toast {
    transition:
      transform 200ms,
      display 200ms allow-discrete;
  }
  @starting-style {
    .toast.is-open {
      transform: translateY(1rem);
    }
  }
}

Common mistakes

  • Omitting allow-discrete. Without it display flips instantly and the element pops in and out with no transition.
  • A @starting-style rule that does not match the visible state. The starting selector must target the same element and state as the open rule, or there is no “before” value to animate from.
  • Animating unconditionally. Ignoring prefers-reduced-motion forces motion on people who have opted out.

Verification

  • Toggle a popover, dialog, or toast: it animates both in and out rather than snapping.
  • In DevTools, confirm the element keeps its display value until the exit transition ends.
  • With prefers-reduced-motion: reduce active, transitions are suppressed and the change is instant.
  • @starting-style and transition-behavior: allow-discrete have been Baseline (newly available across major browsers) since 2024 — treat the animation as a progressive enhancement.

Related topics

Sources & further reading