本文へスキップ

下線スライドタブ

選択中タブの下に下線がスッと横移動するタブUI。現在地が直感的に伝わり、内容も切り替わる。

プレビュー

クリック

操作クリック/タップで動きます。

仕様書

向く場面
  • 同列の内容を2〜5個で切り替える設定・詳細画面
  • 何度も行き来するセクション切替
  • 現在地と移動方向を同時に伝えたいとき
避ける場面
  • タブが6個超・ラベルが長く均等割りで潰れる
  • パネルの高さが大きく異なる内容
  • 別ページへ遷移するナビ(リンクにする)
構成要素
  • タブ列
  • タブ
  • 下線インジケータ
  • パネル
持っている状態
  • 通常
  • 押下中
キーボード

Tab でタブ列へ(選択中のタブだけ tabindex=0)・←→ で隣へ移動と同時に選択(末尾で循環)・Home/End で両端・Enter/Space でも選択

支援技術

APG Tabs: role=tablist/tab/tabpanel、aria-selected と aria-controls/aria-labelledby で結び、ローミング tabindex。非選択パネルは display:none で読み上げ・フォーカス対象外。下線は aria-hidden

動き

下線の left/width を 0.28s cubic-bezier(.4,0,.2,1) で滑らせ移動方向を伝える。文字色 0.2s。reduced-motion では即時

デザインの要点

  • 下線は選択中タブの offsetLeft/offsetWidth から置く。幅の違うタブへ移るとき伸縮しながら滑り、どこへ動いたかが目で追える
  • 非選択パネルは opacity ではなく display:none。見えないパネルの中のリンクに Tab が入る事故を構造で防ぐ
  • タブ列は Tab 1回で通り抜ける(選択中だけ tabindex=0)。タブ間の移動は ←→ に任せ、Home/End で両端へ
  • Webフォントの読み込み完了とリサイズで下線を測り直す。初期計測のままだとフォント差し替え後に下線がずれて残る

使いどころ

2〜5個程度の並列した切替先(概要/仕様/レビューのような同列コンテンツ)を1画面に収めたいときに向く。下線が横移動することで「今どこにいるか」と「どちらへ動いたか」の両方が伝わるため、設定画面やプロフィール画面のセクション切替など、ユーザーが何度も行き来する場所で効果が大きい。逆に、タブの数が可変で6個を超えたり、ラベルが長文になる場合には向かない。flex:1で均等割りする実装のため、タブが増えるほど1個あたりの幅が狭くなり、折り返しや文字の潰れが起きる。そうした場面は横スクロール式のタブか、ドロップダウンでの選択に切り替えるべきだ。また、パネルの中身が大きく異なる(フォームとグラフなど)場合は、切替のたびにレイアウトの高さが大きく変わり、下線のアニメーションよりもガクつきの方が目立つので注意する。

仕組み

下線要素 .tab-ink を tab-row 内に1つだけ置き、クリックされたタブの offsetLeft と offsetWidth を読んでその値を left/width に反映させることで、タブ間を移動しているように見せる。left と width は両方 transition の対象になっており、幅が違うタブへ移動するときは伸び縮みしながら滑る。中身の切替は各タブ・各パネルの is-active クラスを付け替えるだけの単純な仕組みで、パネルは display:none で完全に非表示にする(opacity操作ではないため、非表示中はレイアウトにも操作可能要素の判定にも影響しない)。フォント読み込み完了時(document.fonts.ready)とウィンドウリサイズ時にも moveInk() を呼び直しており、Webフォントの差し替えでタブ幅が変わったときに下線がずれたまま固定される事故を防いでいる。

実装の注意

offsetLeft/offsetWidth を使う実装は、タブの幅がJSの実行タイミングと実際の描画で食い違うと下線がずれる。典型例はWebフォントの読み込み待ちで、フォント切り替え前の幅で計算した位置がそのまま残ってしまうケースだが、これは document.fonts.ready で手当て済みなので流用時も消さないこと。もう一つは、タブ数が増減するUI(動的にタブを足し引きする管理画面など)で、タブを追加・削除した直後に moveInk(activeTab()) を呼び直す処理が抜けやすい点。呼び忘れると新しいレイアウトに対して古い座標のまま下線が浮く。また、tab-row は display:flex で各 .tab は flex:1 で均等割りしているだけで overflow-x の逃げ場がないため、モバイル幅でタブ数やラベル長を増やすなら、横スクロール可能なコンテナへ作り替える設計変更とセットで検討する必要がある。

アクセシビリティ

マークアップは WAI-ARIA の Tabs パターンに沿わせている。タブ列に role="tablist"、各ボタンに role="tab" と aria-selected、各パネルに role="tabpanel" を付け、aria-controls と aria-labelledby で id を相互に結ぶ。これでスクリーンリーダーには「タブの集合であること」「今どれが選ばれているか」「4つ中の何番目か」が伝わる。キーボードは Tabs パターンの定石どおりローミング tabindex にしてあり、Tab キーでタブ列に入ると選択中のタブだけに止まり、左右矢印で隣へ移動すると同時に選択も切り替わる(Home/End で両端へ)。activate() が is-active と一緒に aria-selected と tabindex を更新する一箇所で状態を持つので、後からタブを足しても属性の同期漏れが起きにくい。パネル側は display:none で確実に非表示になっているので、非選択タブの内容がキーボードで誤って触れてしまう心配はない。下線の span は装飾なので aria-hidden にしてある。

調整して使う

触る値は下線の太さ・色・移動速度の3つが中心。デモは2px・#2b50c7・0.28sだが、下線を太く(3〜4px)して角丸を付けると「タブというよりピル型セグメント」に近い印象になる。移動のイージングを cubic-bezier で伸び縮みを強調すると遊び心が出る一方、業務画面では linear に近い方が落ち着く。下線ではなく背景のピル(is-active のタブ全体に色を敷く)に切り替える変化球もあり、その場合は left/width の代わりに transform: translateX() でピル自体を動かす実装になる。タブ数が多い構成に育てる予定があるなら、早い段階で横スクロール対応のレイアウトに置き換えておく方が後の手戻りが少ない。

コード(コピーして使えます)

<div class="tabs">
  <div class="tab-row" role="tablist" aria-label="製品情報">
    <button class="tab is-active" id="tab-0" role="tab" aria-selected="true" aria-controls="tabpanel-0" tabindex="0" data-tab="0">概要</button>
    <button class="tab" id="tab-1" role="tab" aria-selected="false" aria-controls="tabpanel-1" tabindex="-1" data-tab="1">仕様</button>
    <button class="tab" id="tab-2" role="tab" aria-selected="false" aria-controls="tabpanel-2" tabindex="-1" data-tab="2">レビュー</button>
    <span class="tab-ink" aria-hidden="true"></span>
  </div>
  <div class="tab-panels">
    <div class="tab-panel is-active" id="tabpanel-0" role="tabpanel" aria-labelledby="tab-0" tabindex="0">
      <p>このパーツの概要をここに書きます。使い方がすぐわかるシンプルなタブです。</p>
    </div>
    <div class="tab-panel" id="tabpanel-1" role="tabpanel" aria-labelledby="tab-1" tabindex="0">
      <p>対応ブラウザ: 最新Chrome / Firefox / Safari。依存ライブラリ: なし。</p>
    </div>
    <div class="tab-panel" id="tabpanel-2" role="tabpanel" aria-labelledby="tab-2" tabindex="0">
      <p>「現在地がわかりやすい」「アニメが自然」と好評です。</p>
    </div>
  </div>
</div>

タグ

同じカテゴリ「ナビ・リスト操作」のほかのパーツ

関連パーツ