position-try-options in Practice: Recipes for Tooltips and Menus
AI generated
{ }
@
CSS · Anchor Positioning · UI Patterns · Menus
position-try-options Applied in Practice
Ready-made recipes for dropdown menus and tooltips that never spill past the edge

Beyond the built-in shorthands like flip-block, @position-try lets you define your own named fallback positions, complete with their own viewport safety margin. This article walks through ready-to-copy recipes for dropdown menus, nested submenus and tooltips that reliably stay inside the visible area.

17 min read @position-try · dropdown recipes Submenus · scroll containers

1. Why menus have different requirements than plain tooltips

A dropdown menu must never spill past the screen edge, because clipped menu items are not just ugly, they simply become unusable. Unlike a plain info tooltip that can get away with sitting somewhat awkwardly, a menu has to keep every single one of its entries fully clickable, which raises the bar for the fallback logic considerably.

The built-in shorthands flip-block and flip-inline cover many tooltip cases just fine, but hit their limits with menus once you need extra spacing from the viewport edge, a minimum width, or an alignment tied to the anchor's own width. That is exactly what the @position-try rule exists for: it lets you define a complete, named position with all the extra properties you need once, and reuse it across as many components as you like.

2. The syntax of @position-try: which properties are allowed

A @position-try rule is declared with a name of your own choosing in the --dashed-ident format and contains, inside its curly braces, a subset of positioning properties: position-area or the individual inset values, margin, width and height, as well as self-alignment properties like justify-self. Non-layout properties such as colors or fonts are deliberately not allowed here, because the rule is meant exclusively for positioning.

This restriction keeps a @position-try rule clearly separated from the component's visual styling. The menu itself gets its background color, shadow and border through the regular class rule as usual, while the @position-try rule is responsible solely for the spatial alternative. This separation makes it easy to reuse the same visual component in different places on the page with different fallback positions.

3. Building in a safety margin from the viewport edge

A menu that touches the screen edge directly looks cramped and is harder to hit on touch devices. Inside a @position-try rule this margin can be set with margin or explicit inset values, combined with calc() to guarantee a fixed minimum distance regardless of the window size.

For especially robust menus, a fixed max-width combined with width: max-content also helps, so long menu items do not cause the menu to grow past the safety margin defined by the @position-try rule. That way, even the least favorable fallback case is still guaranteed to fit within the visible area.


@position-try --menu-safe-bottom {
  position-area: bottom span-right;
  margin-block-start: 0.5rem;
  margin-inline-end: max(1rem, env(safe-area-inset-right));
  max-width: min(20rem, calc(100vw - 2rem));
}

The following recipe combines a trigger button with a menu opened through popover. Two named @position-try rules cover the cases where the button sits in the right or bottom half of the screen, each using the same safety margin introduced in the previous section.

The order inside position-try-fallbacks matters: first the variant that flips horizontally, then the one that additionally flips vertically, so the menu still finds a sensible position even in the least favorable screen corner, instead of simply falling back to whatever alternative was checked last.


<button popovertarget="user-menu" style="anchor-name: --user-trigger;">
  Account
</button>

<ul id="user-menu" popover class="menu">
  <li><a href="/profile">Profile</a></li>
  <li><a href="/settings">Settings</a></li>
  <li><a href="/logout">Log out</a></li>
</ul>

<style>
@position-try --menu-flip-left {
  position-area: bottom span-left;
  margin-block-start: 0.5rem;
}
@position-try --menu-flip-top {
  position-area: top span-left;
  margin-block-end: 0.5rem;
}

.menu {
  position: absolute;
  position-anchor: --user-trigger;
  position-area: bottom span-right;
  margin-block-start: 0.5rem;
  min-width: anchor-size(width);

  position-try-fallbacks: --menu-flip-left, --menu-flip-top;
}
</style>

5. Recipe: a tooltip whose width is tied to its anchor

A tooltip often should not grow wider than the element that triggered it, so it visually stays calm inside a form or a table row. The anchor-size() function reads the anchor's width or height and can be plugged directly into the tooltip's width, min-width or max-width, regardless of which fallback position is currently active.

Combined with a @position-try rule that additionally sets a fixed max-width as an upper bound, the tooltip stays readable even when the anchor itself is very wide, for example an entire table row. This combination of anchor-size() and an explicit ceiling stops the tooltip from tipping into either extreme, too narrow for its text or uncomfortably wide for a single word.


@position-try --tooltip-capped {
  position-area: top;
  margin-block-end: 0.4rem;
  max-width: 22rem;
}

.field-tooltip {
  position: absolute;
  position-anchor: --field-trigger;
  position-area: top;
  min-width: anchor-size(width);
  max-width: min(anchor-size(width, 1.5), 22rem);

  position-try-fallbacks: --tooltip-capped, flip-block;
}

6. Nested submenus with their own anchor chain

A submenu that opens on hover or click of a menu item needs its own anchor-name on that specific menu item and its own, independent position-try-fallbacks chain. It matters that every menu item capable of opening a submenu gets a unique anchor name, ideally generated dynamically, so that several simultaneously open submenus do not overwrite each other.

For a submenu's fallback chain, right works well as the preferred default position, with flip-inline as the first fallback, so a submenu that would open at the right screen edge automatically switches to the left instead. This logic works independently of the parent menu's own position, because every anchor relationship is evaluated on its own.

7. Anchor Positioning inside scrollable containers

When the anchor does not sit directly in the viewport but inside a scrollable container like a table with a fixed height, the fallback logic by default relates to the nearest matching containing block, not necessarily the entire viewport. In most cases that is exactly the desired behavior, because a tooltip inside a scrollable table should not spill past its edge, even if there is still room elsewhere in the viewport.

If instead you explicitly want an element to orient itself against the whole viewport rather than the surrounding scroll container, the position: fixed property combined with Anchor Positioning helps, because it explicitly raises the containing block to the initial containing block. This distinction should be made deliberately for every component, rather than relying on whatever the default behavior happens to be.

8. Common pitfalls with custom position-try recipes

A frequent mistake is assigning the same anchor-name twice: if two different elements point to the same name, the positioned element binds to whichever one appears last in the document. That leads to seemingly random misbehavior that is hard to trace unless you specifically go looking for duplicate names.

A second stumbling block is setting position-try-fallbacks on an element that itself references no position-anchor and is not positioned absolutely or fixed. Without that prerequisite the browser silently ignores the entire fallback chain, without printing any error, which makes debugging unnecessarily hard in practice.

9. Checklist before shipping a menu or tooltip recipe

Before going to production, it pays off to give every component a short but systematic test against all four screen edges, with both long and short text content, and inside a narrow mobile viewport. Once you write these combinations down as a checklist, you can reuse it for every new component instead of rediscovering all the edge cases from scratch on every project.

Equally important is a check of actual keyboard and screen reader operation, because the spatial fallback position is purely visual and changes nothing about the focus order. A menu that is positioned perfectly on screen but unreachable via keyboard has still missed its actual goal.

Recipe building block CSS property Purpose
Safety margin from the edge margin with calc() inside @position-try Menu never touches the viewport edge directly
Tying width to the anchor anchor-size(width) in min-width/max-width Tooltip stays neither narrower nor much wider than its trigger
Unique submenu anchors Dynamically generated anchor-name per menu item Several simultaneously open submenus do not overwrite each other
Viewport instead of scroll container position: fixed instead of position: absolute Positioning relates to the entire viewport
Checking the fallback prerequisite position-anchor plus position: absolute/fixed Prevents silently ignored fallback chains

Mironsoft

Modern CSS, layout architecture and rendering performance

CSS that stays maintainable instead of breaking with every change?

We review existing stylesheets for specificity chaos and layout thrashing, then build a CSS architecture with cascade layers, custom properties and modern layout primitives that still makes sense after the tenth feature.

CSS Audit

Systematically uncovering specificity issues, cascade conflicts and unused selectors.

Architecture Refactoring

Introducing cascade layers, custom properties and design tokens cleanly.

Performance Tuning

Fixing layout thrashing, expensive selectors and rendering bottlenecks.

10. Summary

Position-Try Recipes for Menus and Tooltips: The Essentials at a Glance

Custom fallback rules

@position-try defines named, reusable positions with their own margin, width and alignment.

Menu recipe

Two @position-try rules with horizontal and vertical flip robustly cover every screen corner.

Tooltip recipe

anchor-size() ties the tooltip's width to its anchor, a max-width prevents uncomfortably wide tooltips.

Common mistakes

Duplicate anchor-name values and a missing position-anchor lead to silently ignored fallback chains.

11. FAQ: Position-Try Recipes for Menus and Tooltips: The Essentials at a Glance

1What is allowed inside a @position-try rule?
Only positioning properties like position-area, inset values, margin, width, height and self-alignment properties. Visual properties like colors or fonts are not allowed there.
2How do I stop a dropdown menu from sticking to the edge?
Set margin or inset values with calc() inside the @position-try rule to guarantee a fixed minimum distance from the viewport edge, regardless of window size.
3How do I tie a tooltip's width to its anchor?
With the anchor-size(width) function, plugged into the tooltip's min-width or max-width. Combined with a fixed ceiling, the tooltip stays readable even with very wide anchors.
4Do submenus need their own anchor-name?
Yes, every menu item that can open a submenu needs a unique anchor-name, otherwise several simultaneously open submenus overwrite each other.
5Does the fallback position relate to the viewport or the scroll container?
By default to the nearest matching containing block, usually the surrounding scroll container. For the entire viewport, position: fixed instead of position: absolute helps.
6Why is my fallback chain being completely ignored?
Usually because the element references no position-anchor or is not positioned absolutely or fixed. Without that prerequisite the browser skips the entire chain without an error message.
7Can I reuse the same @position-try rule across multiple components?
Yes, that is the actual purpose of the named rule. It can be referenced by as many menus or tooltips as needed, as long as the desired spatial logic is identical.
8How do I fully test a dropdown recipe?
Against all four screen edges, with short and long text content, and inside a narrow mobile viewport. That combination covers nearly every practically relevant fallback case.
9Does a position-try fallback change keyboard operation?
No, the fallback position is purely visual and changes nothing about the focus order in the DOM. Keyboard and screen reader accessibility still need to be checked separately.
10What happens with duplicate anchor-name values?
The positioned element binds to whichever element with that name appears last in the document, causing seemingly random misbehavior. Unique, ideally dynamically generated names reliably avoid this.