A popover that slides in instead of just appearing takes three CSS rules and zero JavaScript, and that part works in Chrome, Firefox and Safari. Making it slide out again is a different story, and while preparing this note I found out it's messier than the "Baseline" label suggests. In the demo above, open the popover and the dialog, add and remove cards, and turn @starting-style off to see the difference. The chips at the top tell you what your browser can do.
The problem it solves
MDN puts it clearly: CSS transitions are not triggered on an element's first style update, or when its display changes from none to something else. That's on purpose, but it means a popover, a <dialog> or a node you've just appended has no "before" value to transition from. It simply appears.
@starting-style gives it one. It defines the values an element transitions from when it receives its first style update: when it's inserted into the DOM or goes from display: none to visible. Exits are the other half, and they need transition-behavior: allow-discrete.
How it works
This is the core of the popover in the demo:
/* 1. Where it ends up when open */
.pop:popover-open { opacity: 1; transform: none; }
/* 2. Closed state: where the exit goes */
.pop {
opacity: 0;
transform: translateY(28px) scale(.9);
transition:
opacity .5s, transform .5s,
overlay .5s allow-discrete,
display .5s allow-discrete;
}
/* 3. Where it comes from on the first frame */
@starting-style {
.pop:popover-open { opacity: 0; transform: translateY(28px) scale(.9); }
}
There are three states, and that's the key idea. The closed state, the open state and the starting state. MDN shows they don't have to be the same: an element enters from its @starting-style values and leaves towards its default values. In the demo they match, so it comes and goes the same way.
The pieces:
@starting-styleonly applies on that first frame. After that it's ignored, which is why closing doesn't go "back" to it.- Order matters. According to MDN, the
@starting-stylerule and the original rule have the same specificity, so put@starting-styleafter it or the original wins. You can also nest it inside the rule, as I do with the cards:.item { @starting-style { opacity: 0; } }. - Pseudo-elements need a standalone block. Nesting doesn't work for them, so the dialog's backdrop uses
@starting-style { dialog[open]::backdrop { ... } }on its own. display ... allow-discreteis what makes the exit visible.displayis a discrete property: by default it doesn't transition at all. Withallow-discrete, MDN explains that going fromnonetoblockflips at 0% of the duration and going tononeflips at 100%, so the element stays visible for the whole animation. The initial value oftransition-behaviorisnormal.overlayis for elements in the top layer (open popovers, modal dialogs). Authors can't set it; the browser does. Adding it to the transition list defers the removal from the top layer until the animation ends.- Only for transitions. If you use
@keyframesanimations for the entry, you don't need@starting-style.
The cards panel uses the same rule for nodes created with appendChild(). Removing them is the classic pattern: add a class that animates to the "from" state and call remove() when it ends.
The log panel lists the transitionrun events the browser fires. In Chromium, closing the popover shows display and overlay in that list. Elsewhere you'll see a different story.
Browser support and gotchas
MDN marks @starting-style as Baseline 2024, available since August 2024. Its compatibility data lists Chrome 117, Firefox 129 and Safari 17.5. The entry animation is the safe part.
The exit is not:
- Firefox doesn't transition
displayyet. MDN's compatibility data marks it as unsupported in Firefox, and itstransition-behaviorsupport as partial for that reason. The work landed in Mozilla's code this summer, but behind a preference,layout.css.display-animations.enabled, which is stillfalseby default in the source. On October 13, 2026, a Mozilla engineer noted in the tracking bug that this would move thetransition-behaviorfeature from "Newly Available" back to "Limited Availability". The MDN page fortransition-behavioralready shows "Limited availability". - Safari transitions
display, but not out of the top layer. MDN lists Safari 18 fordisplaytransitions, andoverlayas Chromium-only (and experimental). In the Playwright WebKit build I test the demo with, a plaindivfades out fine, while the popover and the dialog vanish the moment they close. WebKit's bug to implementoverlayand the new top-layer exit algorithm is still open. - You can't detect it with
@supports.CSS.supports('transition-behavior', 'allow-discrete')returnstruein all three browsers I tested with, even where the exit doesn't work. The demo runs a small runtime check, adapted from one posted in Mozilla's bug:
function supportsDisplayTransition() {
const div = document.createElement('div');
div.style.transition = 'display 1s allow-discrete';
document.body.append(div);
const cs = getComputedStyle(div);
cs.display; // flush styles
div.style.display = 'none';
const ok = cs.display !== 'none'; // still visible = it's transitioning
div.remove();
return ok;
}
const nativeExit = supportsDisplayTransition() && CSS.supports('overlay', 'auto');
If nativeExit is false, the demo's Close buttons, and Esc in the dialog, animate the element with Element.animate() while it's still open and only then call hidePopover() or close(). Light-dismissing the popover (clicking outside) stays instant in those browsers; there's no way to delay it. My first plan B used a CSS class instead of Element.animate(), and in WebKit the transition sometimes didn't start when triggered by Esc. The Web Animations version was reliable in all three.
- Motion. With
prefers-reduced-motion, the demo keeps the fades and drops the transforms and blur.
What to keep an eye on
- Firefox flipping the preference. Once
layout.css.display-animations.enabledis on by default, exits will work there with the same CSS. overlayin WebKit. Until then, Safari will keep cutting top-layer exits short.- The Baseline status of
transition-behavior. If it drops to "Limited", tools that rely on web-features data will start flagging it. Entry animations with@starting-styleare unaffected.