CSS Anchor Positioning & Motion

Part of Modern View Transitions & Scroll APIs.

Tooltips, dropdown menus, popovers, selection toolbars and sliding tab indicators all share a structure: one element positioned relative to another element that it is not inside. For years that relationship lived in JavaScript. A positioning library measured the reference element, computed coordinates, applied inline top and left, listened for scroll and resize to recompute, and flipped the element to another side when it would overflow the viewport. Animating those elements meant animating on top of a moving target that script was continuously rewriting.

CSS anchor positioning moves that relationship into the stylesheet. An element declares an anchor-name; another element declares position-anchor and places itself with position-area or the anchor() function; fallback positions are listed with position-try-fallbacks. The browser keeps them attached through scrolling and resizing without script. That changes how motion works: entry effects become ordinary CSS, indicators can slide between anchors with a transition, and the remaining sharp edges — fallback switches, scroll attachment, layout cost — are consistent and predictable. This topic covers those motion-relevant parts and how they combine with @starting-style entry effects and the popover API.

Concept definition

Anchor positioning is a set of CSS features that position an absolutely or fixed-positioned element relative to one or more anchor elements elsewhere in the document.

  • anchor-name: --trigger on the reference element makes it an anchor.
  • position-anchor: --trigger on the positioned element picks its default anchor.
  • position-area: block-end places the element in a region of a 3×3 grid around the anchor — below it, in this case — and is the simplest way to position.
  • anchor(bottom) and friends, used in top, left, inset-* and similar properties, resolve to the anchor’s edges for precise control; anchor-size(width) resolves to its dimensions.
  • position-try-fallbacks: flip-block, flip-inline lists alternative positions the browser tries if the element overflows its containing block, and @position-try defines named custom ones.

The problem it solves for motion is stability. The anchored element’s resting position is a function of the anchor’s layout, recalculated by the browser, so animations can be written against a fixed resting state rather than against coordinates script keeps changing.

How an anchored element gets its positionLayout resolves the anchor relationship; motion is layered on top of the resolved position.How an anchored element gets its positionanchor-nameon triggerposition-anchoron popupposition-area/ anchor()resolved in layoutFallback checkoverflow?Motiontranslate, opacity
Layout resolves the anchor relationship; motion is layered on top of the resolved position.

Execution model

Anchor positioning is resolved during layout. When the anchored element is laid out, the browser finds its anchor, computes the anchor’s box, resolves anchor() and anchor-size() values and position-area into insets, and then checks whether the result overflows. If it does and fallbacks are listed, it tries each fallback in order and uses the first that fits.

That has four consequences for animation.

Transforms apply after positioning. translate, scale and rotate on the anchored element are applied to the box after its anchored position is resolved. An entrance that slides the tooltip up by 6px moves it relative to its anchored position, which is exactly what entry motion needs, and runs on the compositor.

Anchor-derived insets can transition. When the anchored element’s resolved position changes because its position-anchor changes — an indicator moving from one tab to another — properties like left and width that use anchor() and anchor-size() produce new computed values. With transitions declared on those properties, the element slides from the old anchor to the new one. That is a layout animation, run on the main thread each frame, and support for interpolating anchor-dependent values is newer than anchor positioning itself; the pattern is in sliding tab indicators with anchor positioning.

Fallback switches do not interpolate. When the browser chooses a different fallback — flipping a menu from below the button to above it — the change is decided during layout, not as a style change, so no transition runs. The element jumps to the new side. The practical fix is to design entrance motion that works on either side, covered in position-try fallbacks and motion.

Scrolling keeps elements attached. When the anchor scrolls, the anchored element follows. The browser handles this internally without script, which is a large improvement over scroll listeners, but it is still work tied to scrolling; the cost profile is in anchor positioning performance during scroll.

What runs where when a tooltip opens and its anchor scrollsPositioning is layout; entry motion is compositor; scroll attachment is handled by the browser.What runs where when a tooltip opens and its anchor scrollsLayout (position resolved)idle200 msCompositor (entry)fade + translateidle200 msScroll attachmentidlefollows anchor200 msresolveidlefade + translatefollows anchor
Positioning is layout; entry motion is compositor; scroll attachment is handled by the browser.

Property / API reference table

Property / API Accepted values Compositing tier Notes
anchor-name none, <dashed-ident># none On the reference element
position-anchor auto, <dashed-ident> layout Changing it moves the element to another anchor
position-area grid keywords such as block-end, top span-right layout Simplest placement
anchor() anchor(--name? <side>) in inset properties layout Transitionable where supported
anchor-size() anchor-size(--name? width) in sizing properties layout Sliding indicators match anchor width
position-try-fallbacks flip-block, flip-inline, <dashed-ident> layout Switching fallbacks does not animate
@position-try named rule of position properties layout Custom fallback positions
position-visibility always, anchors-visible, no-overflow layout Hide when the anchor scrolls out
translate / opacity on anchored element any composite Entry and exit motion

Annotated code examples

1 — Tooltip with an entry effect

.help-button { anchor-name: --help; }

.help-tip {
  position: absolute;
  position-anchor: --help;
  position-area: block-start;                 /* above the button */
  margin-block-end: 8px;
  opacity: 0;
  translate: 0 4px;
  transition:
    opacity var(--duration-200) linear,
    translate var(--duration-200) var(--ease-decelerate),
    display var(--duration-200) allow-discrete,
    overlay var(--duration-200) allow-discrete;
}
.help-tip:popover-open {
  opacity: 1;
  translate: 0 0;
  @starting-style { opacity: 0; translate: 0 4px; }
}

@media (prefers-reduced-motion: reduce) {
  .help-tip { translate: none; transition: opacity 100ms linear, display 100ms allow-discrete, overlay 100ms allow-discrete; }
}

Rendering Impact: layout once when the popover opens to resolve position, then composite for the fade and 4px rise.

Because translate applies after anchoring, the tooltip rises into its resolved position above the button without the animation needing to know where that is. The full tooltip pattern, including hover and focus triggers, is in animating tooltips with anchor positioning.

2 — An indicator that slides between anchors

.tabs [aria-selected="true"] { anchor-name: --active-tab; }

.tabs__indicator {
  position: absolute;
  position-anchor: --active-tab;
  left: anchor(left);
  width: anchor-size(width);
  bottom: anchor(bottom);
  block-size: 3px;
  transition:
    left var(--motion-move-duration) var(--motion-move-easing),
    width var(--motion-move-duration) var(--motion-move-easing);
}

@media (prefers-reduced-motion: reduce) {
  .tabs__indicator { transition: none; }
}

Rendering Impact: layout per frame while the indicator slides, because left and width are animated; the element is small, so the cost is low.

Moving anchor-name to the newly selected tab changes which element --active-tab refers to, which changes the resolved left and width, which the transition animates. No measurement script is involved.

3 — Hiding anchored UI when its anchor scrolls away

.selection-toolbar {
  position: fixed;
  position-anchor: --selection;
  position-area: block-start;
  position-visibility: anchors-visible;       /* hidden while the anchor is scrolled out of view */
  transition: opacity var(--duration-100) linear;
}

@media (prefers-reduced-motion: reduce) {
  .selection-toolbar { transition: none; }
}

Rendering Impact: layout when visibility changes; no per-frame work while the toolbar stays hidden.

position-visibility prevents an anchored toolbar from floating detached over unrelated content once its anchor is clipped by a scroller. It is a visibility switch, not a transition, so the change is immediate.

Which anchor changes animateStyle-driven changes can transition; layout-driven fallback choices cannot.Which anchor changes animateAnimates?TierUse fortranslate / opacity onanchored elementYesCompositeEntry and exitChanging position-anchorwith anchor() insetsYes, where supportedLayout per frameSliding indicatorsSwitching position-tryfallbackNo, snapsLayout onceOverflow handlingAnchor moves by scrollingFollows, not animatedBrowser-managedStaying attached
Style-driven changes can transition; layout-driven fallback choices cannot.

DevTools workflow

  1. Inspect the anchor relationship. Select the anchored element in the Elements panel. Chromium’s Styles pane shows the resolved anchor; hovering the position-anchor value highlights the anchor element on the page.
  2. Check the chosen fallback. Resize the viewport so the element overflows and watch whether it flips. The computed top, left and related values change when a fallback applies.
  3. Record an indicator slide. In the Performance panel, select a frame during the slide: expect a small Layout event per frame. Large layout events mean the indicator’s containing block is too big or other content depends on it.
  4. Verify entry motion is composited. In the Animations drawer, entry transitions on opacity and translate should appear; enable paint flashing and confirm the tooltip does not repaint during its fade.
  5. Scroll the anchor. Scroll a container holding the anchor and confirm the anchored element follows without lag and hides when position-visibility says it should.

Failure modes & fixes

  • The tooltip appears at the top-left of the page. Root cause: position-anchor names an anchor that does not exist or is not in scope, so positioning falls back. Fix: check the anchor name, and that the anchored element is positioned and laid out after the anchor.
  • The menu flips above the button and its entrance slides the wrong way. Root cause: the entrance always rises from below, but the fallback placed the menu above. Fix: use a fade or scale entrance that works on both sides, or define entrance offsets per @position-try block.
  • The tab indicator jumps instead of sliding. Root cause: the browser does not interpolate anchor-dependent values, or left and width are not in the transition list. Fix: add them to the transition and provide a fallback measured with script where needed.
  • Anchored popups lag behind during scroll in a JS library. Root cause: script repositions on scroll events. Fix: migrate the relationship to anchor positioning so the browser keeps the element attached.
  • A toolbar floats over unrelated content after scrolling. Root cause: the anchor scrolled out of its container but the toolbar is position: fixed. Fix: add position-visibility: anchors-visible.

Accessibility and reduced-motion notes

Anchored elements are usually transient UI — tooltips, menus, toolbars — so their motion should be short and small. Entrances of a few pixels and under about 200ms communicate where the element came from without drawing attention away from the trigger. Under reduced motion, keep a short fade and remove travel.

Anchor positioning is visual only. It does not create any relationship in the accessibility tree, so a tooltip positioned next to a button is not automatically described by it. Keep ARIA relationships explicit: aria-describedby for tooltips, aria-expanded and aria-controls for menus, and use the popover API or dialog element for top-layer behaviour and dismissal. Sliding indicators are decorative; the selected state must be exposed with aria-selected or aria-current, as covered in focus highlights that follow anchors.

Entry travel for anchored UISmall offsets make anchored elements feel attached; large ones make them feel thrown.Entry travel for anchored UItetherednoticeabledetached04812162024 pxtooltip 4 pxmenu 8 pxtoo far 20 px
Small offsets make anchored elements feel attached; large ones make them feel thrown.

Frequently asked questions

Can CSS anchor positioning replace JavaScript positioning libraries?

For many tooltips, menus and popovers, yes. It handles placement, overflow fallbacks and staying attached during scroll. Complex cases, such as virtual anchors at text selections, may still need script to place an anchor element.

Do anchored elements animate when their anchor changes?

Properties that use anchor() and anchor-size() can transition when the anchor changes, where the browser supports interpolating those values. That animation is layout per frame.

Why does my menu jump when it flips to the other side?

Choosing a position-try fallback happens during layout and is not a style transition, so it cannot be interpolated. Design entrance motion that works on either side.

Is anchor positioning expensive for performance?

Positioning is resolved in layout, like any absolutely positioned element. Entry motion on transform and opacity stays on the compositor. Sliding between anchors is a small layout animation.

Which browsers support anchor positioning?

Support is available in Chromium-based browsers and has been arriving in others. Treat it as progressive enhancement with a fallback position that is still usable.