Automatically Resolving Viewport Collisions with CSS Anchor Positioning — `@position-try` and `position-try-fallbacks` Positional Fallback Strategies
Have you ever found yourself installing Floating UI just to build a tooltip, wiring up the useFloating hook, adding the flip() middleware, and attaching a ResizeObserver on top of it all? Honestly, the first time I went through that process, I couldn't shake the feeling that "this should have been handled at the CSS level." And finally, that frustration has been resolved.
CSS Anchor Positioning is a native CSS API that tethers one element to another using CSS alone and automatically tries alternative positions when a collision with the viewport boundary occurs. As of 2026, it has become a Baseline 2026 technology supported by all four major browsers — Chrome, Edge, Firefox, and Safari. With browser coverage across all caniuse targets now at roughly 91%, the time is right to incorporate it into real-world projects.
This article focuses on two key parts of that API — the @position-try at-rule and the position-try-fallbacks property. Combining these two lets the CSS engine automatically try alternative positions in order the moment an element overflows the viewport boundary. Without a single line of JavaScript event handling.
Core Concepts
The Relationship Between an Anchor and a Positioned Element
CSS Anchor Positioning is built around two roles: the anchor, which serves as the reference point, and the positioned element, which is tethered to and placed relative to it. The connection takes just two lines of CSS.
/* Designate the anchor */
.trigger {
anchor-name: --my-anchor;
}
/* Element tethered to the anchor */
.tooltip {
position: absolute;
position-anchor: --my-anchor;
position-area: top center; /* Select a position from the 9-zone grid relative to the anchor */
}
anchor-namenaming format: Like CSS custom properties (--color: red), the name is prefixed with--. Examples:--my-anchor,--btn,--dropdown-trigger.
What is
position-area? Think of a 3×3 grid (9 cells) centered on the anchor.top centeris directly above the anchor in the center, andbottom rightis below and to the right of the anchor. This is a renamed version of theinset-areaproperty from the older spec.
This alone creates an element that stays attached to the anchor. But what if the button is near the top edge of the screen? The tooltip will be clipped outside the viewport. This is where @position-try and position-try-fallbacks come in.
@position-try — Defining Custom Fallback Position Rules
@position-try is an at-rule that defines a named set of alternative position rules. It bundles position, size, margin, and more into a single option that says "try this position."
@position-try --tooltip-bottom {
position-area: bottom center;
width: 220px;
margin-block-start: 8px;
}
@position-try --tooltip-right {
position-area: right span-top;
margin-inline-start: 8px;
}The key point is that you can define not just "change the position" but "when facing this direction, display with this size and spacing" as a complete unit. When I first discovered this, I thought it could be quite useful.
position-try-fallbacks — The Ordered List of Fallbacks the Browser Tries
position-try-fallbacks is the property that defines the order in which fallbacks are attempted. The browser tests the listed options one by one from the front, and automatically applies the first option where the element does not overflow its containing block.
.tooltip {
position: absolute;
position-anchor: --my-anchor;
position-area: top center;
position-try-fallbacks:
flip-block, /* 1st priority: flip along the block axis (top↔bottom) */
flip-inline, /* 2nd priority: flip along the inline axis (left↔right) */
flip-block flip-inline, /* 3rd priority: flip both axes */
--tooltip-bottom; /* 4th priority: apply custom rule */
}If you've ever been confused about the difference between the three keywords, bookmark this table.
| Keyword | Flip Direction | Example Use |
|---|---|---|
flip-block |
Top↔Bottom (block axis) | No space above → flip below |
flip-inline |
Left↔Right (inline axis) | No space on right → flip to left |
flip-start |
Diagonal start→end flip | Right-to-left writing environments like Arabic, Hebrew |
flip-block flip-inline |
Both axes simultaneously | Diagonal flip when clipping at a corner |
flip-start is especially useful in mixed LTR/RTL layouts. For multilingual services that support right-to-left writing environments like Arabic or Hebrew, flip-start alone can unify the directional behavior.
What is a Containing Block? It is the parent box that serves as the size and position reference for a positioned element. For
position: absolute, the containing block is the nearest ancestor element withposition: relative(or absolute, fixed, or sticky) set. The viewport itself can also act as a containing block.
position-try-order — Selecting by "Most Available Space" Instead of "Order"
position-try-order is a supplementary property that changes how position-try-fallbacks behaves. The default value normal tries fallbacks in declaration order, but changing this property makes the browser prioritize the direction with the most available space.
.dropdown {
position-try-fallbacks: flip-block, flip-inline, flip-start;
position-try-order: most-height; /* Prioritize the direction with the most vertical space */
}| Value | Meaning |
|---|---|
normal |
Try in declaration order (default) |
most-width |
Prioritize the direction with the most horizontal space |
most-height |
Prioritize the direction with the most vertical space |
most-block-size |
Prioritize the option with the most block-direction space |
most-inline-size |
Prioritize the option with the most inline-direction space |
For dropdown lists with many items, using most-height lets the browser automatically choose the best direction to expand into. Note, however, that Firefox does not currently support position-try-order, so it's worth first checking whether order-based position-try-fallbacks alone provides sufficient coverage.
Practical Application
Example 1: Auto-Flipping Tooltip with No JavaScript
This is the most fundamental case with the greatest tangible impact. The first time I applied this pattern in practice, I was genuinely surprised to see a tooltip naturally change direction at the corner of the screen without Floating UI.
Here is the corresponding HTML markup and CSS.
<button class="anchor-btn">Help</button>
<div class="tooltip" role="tooltip">
Additional description of the button.
</div>/* Anchor button */
.anchor-btn {
anchor-name: --btn;
}
/* Tooltip */
.tooltip {
position: absolute;
position-anchor: --btn;
position-area: top center;
width: max-content;
max-width: 240px;
padding: 6px 10px;
background: #1e1e2e;
color: #fff;
border-radius: 6px;
/* Fallback: try top → bottom → left → right in order */
position-try-fallbacks:
flip-block,
flip-inline,
flip-block flip-inline;
}Note: The CSS above is an example focused on the positional placement logic. In a real-world service, it is recommended to combine it with
opacity/visibilityhandling or the Popover API to manage show/hide behavior. Using the Popover API means the browser handles keyboard accessibility and focus management as well.
| Scenario | Fallback Applied | Result |
|---|---|---|
| Button in the center of the screen | None (default top center) |
Displayed above the button |
| Button at the top of the screen | flip-block |
Displayed below the button |
| Button at the top-right corner | flip-block flip-inline |
Displayed to the lower-left of the button |
The CSS engine detects the viewport boundary and tests the above list in order, automatically selecting a safe position. It also recalculates in real time on scroll and resize.
Example 2: Context Menu — Switching to the Left When Space on the Right Is Insufficient
This is a pattern well-suited for UI that needs to switch between left and right depending on trigger position. The first time I applied this approach to a context menu, I was able to remove a significant portion of the position-calculation code I had previously written in JavaScript.
/* Custom fallback: display on the left */
@position-try --menu-left {
position-area: left span-bottom;
width: 180px;
}
/* Context menu */
.context-menu {
position: absolute;
position-anchor: --trigger;
position-area: right span-bottom; /* Default: to the right of the trigger */
width: 180px;
position-try-fallbacks: --menu-left; /* If no space on the right, switch to the left */
}What is
span-bottom? It is the span syntax forposition-area.right span-bottommeans "start to the right of the anchor and extend downward." The dropdown expands naturally below and to the right of the anchor.
One thing worth noting: detecting click events themselves or using the cursor position as an anchor — as in a "right-click menu" — is not possible with CSS alone. Handling the right-click event and specifying the anchor position requires JavaScript. The role CSS plays in this pattern is best understood as "positional fallback that automatically avoids viewport boundaries when the menu is open."
Connecting a button and menu via the Popover API's popovertarget attribute and setting an implicit anchor with the anchor attribute makes the connection possible without CSS anchor-name. Backend developers or React/Vue users may find the HTML attribute approach more intuitive — for those who want to learn more, the Frontend Masters article Popover Context Menus with Anchor Positioning is a great starting point.
Example 3: Changing Arrow Direction Based on Fallback Detection — Anchored Container Queries
⚠️ This is an experimental feature available in Chrome 143+ only. Rather than applying the code below directly to production, it is advisable to consider a progressive enhancement strategy.
I was stuck on this part for quite a while myself — the problem was the limitation that "CSS cannot know which fallback has been applied." Even when a tooltip flipped downward, there was no way to change the arrow direction along with it.
In Chrome 143, Anchored Container Queries solved this problem.
.tooltip {
position: absolute;
position-anchor: --btn;
position-area: top center;
position-try-fallbacks: flip-block, flip-inline;
/* Designate this element as an anchor-aware query container */
container-type: anchored;
}
/* Default state: arrow points downward */
.tooltip::after {
content: "▼";
}
/* When the flip-block fallback is applied: flip the arrow */
@container anchored(fallback: flip-block) {
.tooltip::after {
content: "▲";
}
}This feature was introduced in the CSS Anchor Positioning Level 2 spec, and the direction of travel is clear. However, the container-type: anchored and @container anchored(fallback: ...) syntax may change as Chrome releases progress, so it is worth checking the Chrome release notes directly before adopting it.
Pros and Cons Analysis
Pros
| Item | Details |
|---|---|
| Performance | Handled at the layout engine level. No ResizeObserver loops or rAF needed |
| Code brevity | Floating UI installation, configuration, and event binding code can be largely eliminated |
| Automatic handling scope | The browser handles scroll, resize, and viewport overflow directly |
| Separation of concerns | Position logic lives in CSS only. JS doesn't need to know about UI positioning |
| Accessibility integration | Combined with Popover API, the browser guarantees keyboard navigation and focus management |
Cons and Caveats
| Item | Details | Mitigation |
|---|---|---|
| Browser support | Safari 26 requires the latest macOS/iOS | Progressive enhancement with @supports |
position-try-order |
Not supported in Firefox | Fall back to order-based position-try-fallbacks |
| Anchored CQ | Chrome 143+ only | Branch with @supports, keep default styles for unsupported environments |
| Complex scenarios | Shadow DOM cross-origin, virtual scroll, etc. | Use Floating UI alongside with conditional branching |
| Spec changes | position-try-order is Working Draft status |
Apply core features first |
What is a Working Draft? It is an early stage in the W3C standardization process, meaning the spec is not yet finalized and may change. In contrast,
position-try-fallbacksitself is already a stably implemented feature.
The Most Common Mistakes in Practice
-
Leaving the default position in a "broken state" — In older browsers without fallback support, elements will end up outside the viewport. The default value of
position-areamust be at least "visible" in every environment. -
Writing code using the name
position-try-options— It was officially renamed toposition-try-fallbacksin Chrome 129. This is an easy point of confusion when referencing older tutorials. -
Applying it directly without an
@supportsbranch — It is safer to keep your Floating UI initialization code around for environments that don't yet support this. Refer to the progressive enhancement pattern below.
/* Default position for older browsers */
.tooltip {
top: auto;
left: 50%;
transform: translateX(-50%);
}
/* Apply only in environments that support CSS Anchor Positioning */
@supports (anchor-name: --x) {
.tooltip {
position: absolute;
position-anchor: --trigger;
position-area: top center;
transform: none; /* Remove transform when anchor positioning is active */
position-try-fallbacks: flip-block, flip-inline;
}
}Closing Thoughts
@position-try and position-try-fallbacks represent a change that overturns the long-held premise — at the CSS level — that "handling viewport boundaries is JavaScript's job." Rather than replacing all existing code at once, it is better to start by applying this to new tooltip or dropdown components you build. As browser support continues to grow, getting comfortable with it now will certainly pay off.
Three steps you can start with right now:
-
Visualize anchor relationships in DevTools — Select an element with
anchor-namein the Chrome DevTools Elements panel to visually inspect the anchor relationship and theposition-tryfallback attempt process. -
Add
flip-blockto an existing tooltip — Pick one tooltip in your project and add onlyposition-anchor+position-try-fallbacks: flip-blockinside a@supports (anchor-name: --x)block. You'll immediately see how naturally viewport collisions are handled with no additional code. -
Optimize your bundle with conditional imports — Use
CSS.supports('anchor-name', '--x')to detect support, and configure a conditional import so that Floating UI is not loaded when the result istrue. You'll experience the bundle size reduction as a bonus.
References
- MDN: @position-try CSS at-rule | MDN Web Docs
- MDN: position-try-fallbacks | MDN Web Docs
- MDN: Fallback options and conditional hiding for overflow | MDN Web Docs
- MDN: position-try-order | MDN Web Docs
- Introducing the CSS anchor positioning API | Chrome for Developers
- Detect fallback positions with anchored container queries | Chrome for Developers
- position-try-fallbacks | CSS-Tricks
- CSS Anchor Positioning Guide | CSS-Tricks
- Anchor Positioning Updates for Fall 2025 | OddBird
- css-anchor-positioning Polyfill | OddBird GitHub
- W3C CSS Anchor Positioning Module Level 1 | W3C
- Popover Context Menus with Anchor Positioning | Frontend Masters
- The Basics of Anchor Positioning | Ahmad Shadeed
- Understanding CSS Anchor Positioning and Fallback Detection | JavaScript in Plain English