Skip to content
AgileLogics
7 min read

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-name names an anchor; position-anchor attaches a positioned element to it.
  • position-area places it in a 3×3 grid around the anchor (top, bottom span-right, left center, ...).
  • anchor(side) gives the anchor's edge coordinates for top, left, and friends.
  • position-try-fallbacks flips 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-name animate 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.