anchor-name でアニメーションするタブインジケーター

アクティブなタブに anchor-name を付け、CSS トランジションで単一の下線インジケーターをアクティブタブの下にスライドさせる。

UI Patterns

詳細な説明

パターン

よくあるデザインパターン:上部にタブが並び、現在アクティブなタブの下に単一の水平アンダーラインがスライドする。アンカーポジショニング以前は、JavaScriptでタブ位置を計測するか、タブごとに別アンダーラインを用意するかの二択でした。アンカーポジショニングなら、anchor-name を持つタブに単一のアンダーラインが追従できます。

<nav class="tabs">
  <button class="tab" data-active>概要</button>
  <button class="tab">アクティビティ</button>
  <button class="tab">設定</button>
  <span class="indicator"></span>
</nav>
/* アクティブなタブだけが anchor-name を持つ */
.tab[data-active] {
  anchor-name: --active-tab;
}

.indicator {
  position: absolute;
  position-anchor: --active-tab;
  /* アンダーライン:タブの全幅、タブ下端にぴったり */
  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;
}

なぜ position-area ではなく anchor()

position-area: bottom だとインジケーターはタブの下に別ボックスとして配置されますが、ここではインジケーターを タブの左右端と正確に同じ幅 にしたいのです。anchor() 関数なら辺ごとに制御できます。left: anchor(--active-tab left)right: anchor(--active-tab right) でインジケーターがアクティブタブの全幅に自動的に広がります。

JavaScriptでアンカー名を移動

トランジションはインジケーターの解決位置が変わったときだけアニメーションします。タブからタブへ移動するには、どのタブが data-active 属性を持つかを変更します:

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

data-active が動くと anchor-name: --active-tab も一緒に動き、インジケーターの解決位置が変わり、CSSトランジションがアニメーションを処理します。

単一の真実のソース

このパターンの良いところは、タブの数に関わらずDOMにインジケーター要素が1つだけ存在することです。タブごとアプローチ(タブごとに1つのアンダーライン、アクティブなものだけ表示)と比べて、タブ数に対して線形にスケールせず、追加のopacity/visibilityロジックも不要です。

ユースケース

アプリバー・設定パネル・ダッシュボードのタブナビゲーション。長いページのセクションナビゲーション(インジケーターが現在表示中のセクションに追従)。ネストしたルート階層のサブナビゲーション。アクティブ状態を持つフィルターチップ。

試してみるCSS アンカーポジショニング ジェネレーター

フルツールを開く