Anchor Positioning, Popovers, and View Transitions
Prerequisites: Positioning, Transitions
Three recent features replace a lot of JavaScript for interface polish.
Anchor positioning
Tooltips and menus need to sit next to the element that opened them. Anchor positioning does it in CSS:
.menu-button {
anchor-name: --menu-anchor;
}
.menu {
position: absolute;
position-anchor: --menu-anchor;
position-area: bottom span-right;
margin-top: 4px;
position-try-fallbacks: flip-block, flip-inline;
}
.tooltip {
position: fixed;
position-anchor: --menu-anchor;
top: anchor(bottom);
left: anchor(center);
translate: -50% 0;
}
anchor-namenames an anchor;position-anchorattaches a positioned element to it.position-areaplaces it in a 3×3 grid around the anchor (top,bottom span-right,left center, ...).anchor(side)gives the anchor's edge coordinates fortop,left, and friends.position-try-fallbacksflips the element when it would overflow the viewport — the job that used to need a positioning library.
Supported in Chrome and Edge 125+, Safari 26, and Firefox 147.
Popovers and dialogs, styled
The HTML popover attribute and dialog element put content on the
top layer. CSS styles their states and animates them in and out:
[popover] {
opacity: 0;
scale: 0.95;
transition: opacity 150ms, scale 150ms, overlay 150ms allow-discrete, display 150ms allow-discrete;
}
[popover]:popover-open {
opacity: 1;
scale: 1;
}
@starting-style {
[popover]:popover-open {
opacity: 0;
scale: 0.95;
}
}
dialog::backdrop {
background: rgb(0 0 0 / 40%);
}
Transitioning display and overlay with allow-discrete keeps the
element on screen until the exit animation finishes. See the
HTML course for the markup side.
View transitions
View transitions animate between two states of a page — or between two pages. The browser snapshots the old state, you update the DOM, and it cross-fades (or animates however you specify):
<!-- illustrative: needs a user action and real rendering -->
<button type="button" id="toggle">Switch view</button>
<ul id="list" class="grid-view"><li>One</li><li>Two</li></ul>
<script>
document.getElementById("toggle").addEventListener("click", () => {
const list = document.getElementById("list");
if (!document.startViewTransition) {
list.classList.toggle("grid-view");
return;
}
document.startViewTransition(() => list.classList.toggle("grid-view"));
});
</script>
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 250ms;
}
.product-image { view-transition-name: product-hero; }
@view-transition { navigation: auto; }
document.startViewTransition()handles same-document changes (Chrome and Edge 111, Safari 18, Firefox 144).- Elements with a
view-transition-nameanimate individually — a thumbnail can grow into the product page's hero image. @view-transition { navigation: auto; }enables transitions between pages of the same site, with no JavaScript (Chromium 126 and Safari 18.2; not yet Firefox).- Wrap them in
prefers-reduced-motion: no-preference.
Where these aren't supported, the page simply changes without animation — a perfect progressive enhancement.