Entry 06

Indicator that slides to the active item

Use it for the underline on a tab row or the bar beside a side nav. It replaces a script that measures the active item and writes a transform and a width.

Open
Click the links in each row. The bar slides under that row's current link and takes its width. The bar in the other row does not move, because each row scopes its own anchor.
Code HTML · CSS · 2 blocks · 34 lines
<nav class="tabs">
  <ul>
    <li><a href="/overview" aria-current="page">Overview</a></li>
    <li><a href="/pricing">Pricing and billing</a></li>
    <li><a href="/docs">Docs</a></li>
  </ul>
</nav>
.tabs { anchor-scope: --active; }

.tabs ul {
  position: relative;
  display: flex;
}

.tabs [aria-current="page"] { anchor-name: --active; }

.tabs ul::after {
  content: "";
  position: absolute;
  position-anchor: --active;
  inset-block-end: 0;
  inset-inline-start: anchor(start);
  inline-size: anchor-size(inline);
  block-size: 2px;
  background: currentColor;
}

@media (prefers-reduced-motion: no-preference) {
  .tabs ul::after {
    transition:
      inset-inline-start 0.25s var(--ease-out, ease-out),
      inline-size 0.25s var(--ease-out, ease-out);
  }
}
Why it works, the rules and the credits 3 reasons · 4 rules · support · credits

Why it works

  1. The anchor name sits on whichever item carries aria-current. Move the attribute and the anchor moves with it. The same attribute tells a screen reader which item is current, so there is no separate "active" class to keep in sync.
  2. anchor() and anchor-size() resolve to the item's edge and size. A longer label, a larger font or a wrapped line needs no new measurement.
  3. The bar is one element whose inset and size change, so a plain transition slides it.

Rules

  1. Keep anchor-scope on the component root. Without it an anchor name is global, and two navs on one page both follow the last active item.
  2. The list needs position: relative, so the bar is positioned against the box that holds the items.
  3. For a vertical list swap the axes. Use inset-block-start: anchor(top) and block-size: anchor-size(height), and transition those two.
  4. Give the active item a second cue such as weight or color. A browser without anchor positioning drops the anchor() declarations and the bar has no width.

Support

Chrome
131
Firefox
147
Safari
26

Firefox places the bar and does not slide it.

Credits

Borrowed from: Stripe, which ships the vertical form for a side nav in its home page stylesheet. The research found the rules and no page that renders the component, so this is from the stylesheet alone.

Docs: MDN: anchor-name · MDN: anchor-scope · MDN: anchor-size()