MinhVo

Minh Vo

rss feed

Slaying code & making it lit fr fr 🔥 tagline

Hey there 👋 I'm an AI Engineer with 7 years of experience building scalable web and mobile applications. Currently at Neurond AI (May 2025 — present), architecting an Enterprise AI Assistant Platform with multi-tenant RAG on pgvector, multi-provider LLM orchestration, and Azure-native infrastructure. Previously spent 5+ years at SNAPTEC (Sep 2019 — Apr 2025), leading SaaS themes, admin dashboards, and e-commerce platforms — earned the Hero of the Year award in 2021. I specialize in TypeScript, React, Next.js, and AI-Native engineering with Claude Code and Cursor.bio

Back to blogs

CSS Anchor Positioning: Tooltip and Popover Layout

Use CSS anchor positioning: anchor(), position-area, and popover positioning without JS.

CSSAnchor PositioningPopoverFrontend

By MinhVo

Introduction

Tooltips and popovers have been a staple of web interfaces for decades, yet positioning them correctly has always been surprisingly difficult. Developers have relied on JavaScript libraries like Popper.js and Floating UI to handle the complex calculations needed to place elements relative to their triggers while avoiding viewport overflow. CSS Anchor Positioning changes everything by bringing this capability natively to the browser.

The CSS Anchor Positioning API allows you to declaratively attach any element to another element using pure CSS. No JavaScript required. This means tooltips that follow their trigger buttons, dropdown menus that stay aligned, and popover panels that flip when they hit the viewport edge — all handled by the browser's layout engine with zero runtime JavaScript overhead.

CSS Anchor Positioning enables declarative tooltip layout

In this comprehensive guide, we'll explore how CSS Anchor Positioning works under the hood, walk through practical implementation patterns for tooltips and popovers, and examine advanced techniques for building robust positioning systems without a single line of JavaScript.

Understanding Anchor Positioning: Core Concepts

CSS Anchor Positioning introduces two fundamental concepts: anchor elements and positioned elements. An anchor element is the reference point — typically a button, icon, or any interactive element that triggers a tooltip or popover. A positioned element is the floating content that needs to be placed relative to the anchor.

The system works through two key properties: anchor-name on the anchor element and position-anchor on the positioned element. The anchor element declares itself as a named anchor using a dashed-ident (a custom identifier prefixed with --), and the positioned element references that name to establish the relationship.

/* Declare an anchor element */
.trigger-button {
  anchor-name: --my-anchor;
}
 
/* Attach a positioned element to the anchor */
.tooltip {
  position: fixed;
  position-anchor: --my-anchor;
  top: anchor(bottom);
  left: anchor(center);
}

The anchor() function is the heart of the API. It takes an anchor edge (top, right, bottom, left, center) and returns the coordinate of that edge on the anchor element. You can then use this coordinate to position the floating element. The center keyword resolves to the midpoint of the element in that axis.

What makes this truly powerful is the fallback positioning system. When a positioned element would overflow the viewport, you can define alternative positions using position-try-fallbacks or the @position-try at-rule. The browser automatically selects the first fallback position that keeps the element fully visible, eliminating the need for manual flip/shift logic.

.tooltip {
  position: fixed;
  position-anchor: --my-anchor;
  top: anchor(bottom);
  left: anchor(center);
  translate: -50% 0;
  position-try-fallbacks: --top, --right, --left;
}
 
@position-try --top {
  top: anchor(top);
  bottom: auto;
  translate: -50% -100%;
}
 
@position-try --right {
  top: anchor(center);
  left: anchor(right);
  translate: 0 -50%;
}

Anchor positioning concept with fallback positions

Anchor Scoping

Anchor names are scoped to the containing block of the anchor element. This means if you have multiple components with the same anchor name, each positioned element will only attach to its nearest ancestor anchor. This scoping behavior makes the API naturally component-friendly — you can reuse the same anchor names across different components without conflicts.

Position Area Shorthand

Instead of using anchor() functions for each edge, you can use the position-area property to place elements using a grid-based shorthand. The anchor element divides the space around it into a 3×3 grid, and you can specify placement using keywords like top, bottom, left, right, and their combinations.

/* Place tooltip above the anchor, centered */
.tooltip {
  position-area: top;
}
 
/* Place to the top-right of the anchor */
.tooltip {
  position-area: top right;
}

Architecture and Design Patterns

The Tooltip Pattern

The most common use case for anchor positioning is tooltips. A tooltip typically appears adjacent to its trigger element, with a small offset for visual separation. The architecture involves a trigger element that declares itself as an anchor, and a tooltip element that positions itself relative to the trigger. The tooltip should use position: fixed and reference the anchor by name, with appropriate margins for spacing.

The Popover Pattern

Popovers are more complex than tooltips because they contain interactive content and often need to be dismissible. The popover pattern typically uses the HTML Popover API in conjunction with anchor positioning. The popover element is attached to the trigger via anchor positioning, and the Popover API handles show/hide behavior, focus trapping, and light-dismiss semantics automatically.

The Dropdown Menu Pattern

Dropdown menus require careful positioning to ensure all menu items remain visible regardless of the number of items. The pattern involves positioning the dropdown container relative to the trigger and using max-height with overflow scrolling to handle long menus. Anchor positioning ensures the menu stays aligned with the trigger even when the trigger is near the edge of the viewport.

The Context Menu Pattern

Context menus appear at the cursor position rather than relative to a specific element. This pattern requires a different approach — creating a pseudo-anchor at the cursor coordinates using JavaScript event data, then letting CSS anchor positioning handle the rest.

Step-by-Step Implementation

Building a Basic Tooltip

Let's build a production-ready tooltip system using CSS Anchor Positioning. We'll start with the HTML structure:

<div class="tooltip-wrapper">
  <button class="tooltip-trigger" aria-describedby="tip1">
    Hover me
  </button>
  <div class="tooltip" id="tip1" role="tooltip">
    This is a helpful tooltip with anchor positioning.
  </div>
</div>

Now let's implement the CSS with full fallback support:

/* Anchor definition */
.tooltip-wrapper {
  position: relative;
  display: inline-block;
}
 
.tooltip-trigger {
  anchor-name: --tooltip-anchor;
  padding: 8px 16px;
  border: 1px solid #ccc;
  border-radius: 6px;
  cursor: pointer;
}
 
/* Tooltip base styles */
.tooltip {
  position: fixed;
  position-anchor: --tooltip-anchor;
  position-area: block-start;
  margin-block-end: 8px;
  width: max-content;
  max-width: 250px;
  padding: 8px 12px;
  background: #1a1a1a;
  color: white;
  font-size: 14px;
  border-radius: 6px;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.2s, visibility 0.2s;
}
 
/* Show on hover */
.tooltip-trigger:hover + .tooltip,
.tooltip-trigger:focus + .tooltip {
  opacity: 1;
  visibility: visible;
}

Adding Fallback Positions

The real power comes from automatic fallback positioning. When the tooltip would overflow the viewport above the anchor, it should flip to below:

.tooltip {
  position: fixed;
  position-anchor: --tooltip-anchor;
  position-area: block-start;
  margin-block-end: 8px;
  position-try-fallbacks: --below, --left, --right;
}
 
@position-try --below {
  position-area: block-end;
  margin-block-end: 0;
  margin-block-start: 8px;
}
 
@position-try --left {
  position-area: inline-start;
  margin-inline-end: 8px;
  margin-inline-start: 0;
}
 
@position-try --right {
  position-area: inline-end;
  margin-inline-start: 8px;
  margin-inline-end: 0;
}

Building a Popover with Arrow

Popovers often include an arrow pointing to the trigger. With anchor positioning, we can create the arrow using a pseudo-element and position it dynamically:

.popover {
  position: fixed;
  position-anchor: --popover-anchor;
  position-area: block-end;
  margin-block-start: 10px;
  padding: 16px;
  background: white;
  border: 1px solid #e0e0e0;
  border-radius: 8px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  position-try-fallbacks: --popover-top;
}
 
.popover::before {
  content: '';
  position: absolute;
  top: -6px;
  left: anchor(center);
  translate: -50% 0;
  width: 12px;
  height: 12px;
  background: white;
  border-top: 1px solid #e0e0e0;
  border-left: 1px solid #e0e0e0;
  transform: rotate(45deg);
}
 
@position-try --popover-top {
  position-area: block-start;
  margin-block-start: 0;
  margin-block-end: 10px;
}

Integrating with the Popover API

For a fully functional popover that handles show/hide states, we combine anchor positioning with the HTML Popover API:

<button popovertarget="my-popover" class="popover-trigger">
  Open Menu
</button>
<div id="my-popover" popover class="popover-content">
  <nav>
    <a href="/profile">Profile</a>
    <a href="/settings">Settings</a>
    <a href="/logout">Logout</a>
  </nav>
</div>
.popover-trigger {
  anchor-name: --menu-anchor;
}
 
[popover] {
  position: fixed;
  position-anchor: --menu-anchor;
  position-area: block-end span-inline-end;
  margin-block-start: 4px;
}
 
/* Style the popover when open */
[popover]:popover-open {
  display: grid;
  gap: 4px;
  padding: 8px;
  border: 1px solid #ddd;
  border-radius: 8px;
}

Building tooltip and popover components

Real-World Use Cases

Use Case 1: Action Menu on a Data Table

In data-heavy applications, each row often has an action menu (edit, delete, archive). With anchor positioning, you can create a reusable action menu that positions itself relative to the clicked row's action button without JavaScript calculations. The menu automatically flips when near the bottom of the table, ensuring all menu items remain accessible regardless of the row's position.

Use Case 2: Form Field Help Tooltips

Complex forms benefit from contextual help tooltips that explain each field. Anchor positioning ensures these tooltips always appear adjacent to their input fields, even when the form is inside a scrollable container. The position-visibility: anchor-visible property ensures tooltips hide when their anchor scrolls out of view.

Use Case 3: Color Picker Popover

Design tools and rich text editors often include color picker popovers. These need to appear directly below the color swatch button and adjust their position based on available space. Anchor positioning handles this elegantly, with the picker flipping to the top or sides when the user scrolls near the viewport edge.

Use Case 4: Notification Badges

Notification badges attached to avatar icons or navigation items can use anchor positioning to ensure they stay perfectly aligned with their parent element regardless of the parent's size or position changes.

Best Practices for Production

  1. Always define fallback positions — Don't rely on a single position. Use position-try-fallbacks to handle viewport overflow gracefully. Define at least two fallback positions for every anchored element to cover edge cases at the top, bottom, and sides of the viewport.

  2. Use semantic anchor names — Name your anchors based on their purpose (e.g., --menu-trigger, --help-icon) rather than generic names like --anchor-1. This improves code readability and prevents naming conflicts in large applications.

  3. Combine with the Popover API — For interactive popovers, use the native Popover API (popover attribute) alongside anchor positioning. This provides proper focus management, accessibility, and dismiss behavior for free.

  4. Set appropriate z-index values — Anchored elements need proper stacking context management. Use z-index or the @layer system to ensure tooltips and popovers appear above other content consistently.

  5. Test with dynamic content — Anchor positions work best when the anchor element has a stable size. Test your tooltips and popovers with varying anchor sizes to ensure the positioning remains correct when content changes.

  6. Consider reduced motion preferences — When adding transitions to anchored elements, respect the prefers-reduced-motion media query. Some users may experience discomfort with animated tooltips and popovers.

  7. Use width: max-content for tooltips — Tooltip content varies in length. Using max-content ensures the tooltip expands to fit its text while respecting max-width constraints for readability.

  8. Handle nested anchors carefully — When you have nested interactive elements (e.g., a button inside a card that's also clickable), ensure each anchor name is unique within its scope to avoid positioning conflicts.

Common Pitfalls and Solutions

PitfallImpactSolution
Forgetting position: fixedElement won't anchor correctlyAnchored elements must use position: fixed or position: absolute
No fallback positions definedTooltips overflow viewportAlways define position-try-fallbacks with at least 2 alternatives
Using same anchor name in different scopesUnexpected positioning behaviorUse descriptive, unique anchor names per component
Missing width: max-content on tooltipsTooltip wraps to narrowSet width: max-content with a max-width limit
Anchored element not in DOM when anchor isPositioning fails silentlyEnsure both elements are rendered simultaneously
Overriding display on [popover]Popover API breaksUse display: grid or display: flex within the popover, not on it

Performance Optimization

CSS Anchor Positioning is inherently more performant than JavaScript-based positioning libraries. The browser handles all calculations during layout, which means no JavaScript execution overhead on scroll or resize events. However, there are still optimization considerations for large-scale applications.

When using anchor positioning with many tooltips on a page (such as in a data table with hundreds of rows), avoid creating excessive @position-try rules per element. Instead, define shared fallback rules as utility classes that all tooltips can reference. Each fallback rule requires the browser to evaluate alternative positions, so consolidating them reduces the layout calculation overhead.

/* Efficient: shared fallback rules */
.table-tooltip {
  position-try-fallbacks: --flip-top, --flip-left, --flip-right;
}

For animations, use opacity and transform for transitions rather than top or left, as these properties don't trigger layout recalculations:

.tooltip {
  transition: opacity 0.15s ease, transform 0.15s ease;
}
 
.tooltip[hidden] {
  opacity: 0;
  transform: translateY(4px);
}

Comparison with Alternatives

FeatureCSS Anchor PositioningFloating UI (Popper.js)Tippy.js
JavaScript requiredNoYes (~5KB)Yes (~10KB)
Automatic viewport flippingBuilt-inManual configBuilt-in
Scroll-aware repositioningYesYesYes
Arrow positioningManual (pseudo-element)Built-inBuilt-in
Animation controlCSS transitionsManualBuilt-in
Browser supportChrome 125+, Edge 125+All modernAll modern
PerformanceNative (no JS)JS on scroll/resizeJS on scroll/resize
Custom fallback strategies@position-try at-ruleMiddleware pipelinePlugin system

Advanced Patterns

Inset Area Positioning

The position-area property provides a grid-based shorthand for placing elements. The anchor divides the surrounding space into a 3×3 grid, and you specify placement using row and column keywords. This is particularly useful for complex layouts where you need precise control over which quadrant the positioned element occupies.

/* Place element spanning the entire right side */
.context-menu {
  position-area: right;
}
 
/* Place at the top-right corner */
.badge {
  position-area: top right;
}
 
/* Span the full width below */
.expanded-panel {
  position-area: block-end;
}

Position Visibility

The position-visibility property controls whether an anchored element should be hidden when its anchor is not visible or when the element itself overflows. Setting position-visibility: anchor-visible ensures tooltips automatically hide when their trigger scrolls out of the viewport, preventing orphaned tooltips from floating on screen.

.tooltip {
  position-visibility: anchor-visible;
}

Multiple Anchors

A positioned element can reference different anchors for different axes, enabling complex layouts where the top edge follows one anchor and the left edge follows another. This is useful for creating overlay panels that stretch between two reference points.

.indicator {
  position: fixed;
  top: anchor(--top-bar bottom);
  left: anchor(--sidebar right);
  right: anchor(--main-content left);
  bottom: anchor(--status-bar top);
}

Testing Strategies

Testing anchor-positioned elements requires checking both the default position and fallback behavior. Use visual regression testing tools to capture screenshots at different viewport sizes and verify that tooltips and popovers remain visible and properly aligned:

// Playwright test for tooltip positioning
test('tooltip flips when near viewport edge', async ({ page }) => {
  await page.goto('/components/tooltip');
  const trigger = page.locator('.tooltip-trigger');
  
  // Scroll trigger to bottom of viewport
  await trigger.scrollIntoViewIfNeeded();
  await trigger.hover();
  
  const tooltip = page.locator('.tooltip');
  const tooltipBox = await tooltip.boundingBox();
  const triggerBox = await trigger.boundingBox();
  
  // Verify tooltip is above the trigger (flipped from default below)
  expect(tooltipBox.y).toBeLessThan(triggerBox.y);
});

Future Outlook

CSS Anchor Positioning is part of the broader CSS positioning evolution that includes the Popover API and CSS @scope. As browser support expands beyond Chromium to Firefox and Safari, this will become a universal solution for floating UI. The CSS Working Group is also exploring anchor-scope for more fine-grained control over anchor name resolution, which will simplify complex component hierarchies further.

The combination of anchor positioning with the Popover API and View Transitions represents a fundamental shift toward declarative UI patterns that were previously only possible with JavaScript frameworks. As these APIs mature, the need for third-party positioning libraries will diminish significantly.

Conclusion

CSS Anchor Positioning eliminates one of the most persistent pain points in frontend development: positioning floating elements relative to their triggers. The key takeaways from this guide are:

  1. Use anchor-name and position-anchor to establish the relationship between trigger and floating element declaratively in CSS.
  2. Always define fallback positions with position-try-fallbacks to handle viewport overflow gracefully without JavaScript.
  3. Combine with the Popover API for accessible, interactive popovers with proper focus management and light-dismiss behavior.
  4. Use position-area shorthand for common positioning patterns to keep your CSS concise and readable.
  5. Test across viewport sizes to ensure fallback positions work correctly in all scenarios and edge cases.

Start by replacing your most common tooltip patterns with anchor positioning. As browser support matures, you'll be able to remove JavaScript positioning libraries entirely, reducing bundle size and improving performance across your application.