CSS transitions run when a property changes between two rendered states. They never ran on the way in, because an element's first frame arrives already final; so every open animation on the web lived in keyframe animation or JavaScript. at-rules fixed the missing half.

dialog {
  opacity: 1;
  translate: 0 0;
  transition: opacity 0.3s, translate 0.3s;
}

dialog[open] {
  @starting-style {
    opacity: 0;
    translate: 0 1rem;
  }
}

Why this beat keyframes

  • The element's first frame gets the starting values; the transition property then animates into the open state with the same easing you already use.
  • It composes with the display and popover transitions: display transitions let an element animate from display:none, which used to hard-block transitions.
  • The exit uses the regular transition rules, so entry and exit share one source of truth instead of one animation and one reverse hack.
  • Wrap the whole thing in a prefers-reduced-motion media query and the animation is disabled for readers who opted out.

Adoption recipe

  1. Pick your popovers and dialogs first: they already have app-controlled open states, so the win is immediate.
  2. Write the resting styles, then add the starting block with only the properties you will transition.
  3. Add transition-behavior: allow-discrete to any display transition, or the close animation will snap.
  4. Test keyboard, Escape, and reduced motion; the platform has one polish gap at most, and it is your QA to name.

Between dialog, popover, and starting styles, entry animation is now three declarations on a static page. This site's menu opens with exactly such a transition, and the fact that no script knows about it is gacor by design.