By following this guide, you will have a working CSS Anchor Positioning layout system that handles tooltips, popovers, and contextual overlays without a single line of JavaScript positioning logic. The build takes about 60 to 90 minutes and assumes you are working in a modern browser-based project. CSS Anchor Positioning reached baseline availability across Chrome, Edge, Firefox, and Safari as of mid-2025, making it production-ready for most SMB and SaaS projects in 2026.

What You'll Build

  • A reusable CSS anchor positioning utility system that works across Chrome 125+, Edge 125+, Firefox 130+, and Safari 18+
  • A tooltip component that positions itself relative to any trigger element using pure CSS
  • A popover panel that auto-repositions itself when it overflows the viewport
  • A set of utility classes for top, bottom, left, and right anchor placements with fallback positions
  • A progressive enhancement wrapper so the experience degrades gracefully in older browsers

Prerequisites

  • Familiarity with CSS custom properties and the cascade
  • A project using plain HTML/CSS, Next.js, Astro, or any modern framework
  • Chrome 125+ or Edge 125+ for testing (Firefox 130+ and Safari 18+ for cross-browser validation)
  • Basic understanding of the HTML Popover API (introduced in Chrome 114)

Step 1: Understand the Anchor Positioning Mental Model

CSS Anchor Positioning lets you attach a positioned element to any other element in the DOM, regardless of where either element sits in the document tree. Before this spec, you needed JavaScript to read bounding rectangles and compute positions manually. That approach breaks on scroll, zoom, and dynamic content changes.

The system works through two declarations. First, you give the reference element an anchor-name. Second, you position the floating element using position-anchor and the anchor() function inside logical properties like top, left, bottom, and right.

The floating element must be absolutely or fixed positioned. This is non-negotiable. If your element is not positioned, the anchor functions have no effect.

What does the anchor() function actually do?

The anchor() function resolves to a pixel value at paint time. It reads the geometry of the named anchor element and returns the coordinate of whichever edge you request. For example, top: anchor(--my-anchor bottom) places the top edge of your floating element flush with the bottom edge of the anchor.

Step 2: Set Up the Base CSS File

Create a file called anchor-system.css at the root of your stylesheet directory. This file will hold all anchor utilities.

/* anchor-system.css */
/* Requires Chrome 125+, Edge 125+, Firefox 130+, Safari 18+ */

@layer anchor-base {

  /* Assign an anchor name to any element with this class */
  .anchor {
    anchor-name: --default-anchor;
  }

  /* Base styles for any floating element */
  .anchor-float {
    position: absolute;
    position-anchor: --default-anchor;
    margin: 0;
  }

  /* Placement utilities */
  .place-bottom {
    top: anchor(bottom);
    left: anchor(center);
    translate: -50% 0;
  }

  .place-top {
    bottom: anchor(top);
    left: anchor(center);
    translate: -50% 0;
  }

  .place-right {
    left: anchor(right);
    top: anchor(center);
    translate: 0 -50%;
  }

  .place-left {
    right: anchor(left);
    top: anchor(center);
    translate: 0 -50%;
  }

}

The @layer declaration keeps anchor utilities at a predictable specificity. You can override any placement from a component stylesheet without fighting cascade order.

Why use anchor(center) instead of a calc() expression?

The center keyword inside anchor() resolves to the midpoint of the named edge axis. Using it saves you from writing calc(anchor(left) + (anchor(right) - anchor(left)) / 2), which is valid but verbose and harder to maintain.

Step 3: Build a Named Anchor Pair for Components

When you have multiple anchored components on one page, --default-anchor will conflict. Each component needs a unique anchor name. Use a scoping convention like --anchor-[component-id].

<!-- Tooltip example -->
<button
  class="anchor"
  style="anchor-name: --tooltip-1"
  aria-describedby="tip-1"
>
  Hover me
</button>

<div
  id="tip-1"
  role="tooltip"
  class="anchor-float tooltip place-bottom"
  style="position-anchor: --tooltip-1"
>
  This is a CSS-only tooltip
</div>

Setting the anchor name and position-anchor as inline styles is intentional here. It lets you generate unique identifiers from your template or component layer without touching the stylesheet.

/* Component-level tooltip styles */
.tooltip {
  background: #1a1a2e;
  color: #fff;
  font-size: 0.875rem;
  padding: 0.375rem 0.75rem;
  border-radius: 6px;
  white-space: nowrap;
  pointer-events: none;
}

/* Visibility toggle via :hover on the anchor */
.anchor:not(:hover) + .tooltip {
  opacity: 0;
}

.anchor:hover + .tooltip {
  opacity: 1;
}

This approach requires the tooltip to be the immediate sibling of the trigger. If your markup cannot guarantee that, use the Popover API instead, which is covered in Step 5.

Step 4: Add Viewport-Aware Fallback Positions

The most powerful part of CSS Anchor Positioning is position-try-fallbacks. It lets the browser try alternative placements automatically when the default position would overflow the viewport.

/* In anchor-system.css, inside @layer anchor-base */

.anchor-float {
  position: absolute;
  position-anchor: --default-anchor;
  margin: 0;

  /* Try bottom first, then top if bottom overflows */
  position-try-fallbacks: --place-top;
}

@position-try --place-top {
  bottom: anchor(top);
  top: auto;
  left: anchor(center);
  translate: -50% 0;
}

The browser evaluates each @position-try block in order. It applies the first one that keeps the element fully within the containing block or viewport. This eliminates the need for JavaScript intersection observers on most tooltip and dropdown use cases.

What if my popover still overflows after all fallbacks?

Add overflow: auto and a max-height to your floating element as a last resort. The browser will not clip a positioned element automatically. You need to constrain it explicitly. Pair this with position-try-order: most-height to tell the browser to prefer the fallback that gives the most vertical space.

Step 5: Integrate with the HTML Popover API

The Popover API solves the DOM proximity problem. A popover element sits at the top layer, meaning it renders above all other content regardless of stacking context. Combining it with anchor positioning gives you a full overlay system in pure HTML and CSS.

<button
  popovertarget="settings-panel"
  style="anchor-name: --settings-btn"
>
  Settings
</button>

<div
  id="settings-panel"
  popover
  class="anchor-float place-bottom panel"
  style="position-anchor: --settings-btn"
>
  <p>Settings content goes here</p>
</div>
.panel {
  border: 1px solid #e2e8f0;
  border-radius: 8px;
  padding: 1rem;
  background: #fff;
  width: 240px;
  box-shadow: 0 4px 16px rgba(0,0,0,0.1);

  /* Reset default popover styles */
  margin: 0;

  /* Popover is hidden by default; override for anchor placement */
  position-try-fallbacks: --place-top;
}

[popover]:popover-open {
  display: block;
}

The popovertarget attribute handles show/hide toggle without JavaScript. The browser manages focus trapping and light-dismiss behaviour for free. This combination reduces the JavaScript footprint of a typical settings panel by roughly 80 to 90 lines of positioning and event handling code.

Step 6: Write a Progressive Enhancement Wrapper

Not every user will be on a fully supporting browser. Use a @supports query to serve a safe fallback.

/* Fallback for non-supporting browsers */
.anchor-float {
  display: none; /* Hide by default if no JS is controlling it */
}

/* Enable anchor system only when supported */
@supports (anchor-name: --test) {
  .anchor-float {
    display: revert;
    position: absolute;
    position-anchor: --default-anchor;
  }
}

For projects that must support older browsers, keep a lightweight JavaScript fallback using getBoundingClientRect() and set it to activate only when CSS.supports('anchor-name', '--test') returns false. This way the CSS version runs for the majority of users and the JS fallback runs for the rest.

Step 7: Test Across Browsers and Screen Sizes

Run your layout system against these four test cases before shipping.

  1. Trigger near the bottom edge of the viewport. The fallback should move the floating element above the anchor.
  2. Trigger in a scrollable container. Verify the popover follows the anchor during scroll.
  3. Trigger on a mobile screen at 375px width. Confirm the element does not clip horizontally.
  4. Trigger with keyboard navigation. Confirm the popover opens, receives focus, and closes on Escape.

Use Chrome DevTools with device emulation for cases 3 and 4. The Computed Styles panel now shows resolved anchor() values as of Chrome 125, which makes debugging placement issues considerably faster.

What if the floating element appears at position 0,0?

This almost always means the position-anchor name does not match the anchor-name exactly. CSS custom property names are case-sensitive. Double-check that --tooltip-1 and --tooltip-1 are identical on both sides.

Step 8: Document the System for Your Team

A layout utility only scales if the team knows how to use it. Add a usage comment block at the top of anchor-system.css.

/*
 * Anchor Positioning System v1.0
 * Requires: Chrome 125+, Edge 125+, Firefox 130+, Safari 18+
 *
 * Usage:
 * 1. Add `style="anchor-name: --unique-name"` to the trigger element.
 * 2. Add `class="anchor-float place-bottom"` and
 *    `style="position-anchor: --unique-name"` to the floating element.
 * 3. Add `position-try-fallbacks` for viewport-aware repositioning.
 *
 * Placements: place-top | place-bottom | place-left | place-right
 */

If your team also manages Figma files, this is a good moment to align the component states with the CSS placement system. Teams at Lenka Studio map anchor placement variants directly to Figma component properties so designers and developers are working from the same mental model.

Frequently Asked Questions

Does CSS Anchor Positioning work inside overflow: hidden containers?

No. A floating element positioned with position: absolute is clipped by an ancestor with overflow: hidden. Use position: fixed combined with the Popover API to escape the clipping context and render in the top layer.

Is this the same as CSS position: sticky?

No. Sticky positioning keeps an element within its scroll container. Anchor positioning attaches a floating element to a reference element anywhere in the document, regardless of scroll or DOM hierarchy.

Can I use CSS Anchor Positioning with React or Vue components?

Yes. Generate a unique anchor name as a prop or a computed value, pass it as an inline style, and apply the same CSS classes. Most teams derive the anchor name from the component's ID or a useId() hook in React.

What happens if two elements share the same anchor-name?

The browser uses the last declared anchor with that name in the DOM order. This will produce unexpected positions. Always use unique anchor names per component instance, especially in lists or repeated UI elements.

Does this replace Floating UI or Popper.js entirely?

For projects targeting modern browsers only, CSS Anchor Positioning covers the majority of tooltip, dropdown, and popover use cases without a JavaScript dependency. Floating UI remains useful for complex scenarios like virtual scrolling lists, sub-menus, or environments with heavy browser support requirements below the 2025 baseline.

Next Steps

Your anchor positioning system is now production-ready for modern browsers. From here, consider extending it in these directions.

  • Add animation: pair @starting-style with transition on the popover to animate entry and exit without JavaScript.
  • Build a Storybook story for each placement utility so new team members can preview them in isolation.
  • Audit your existing tooltip and dropdown components and migrate them one at a time, starting with the highest-traffic UI.
  • Check your overall site health and brand consistency while you are refactoring. The free brand health score assessment from Lenka Studio is a useful checkpoint when you are making systematic changes to your product UI.

If you are building a design system from scratch or modernising an existing one and want a team that has done this across real production products, the team at Lenka Studio is happy to talk through your setup. Get in touch and we can help you scope what makes sense for your stack and timeline.