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
- Pick your popovers and dialogs first: they already have app-controlled open states, so the win is immediate.
- Write the resting styles, then add the starting block with only the properties you will transition.
- Add transition-behavior: allow-discrete to any display transition, or the close animation will snap.
- 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.
