本文へスキップ
  1. Web BENTO JS
  2. カテゴリ一覧
  3. タブ切り替え
02 / TAB 12 パーツ

JSタブ切り替え実装 12選

コピペで動くタブ切り替えのサンプル集です。プレビューは実際にクリックで切り替えできます。HTML / CSS / JS タブでコードを確認し、全部コピーを押せば1回の貼り付けで完成します。すべて依存ライブラリなしのバニラJSで、data-bnto-* 属性による自動初期化式です。

該当するパーツが見つかりませんでした。

もっとも基本的なタブ。クリックでパネルが切り替わります。
role="tab" と aria-selected はJSが自動更新します。
タブとパネルを同じ順番で並べるだけでOK。

ベーシック

<!-- タブとパネルは同じ順番で並べる -->
<div class="bnto-tab" data-bnto-tab>
  <div class="tl" role="tablist" aria-label="サンプルタブ">
    <button type="button" class="tb is-active" role="tab" aria-selected="true">タブ1</button>
    <button type="button" class="tb" role="tab" aria-selected="false">タブ2</button>
    <button type="button" class="tb" role="tab" aria-selected="false">タブ3</button>
  </div>
  <div class="pn is-active" role="tabpanel">パネル1の中身</div>
  <div class="pn" role="tabpanel">パネル2の中身</div>
  <div class="pn" role="tabpanel">パネル3の中身</div>
</div>
使い方のコツ

最初に表示するタブを変えたいときは、is-activearia-selected="true" の位置をずらすだけです。タブの数を増やす場合は、ボタンとパネルを同じ順番で追加してください。

アクティブな下線が transform でスルッと移動します。
タブごとの幅の違いにも自動で追従します。
下線の位置と幅はJSが計算しています。

下線スライド

<div class="bnto-tab2" data-bnto-tab-line>
  <div class="tl" role="tablist" aria-label="サンプルタブ">
    <button type="button" class="tb is-active" role="tab" aria-selected="true">タブ1</button>
    <button type="button" class="tb" role="tab" aria-selected="false">タブ2</button>
    <button type="button" class="tb" role="tab" aria-selected="false">タブ3</button>
    <!-- スライドする下線バー -->
    <span class="bar" aria-hidden="true"></span>
  </div>
  <div class="pn is-active" role="tabpanel">パネル1の中身</div>
  <div class="pn" role="tabpanel">パネル2の中身</div>
  <div class="pn" role="tabpanel">パネル3の中身</div>
</div>
使い方のコツ

タブ名の長さがバラバラでも、下線の幅はJSが自動計算するのでOKです。Webフォント読み込みで幅が変わるケースも、リサイズ時の再計算でカバーしています。バーを太く(height)すると存在感が出ます。

背景のピルがスライドするセグメント風タブ。
期間切替や表示モードの切替にぴったりです。
色は --c1 を変えるだけで着せ替えできます。

ピル型(セグメント)

<div class="bnto-tab3" data-bnto-tab-pill>
  <div class="tl" role="tablist" aria-label="サンプルタブ">
    <!-- スライドする背景ピル -->
    <span class="pill" aria-hidden="true"></span>
    <button type="button" class="tb is-active" role="tab" aria-selected="true">タブ1</button>
    <button type="button" class="tb" role="tab" aria-selected="false">タブ2</button>
    <button type="button" class="tb" role="tab" aria-selected="false">タブ3</button>
  </div>
  <div class="pn is-active" role="tabpanel">パネル1の中身</div>
  <div class="pn" role="tabpanel">パネル2の中身</div>
  <div class="pn" role="tabpanel">パネル3の中身</div>
</div>
使い方のコツ

iOSのセグメントコントロール風のタブです。設定画面・料金の「月額/年額」切替・グラフの期間切替などに好相性です。角の丸みを border-radius: 12px くらいに下げると、落ち着いた印象になります。

パネルがふわっとフェードインします。
displayの切替に animation を重ねるだけの軽量実装。
アニメ時間は --dur で調整できます。

フェード切替

<div class="bnto-tab4" data-bnto-tab-fade>
  <div class="tl" role="tablist" aria-label="サンプルタブ">
    <button type="button" class="tb is-active" role="tab" aria-selected="true">タブ1</button>
    <button type="button" class="tb" role="tab" aria-selected="false">タブ2</button>
    <button type="button" class="tb" role="tab" aria-selected="false">タブ3</button>
  </div>
  <div class="pn is-active" role="tabpanel">パネル1の中身</div>
  <div class="pn" role="tabpanel">パネル2の中身</div>
  <div class="pn" role="tabpanel">パネル3の中身</div>
</div>
使い方のコツ

display の切替に animation を重ねるだけなので、高さのガタつきや transition の管理が不要です。translateY の値を大きくするとスライド感が強まり、0 にすれば純粋なフェードになります。

アイコン+ラベルのタブ。SVGは自由に差し替えOK。
アイコンの色は currentColor で文字色に追従します。
モバイルの下部ナビ風レイアウトにも使えます。

アイコン付き

<div class="bnto-tab5" data-bnto-tab-ico>
  <div class="tl" role="tablist" aria-label="サンプルタブ">
    <button type="button" class="tb is-active" role="tab" aria-selected="true">
      <!-- SVGアイコンは好きなものに差し替えOK -->
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><path d="M9 22V12h6v10"/></svg>
      ホーム
    </button>
    <button type="button" class="tb" role="tab" aria-selected="false">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z"/></svg>
      いいね
    </button>
  </div>
  <div class="pn is-active" role="tabpanel">パネル1の中身</div>
  <div class="pn" role="tabpanel">パネル2の中身</div>
</div>
使い方のコツ

アイコンは Lucide などのSVGをそのまま貼り替えできます。ラベルを省略してアイコンだけにする場合は、ボタンに aria-label="ホーム" を必ず付けてください。

左に縦並びタブ、右にパネルのレイアウトです。
項目が多いときや、ラベルが長いときに便利。
メディアクエリで横並びへの切替もかんたん。

縦タブ

<div class="bnto-tab6" data-bnto-tab-v>
  <div class="tl" role="tablist" aria-label="サンプルタブ" aria-orientation="vertical">
    <button type="button" class="tb is-active" role="tab" aria-selected="true">タブ1</button>
    <button type="button" class="tb" role="tab" aria-selected="false">タブ2</button>
    <button type="button" class="tb" role="tab" aria-selected="false">タブ3</button>
  </div>
  <div class="pns">
    <div class="pn is-active" role="tabpanel">パネル1の中身</div>
    <div class="pn" role="tabpanel">パネル2の中身</div>
    <div class="pn" role="tabpanel">パネル3の中身</div>
  </div>
</div>
使い方のコツ

スマホでは @media (max-width: 600px) { .bnto-tab6 { flex-direction: column; } .bnto-tab6 .tl { flex-direction: row; width: 100%; } } のように横並びへ切り替えるのがおすすめです。

←→キーでタブ移動、Home / End で先頭・末尾へ。タブをクリックしてからキーボードで試してみてください。

キーボード対応(WAI-ARIA準拠)

<!-- id / aria-controls / tabindex はJSが自動で付与 -->
<div class="bnto-tab7" data-bnto-tab-key>
  <div class="tl" role="tablist" aria-label="サンプルタブ">
    <button type="button" class="tb is-active" role="tab" aria-selected="true">タブ1</button>
    <button type="button" class="tb" role="tab" aria-selected="false">タブ2</button>
    <button type="button" class="tb" role="tab" aria-selected="false">タブ3</button>
  </div>
  <div class="pn" role="tabpanel">パネル1の中身</div>
  <div class="pn" role="tabpanel" hidden>パネル2の中身</div>
  <div class="pn" role="tabpanel" hidden>パネル3の中身</div>
</div>
使い方のコツ

WAI-ARIA Authoring Practices の「Tabs パターン」に沿った実装です。Tabキーはタブ列を1ストップとして扱い、タブ間の移動は矢印キーで行うのが正しい作法です。公共系サイトやアクセシビリティ要件のある案件はこれを選んでください。

切り替えるとURL末尾が #tab1 のように変わります(replaceStateなので履歴は汚しません)。
#tab2 付きのURLを開くと、このタブが最初から選ばれた状態になります。
特定のタブへの直リンクを共有したいときに便利です。

URLハッシュ連動

<!-- パネルの id がそのままURLの # になる(ページ内で一意に) -->
<div class="bnto-tab8" data-bnto-tab-hash>
  <div class="tl" role="tablist" aria-label="サンプルタブ">
    <button type="button" class="tb is-active" role="tab" aria-selected="true" aria-controls="tab1">タブ1</button>
    <button type="button" class="tb" role="tab" aria-selected="false" aria-controls="tab2">タブ2</button>
    <button type="button" class="tb" role="tab" aria-selected="false" aria-controls="tab3">タブ3</button>
  </div>
  <div class="pn is-active" role="tabpanel" id="tab1">パネル1の中身</div>
  <div class="pn" role="tabpanel" id="tab2">パネル2の中身</div>
  <div class="pn" role="tabpanel" id="tab3">パネル3の中身</div>
</div>
使い方のコツ

パネルの id がそのままURLに載るので、#price のように意味の分かる名前にすると共有時に親切です。ページ内の他の要素と id が重複しないよう注意してください。

パネルを左右にスワイプしても切り替わります。PCではドラッグでお試しください。
40px以上動かすと隣のタブへ。それ未満なら元の位置へスッと戻ります。
Pointer Events なので、タッチもマウスも同じコードで動きます。

スワイプ対応タブ

<!-- パネルは .trk の中に横並び。タブと同じ順番で並べる -->
<div class="bnto-tab9" data-bnto-tab-swipe>
  <div class="tl" role="tablist" aria-label="サンプルタブ">
    <button type="button" class="tb is-active" role="tab" aria-selected="true">タブ1</button>
    <button type="button" class="tb" role="tab" aria-selected="false">タブ2</button>
    <button type="button" class="tb" role="tab" aria-selected="false">タブ3</button>
  </div>
  <div class="vp">
    <div class="trk">
      <div class="pn" role="tabpanel">パネル1の中身</div>
      <div class="pn" role="tabpanel">パネル2の中身</div>
      <div class="pn" role="tabpanel">パネル3の中身</div>
    </div>
  </div>
</div>
使い方のコツ

スマホアプリのような操作感を出せるタブです。PCではドラッグでも試せます。切り替えのしきい値はJS内の 40(px)を変えるだけで調整できます。touch-action: pan-y を指定しているので、縦スクロールの邪魔はしません。端のタブでさらにスワイプした場合は元の位置に戻ります。

3秒ごとに自動で次のタブへ切り替わります。カーソルを乗せている間は一時停止します。
キーボードでフォーカスしている間も止まるので、操作の邪魔をしません。
切替間隔は data-interval(ミリ秒)で調整できます。

自動ローテーションタブ

<!-- data-interval は切替間隔(ミリ秒。省略時は3000) -->
<div class="bnto-tab10" data-bnto-autotab data-interval="3000">
  <div class="tl" role="tablist" aria-label="サンプルタブ">
    <button type="button" class="tb is-active" role="tab" aria-selected="true">タブ1</button>
    <button type="button" class="tb" role="tab" aria-selected="false">タブ2</button>
    <button type="button" class="tb" role="tab" aria-selected="false">タブ3</button>
  </div>
  <div class="pn is-active" role="tabpanel">パネル1の中身</div>
  <div class="pn" role="tabpanel">パネル2の中身</div>
  <div class="pn" role="tabpanel">パネル3の中身</div>
</div>
使い方のコツ

お知らせ欄やランキング表示など「放っておいても中身が入れ替わる」演出に向いています。自動で動くUIは読んでいる途中に切り替わるとストレスになるため、ホバー / フォーカス中は必ず止めるのが定石です(実装済み)。動きを減らす設定(prefers-reduced-motion)のユーザーには自動切替を行いません。

選んだタブを localStorage に保存します。ページを再読み込みしても選択が復元されます。
このタブを選んで再読み込みすると、料金タブが開いたまま表示されます。
同じページに複数置くときは data-key で保存名を分けてください。

選択を記憶するタブ

<!-- 複数設置するなら data-key="別名" で保存名を分ける -->
<div class="bnto-tab11" data-bnto-memorytab>
  <div class="tl" role="tablist" aria-label="サンプルタブ">
    <button type="button" class="tb is-active" role="tab" aria-selected="true">タブ1</button>
    <button type="button" class="tb" role="tab" aria-selected="false">タブ2</button>
    <button type="button" class="tb" role="tab" aria-selected="false">タブ3</button>
  </div>
  <div class="pn is-active" role="tabpanel">パネル1の中身</div>
  <div class="pn" role="tabpanel">パネル2の中身</div>
  <div class="pn" role="tabpanel">パネル3の中身</div>
</div>
使い方のコツ

管理画面や設定ページなど「前回の続きから見たい」場面に向いています。保存されるのはタブの番号(インデックス)なので、タブの並び順を変えると復元位置もずれる点に注意してください。同じサイトに複数置くときは data-key="plan-tab" のように別名を付ければ、それぞれ独立して記憶されます。

幅に入り切らないタブは、右端の「その他 ▾」に自動で格納されます。
ウィンドウ幅を変えると、入る本数を自動で数え直します(ResizeObserver)。
「その他」の中の項目を選ぶと、そのパネルが表示されます。
タブが全部入る幅なら「その他」ボタンは自動で消えます。
メニューの項目は、タブのテキストからJSが自動生成しています。

溢れタブの「その他」格納

<!-- タブは何本でもOK。入り切らない分は「その他」へ自動格納 -->
<div class="bnto-tab12" data-bnto-overflowtab>
  <div class="tl" role="tablist" aria-label="サンプルタブ">
    <button type="button" class="tb is-active" role="tab" aria-selected="true">タブ1</button>
    <button type="button" class="tb" role="tab" aria-selected="false">タブ2</button>
    <button type="button" class="tb" role="tab" aria-selected="false">タブ3</button>
    <button type="button" class="tb" role="tab" aria-selected="false">タブ4</button>
    <button type="button" class="tb" role="tab" aria-selected="false">タブ5</button>
    <!-- 溢れたタブの受け皿(メニューの中身はJSが自動生成) -->
    <div class="mr">
      <button type="button" class="more" aria-haspopup="true" aria-expanded="false">その他 <span aria-hidden="true">▾</span></button>
      <div class="menu" hidden></div>
    </div>
  </div>
  <div class="pn is-active" role="tabpanel">パネル1の中身</div>
  <div class="pn" role="tabpanel">パネル2の中身</div>
  <div class="pn" role="tabpanel">パネル3の中身</div>
  <div class="pn" role="tabpanel">パネル4の中身</div>
  <div class="pn" role="tabpanel">パネル5の中身</div>
</div>
使い方のコツ

タブの本数が多い・可変・多言語で幅が読めない、といったケースの決定版です。ResizeObserver が幅の変化を監視するので、ウィンドウリサイズでもサイドバーの開閉でも自動で追従します。メニューの項目はタブのテキストから自動生成されるため、タブを増やすときは .tb.pn を同じ順番で足すだけです。「その他」の文言はHTMLを書き換えるだけで変更できます。

JSタブ切り替え実装の基礎知識

タブ切り替えは、複数のコンテンツを同じ場所で切り替えて限られたスペースに情報を整理する定番UIです。商品説明とレビューとQ&A、料金プランの比較、期間別のデータ表示など「並列な情報をコンパクトに見せたい」場面で活躍します。このページのサンプルはすべて依存ライブラリなしのバニラJavaScriptで、jQueryもフレームワークも不要です。

使い方は、HTML / CSS / JSの3タブをそれぞれ貼り付けるか、「全部コピー」ボタンでひとまとめになったコードを1回貼り付けるだけです。初期化は data-bnto-* 属性の自動検出式なので、同じページに何個置いても、うっかりコードを2回貼っても壊れません。

切り替えアニメーションの考え方

パネルの切り替えは display の切替が基本ですが、それだけだと動きが硬く見えます。「下線スライド」「ピル型」はインジケーターを transform で動かし、「フェード切替」は表示された瞬間に animation を走らせる方式です。どちらもGPUで処理される軽い実装で、パネルの高さ計算は不要です。

アクセシビリティ:WAI-ARIAのタブパターン

タブには role="tablist" / role="tab" / role="tabpanel"aria-selected を付けるのが基本形です。さらに本格対応するなら、タブ間は矢印キーで移動し、Tabキーではタブ列を1ストップとして扱う「ロービングタブインデックス」が推奨です。「キーボード対応タブ」はこのWAI-ARIA Authoring Practicesのタブパターンに準拠しています。ボタンには必ず <button> 要素を使ってください。

SEOとの関係

タブで隠れたパネルも、HTMLとして存在していればGoogleにインデックスされます(モバイルファーストインデックスでは非表示コンテンツも評価対象)。ただしユーザーが最初に目にするのはアクティブなタブだけなので、重要な情報は1つ目のタブに置くのが無難です。特定のタブを直接見せたい場合は「URLハッシュ連動タブ」で #id 付きのリンクを共有できます。

「全部コピー」と「分けて貼る」の使い分け

「全部コピー」を使うと、HTML・CSS・JavaScriptがひとつの自己完結ブロックとしてコピーされるので、1回の貼り付けだけですぐ動きを確認できます。動作のお試しや、1ページ完結のLPに組み込むときはこの方法が手軽です。

一方、本番サイトへ本格的に組み込む場合は、CSSはスタイルシート(.cssファイル)へ、JavaScriptは</body>直前や共通の.jsファイルへ、HTMLは使いたい場所へと分けて貼るのが一般的です。スタイルとスクリプトを1か所に集約できるため、複数ページ・複数パーツで使い回すときの管理がしやすく、ブラウザのキャッシュも効きやすくなります。なお、同じパーツをページ内で何度も使う場合でも、CSSとJSを貼るのは1回だけで大丈夫です(初期化スクリプトが該当要素すべてに自動で効きます)。

よくある質問

コピーしたタブはそのまま動きますか?
はい。「全部コピー」を押すとHTML・CSS・JSがひとつの自己完結ブロックになってコピーされ、ページに貼り付けるだけで動きます。ライブラリの読み込みは一切不要です。
同じページに複数設置しても大丈夫ですか?
大丈夫です。初期化スクリプトは data 属性で対象を自動検出し、初期化済みの要素はスキップするため、複数設置してもコードを2回貼っても壊れません。
キーボードだけで操作できますか?
「キーボード対応タブ」はWAI-ARIAのタブパターンに準拠しており、左右の矢印キーでタブを移動、Home / End で先頭・末尾へジャンプできます。フォーカスはロービングタブインデックスで管理しています。
タブで隠れたコンテンツはSEOに不利になりませんか?
非表示のパネルもHTMLとして存在していればGoogleにインデックスされます。ただし重要な情報は最初に表示されるタブに置くのが無難です。URLハッシュ連動タブを使えば、特定のタブへの直リンクも共有できます。
このページのパーツはAIコーディングツールでも使えますか?
はい。Claude CodeやCursorのようにWebページを読めるAIには、このページのURLとパーツ名(例:「下線スライド」)を伝えるだけで実装を指示できます。AIがWebを読めない場合は、「全部コピー」で取得した自己完結ブロックをチャットに貼り付けて調整を頼むのが確実です。改変後のコードは、公開前に動作確認することをおすすめします。

関連カテゴリ

タブ切り替えと組み合わせて使いやすいパーツはこちらです。