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: --triggeron the reference element makes it an anchor.position-anchor: --triggeron the positioned element picks its default anchor.position-area: block-endplaces 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 intop,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-inlinelists alternative positions the browser tries if the element overflows its containing block, and@position-trydefines 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.
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.
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
leftandwidthare 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.
DevTools workflow
- Inspect the anchor relationship. Select the anchored element in the Elements panel. Chromium’s Styles pane shows the resolved anchor; hovering the
position-anchorvalue highlights the anchor element on the page. - Check the chosen fallback. Resize the viewport so the element overflows and watch whether it flips. The computed
top,leftand related values change when a fallback applies. - 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.
- Verify entry motion is composited. In the Animations drawer, entry transitions on
opacityandtranslateshould appear; enable paint flashing and confirm the tooltip does not repaint during its fade. - Scroll the anchor. Scroll a container holding the anchor and confirm the anchored element follows without lag and hides when
position-visibilitysays it should.
Failure modes & fixes
- The tooltip appears at the top-left of the page. Root cause:
position-anchornames 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-tryblock. - The tab indicator jumps instead of sliding. Root cause: the browser does not interpolate anchor-dependent values, or
leftandwidthare 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: addposition-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.
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.
Related
- CSS @starting-style & Entry Effects — entrances for anchored popovers
- Animating Tooltips with Anchor Positioning — the complete tooltip pattern
- Position-Try Fallbacks and Motion — motion that survives flipping
- Sliding Tab Indicators with Anchor Positioning — transitions between anchors
- Animating Popover and Dialog Entry and Exit — top-layer motion fundamentals
- Anchored Menus with Popover Entry Effects — a dropdown menu built from popovertarget, anchor positioning and