Animated Tab Indicator with anchor-name

Slide a single underline indicator under the active tab using anchor-name on the active tab and CSS transitions on the indicator.

UI Patterns

Detailed Explanation

The Pattern

A common design pattern: tabs across the top, with a single horizontal underline that slides under the currently-active tab. Pre-anchor-positioning, this required either measuring tab positions in JavaScript or using a separate underline per tab. Anchor positioning lets one underline element follow whichever tab has anchor-name.

<nav class="tabs">
  <button class="tab" data-active>Overview</button>
  <button class="tab">Activity</button>
  <button class="tab">Settings</button>
  <span class="indicator"></span>
</nav>
/* Only the active tab gets the anchor name */
.tab[data-active] {
  anchor-name: --active-tab;
}

.indicator {
  position: absolute;
  position-anchor: --active-tab;
  /* Underline: full width of the tab, flush at the tab's bottom edge */
  left: anchor(--active-tab left);
  right: anchor(--active-tab right);
  top: anchor(--active-tab bottom);
  height: 2px;
  background: var(--primary);
  transition:
    left 200ms ease,
    right 200ms ease,
    top 200ms ease;
}

Why anchor() instead of position-area?

position-area: bottom would place the indicator below the tab as a separate box, but you want the indicator to stretch to exactly the tab's left and right edges. The anchor() function gives you per-edge control. Setting left: anchor(--active-tab left) and right: anchor(--active-tab right) makes the indicator span the full width of the active tab automatically.

Move the anchor name in JavaScript

The transition only animates if the indicator's resolved position changes. To move it from one tab to another, change which tab has the data-active attribute:

function activate(tab) {
  document.querySelectorAll('.tab[data-active]')
    .forEach(t => t.removeAttribute('data-active'));
  tab.setAttribute('data-active', '');
}

When data-active moves, anchor-name: --active-tab moves with it, the indicator's resolved position changes, and CSS transitions handle the animation.

Single-source-of-truth styling

A nice property of this pattern: there's exactly one indicator element in the DOM, regardless of tab count. Compare with the per-tab approach (one underline per tab, only the active one visible) which scales linearly with tab count and requires extra opacity/visibility logic.

Use Case

Tab navigation in app bars, settings panels, and dashboards. Section navigation in long pages (where the indicator follows the currently-visible section). Sub-navigation in nested route hierarchies. Filter chips with an active state.

Try ItCSS Anchor Positioning Generator

Open full tool