position-try Fallbacks and Motion
Part of CSS Anchor Positioning & Motion in Modern View Transitions & Scroll APIs.
The problem
A dropdown menu opens below its button with a pleasant downward slide and a scale from its top edge. Near the bottom of the viewport, anchor positioning flips it above the button — correct placement — but it still slides downward toward the button and scales from its top edge, so it appears to grow away from the thing that opened it. When the page is scrolled so the button sits right at the threshold, the menu occasionally jumps between above and below as layout settles.
Fallbacks solve placement. They do not know anything about motion.
Root cause analysis: fallbacks are chosen in layout
The switch is not a style change. position-try-fallbacks lists alternatives the browser tries when the element overflows its containing block. The choice happens while laying out the element, after styles have been computed. Transitions are triggered by computed style changes, so moving from one fallback to another is not something a transition can see or interpolate. The element simply appears in the new position.
Motion defined on the base rule assumes the base position. An entrance of translate: 0 -8px to 0 0 slides down into place, which is right for a menu below its button and wrong for one above it. transform-origin: top scales from the edge nearest the button when below, and from the far edge when above.
@position-try can carry more than position. A named @position-try --above { … } rule may contain only a limited set of properties: inset properties, margins, sizing, self-alignment and position-anchor and position-area. It cannot set translate or transform-origin. So per-position motion cannot live directly in the fallback.
Flip-dependent motion needs a different signal. Two approaches work. Make motion direction-neutral, so it looks right in any position: a fade with a small uniform scale, transform-origin: center. Or use margin — which is allowed in @position-try — to create a gap, and animate a property that does not depend on direction. Where direction really matters, script can read the resolved position after opening and set a data attribute, at the cost of a frame.
Thrash near the threshold. An element sitting at the edge of fitting can flip as its own size changes — including during a scale entrance, when it is briefly smaller. Transforms do not affect layout, so a scale entrance does not change fallback choice; animating height or width would.
Step-by-step resolution
Production code pattern
.menu-button { anchor-name: --menu; }
.menu {
position: absolute;
inset: auto;
margin: 0;
position-anchor: --menu;
position-area: block-end span-inline-end;
margin-block-start: 6px;
position-try-fallbacks: --above, flip-inline;
opacity: 0;
scale: 0.96; /* direction-neutral */
transition:
opacity var(--motion-exit-duration) linear,
scale var(--motion-exit-duration) var(--motion-exit-easing),
display var(--motion-exit-duration) allow-discrete,
overlay var(--motion-exit-duration) allow-discrete;
}
.menu:popover-open {
opacity: 1;
scale: 1;
transition:
opacity var(--motion-enter-duration) linear,
scale var(--motion-enter-duration) var(--motion-enter-easing),
display var(--motion-enter-duration) allow-discrete,
overlay var(--motion-enter-duration) allow-discrete;
@starting-style { opacity: 0; scale: 0.96; }
}
@position-try --above {
position-area: block-start span-inline-end;
margin-block-start: 0;
margin-block-end: 6px; /* margins are allowed in @position-try */
}
/* Optional directional nuance, driven by a measured side. */
.menu[data-side="above"]:popover-open { @starting-style { translate: 0 4px; } }
.menu[data-side="below"]:popover-open { @starting-style { translate: 0 -4px; } }
.menu:popover-open { translate: 0 0; transition-property: opacity, scale, translate, display, overlay; }
@media (prefers-reduced-motion: reduce) {
.menu, .menu:popover-open { scale: 1; translate: none; }
}
// Only if directional nuance is wanted: detect the resolved side on open.
menu.addEventListener('beforetoggle', (e) => {
if (e.newState !== 'open') return;
const b = button.getBoundingClientRect();
const spaceBelow = innerHeight - b.bottom;
menu.dataset.side = spaceBelow >= menu.offsetHeight + 12 ? 'below' : 'above';
});
Rendering Impact: composite for the entrance; layout once to resolve the anchored position and choose a fallback. Because only
scale,translateandopacityanimate, the entrance cannot cause the element to flip mid-animation.
The script predicts the side from available space rather than reading the resolved position after opening, so the attribute is set before the starting style applies. Its prediction can disagree with the browser’s choice in unusual layouts; the neutral scale-and-fade remains correct either way, and the 4px nuance is small enough that a rare mismatch is barely visible.
Verification checklist
Constraints and trade-offs
- Transitions cannot interpolate between fallback positions.
@position-tryaccepts only positioning-related properties.- Predicting the side in script duplicates the browser’s decision and can disagree.
- Direction-neutral motion conveys less about where an element came from.
- Scroll-triggered fallback changes while open also snap.
Frequently asked questions
Can I animate an anchored element moving to its fallback position?
No. The fallback is chosen during layout rather than through a style change, so there is no transition to run. The element appears in the new position.
Why does my menu animate away from its button after flipping?
Its entrance uses a direction and transform-origin designed for the default position. Use a direction-neutral fade and scale, or set per-side offsets.
Can @position-try blocks contain transforms?
No. They only accept positioning-related properties such as insets, margins, sizes, alignment and position-area.
Can an entrance animation cause the element to flip?
Not if it only animates transforms and opacity, which do not affect layout. Animating width or height can change whether it fits.
Related
- CSS Anchor Positioning & Motion — the parent topic
- Anchored Menus with Popover Entry Effects — a full menu using these rules
- Choosing Easing for Enter and Exit Motion — curves for the neutral entrance