Speed
Slow motion multiplier
1×
Reduced motion
Show

The exit gap

conflicts with the published page
Eleven components animate in and vanish out — every surface built on useLayer, plus Dialog, AlertDialog and CommandPalette. One change to layerAnimations and useLayer would give all eleven a dismissal animation, which is why the brief calls it the highest-leverage fix in the audit.Decide here: Whether presence surfaces should animate out at all, and what enter and exit cost.

Tune the presence budget

These two values drive every pane on this page, and every layer demo in the lab.
enter
230ms
exit
175ms
overlay
410ms
Exit is deliberately shorter than enter, because old content should leave faster than new content arrives. Drag them equal and a dismissal starts to feel sticky.

Tooltip

useLayer · highest-frequency surface in the system
The published page names tooltips specifically as a surface that may vanish. Does the exit earn its 175ms here, on something seen hundreds of times a day?
Today — animates in, vanishes out
Tooltip
Proposed — the exit retraces the entry
Tooltip

HoverCard

useLayer · the most visible instance of the gap
A larger surface travelling the same distance. Judge the scale-from value here rather than on Tooltip — 0.96 reads differently at size.
Today — animates in, vanishes out
HoverCard
Proposed — the exit retraces the entry
HoverCard

Popover

useLayer
Click twice quickly. The exit has to retarget rather than restart, or rapid dismissal looks broken.
Today — animates in, vanishes out
Popover
Proposed — the exit retraces the entry
Popover

DropdownMenu

useLayer · submenus too
A menu snapping shut is the case people notice. Does the exit help, or does it just delay the next click?
Today — animates in, vanishes out
DropdownMenu
Proposed — the exit retraces the entry
DropdownMenu

Dialog, AlertDialog, CommandPalette

focus races the exit
Two separate bugs in one surface: the panel has no exit, and the backdrop has no transition at all. Watch the focus ring on the trigger.
Today — no exit, scrim hard-cuts, focus returns immediately
Delete workspace?This cannot be undone.
Proposed — panel and scrim both exit, focus waits
Delete workspace?This cannot be undone.
In the left pane the focus ring lights the instant the dialog closes, while the dialog is still on screen. With a real exit animation that means focus moves — and the page may scroll — mid-animation. The brief lists this as a risk; here it is a thing you can watch, and it has to be settled before the Dialog work starts rather than after.

The technique, running natively in this browser

Is the platform enough, or does the shared primitive need a JS fallback? This card answers it for real — nothing here is simulated.
A native popover transitioning display and overlay with allow-discrete, entering from @starting-style. This is the proposed implementation, not a mock-up of it.
Green across the row means the shared presence primitive can be built on the platform with no JS fallback, which is the question the spike exists to answer.

What one change buys

Every component below is a useLayer consumer, so all of them inherit a dismissal animation from a single definition. That is the leverage — and it is also the risk, because it is one change to the code path that owns dismissal.
TooltipHoverCardPopoverDropdownMenuContextMenuSelectorComplexSelectorPowerSearchTypeaheadMoreMenuBreadcrumbs overflow
layerAnimations — one definition, eleven consumerscss
.layer {
opacity: 0;
transform: scale(0.96) translateY(-4px);
transform-origin: var(--layer-origin);
transition:
opacity var(--duration-exit) var(--ease-exit),
transform var(--duration-exit) var(--ease-exit),
display var(--duration-exit) allow-discrete,
overlay var(--duration-exit) allow-discrete;
}
.layer:popover-open {
opacity: 1;
transform: scale(1) translateY(0);
transition:
opacity var(--duration-enter) var(--ease-entry),
transform var(--duration-enter) var(--ease-entry),
display var(--duration-enter) allow-discrete,
overlay var(--duration-enter) allow-discrete;
}
@starting-style {
.layer:popover-open { opacity: 0; transform: scale(0.96) translateY(-4px); }
}
useLayer — await the exit instead of hiding synchronouslytsx
- setOpen(false); // element gone this frame
+ setClosing(true); // stops accepting input immediately
+ await Promise.all(
+ el.getAnimations().map(a => a.finished),
+ );
+ setOpen(false); // focus return happens here

What this work has to be careful about

Dismissal is load-bearingLayer dismissal has its own invariants test for good reason. Keeping an element alive through its exit doubles the state matrix for focus return, outside-press and escape handling — and every one of those has to be tested against an element that is visible but already closing.
The published page already warns about thisAnimations that block interaction are worse. If a user has to wait for a panel to finish sliding before they can click something inside it, the animation has become an obstacle.” The answer has to be that the surface stops accepting input the moment dismissal starts, not when the animation ends. Write that into the primitive’s contract rather than leaving it to each consumer.
Motion is already fighting the automated gatesTwo fixes landed this month to stop animation interfering with the accessibility audit and the visual-regression capture. More motion means more of this, and there is no documented policy for it. The lab’s reduced-motion switch doubles as the freeze mode a capture needs.