HTMLページでタブUIを実装する

ぐゎもう
質問: HTMLページでのタブUI
HTMLページ内でタブで切り替えられるUIを実装したいんだ。どんなコードで実現できるか教えてほしいな。

実装方針

HTMLページでタブのUIを実装する方法として

  • Bootstrap のタブコントロールを利用する
  • HTML+CSS+JavaScriptでタブを実装する
  • アクティブなタブをそれぞれのページで実装しページを切り替える

の3つの方法があります。今回は、HTML+CSS+JavaScriptで実装する方法を紹介します。

プログラム

注意
本記事のコードは複数のLLMでの検討により作成されたコードです。

以下のHTMLファイルを作成します。

SimpleTab.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title></title>
    <link href="SimpleTab.css" rel="stylesheet" />
</head>
<body>
  <h1>タブのUIデモ</h1>

  <div class="tabs" role="tablist" aria-label="タブ">
    <button class="tab-btn" role="tab" id="t-01" aria-controls="p-01" aria-selected="true">タブ No.1</button>
    <button class="tab-btn" role="tab" id="t-02" aria-controls="p-02" aria-selected="false">タブ No.2</button>
    <button class="tab-btn" role="tab" id="t-03" aria-controls="p-03" aria-selected="false">タブ No.3</button>
  </div>

  <section class="tab-panel is-active" role="tabpanel" id="p-01" aria-labelledby="t-03">
    <p>タブ No.1 のコンテンツ</p>
    <p>あああ</p>
    <p>いいい</p>
    <p>ううう</p>
  </section>
  <section class="tab-panel" role="tabpanel" id="p-02" aria-labelledby="t-02">
    <p>タブ No.2 のコンテンツ</p>
    <p></p>>AAA</p>
    <p>BBB</p>
    <p>CCC</p>
  </section>
  <section class="tab-panel" role="tabpanel" id="p-03" aria-labelledby="t-03">
    <p>タブ No.3 のコンテンツ</p>
    <p>111</p>
    <p>222</p>
    <p>333</p>
  </section>


  <script>
  const tabs = [...document.querySelectorAll('.tab-btn')];
  const panels = new Map(tabs.map(t => [t.id, document.getElementById(t.getAttribute('aria-controls'))]));

  function activate(tab) {
    tabs.forEach(t => t.setAttribute('aria-selected', t === tab ? 'true' : 'false'));
    panels.forEach((p, id) => p.classList.toggle('is-active', id === tab.id));
    tab.focus();
  }

  tabs.forEach(t => t.addEventListener('click', () => activate(t)));

  /*
  // キーボード左右で移動(アクセシビリティ)
  document.querySelector('[role="tablist"]').addEventListener('keydown', (e) => {
    if (!['ArrowLeft','ArrowRight','Home','End'].includes(e.key)) return;
    e.preventDefault();
    const i = tabs.findIndex(t => t.getAttribute('aria-selected') === 'true');
    let next = i;
    if (e.key === 'ArrowRight') next = (i + 1) % tabs.length;
    if (e.key === 'ArrowLeft')  next = (i - 1 + tabs.length) % tabs.length;
    if (e.key === 'Home') next = 0;
    if (e.key === 'End')  next = tabs.length - 1;
    activate(tabs[next]);
  });
  */  
  </script>

</body>
</html>

SimpleTab.css
.tabs {
  display: flex;
  gap: 8px;
  padding-left: 2px;
}

.tab-btn {
  position: relative;
  background: #f5f5f5;
  border: 1px solid #ccc;
  border-bottom: none;
  padding: 10px 14px;
  color: #606060;
  cursor: pointer;
  border-radius: 8px 8px 0 0;
  margin-bottom: -1px;
  transition: background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

  .tab-btn:hover {
    background: #fafafa;
    color: #404040;
  }

  .tab-btn[aria-selected="true"] {
    background: #ffffff;
    color: #000;
    border-color: #4da3ff;
    z-index: 1;
  }

.tab-panel {
  display: none;
  padding: 16px 14px;
  border: 1px solid #ccc;
  border-radius: 0 8px 8px 8px;
  background: #ffffff;
  margin-top: -1px;
}

  .tab-panel.is-active {
    display: block;
  }

解説

以下のコードでtab-btnクラスの全てのタブボタンを配列化します。
...はスプレッド構文で、document.querySelectorAll('.tab-btn') はNodeListで戻りますが、...により、配列リテラルに詰めなおしできます。

const tabs = [...document.querySelectorAll('.tab-btn')];


タブのボタンにタブのボタンに対応したコンテンツのパネルを関連付けます。

const panels = new Map(tabs.map(t => [t.id, document.getElementById(t.getAttribute('aria-controls'))]));

上記コードをfor文で記述した場合は以下になります。

tabsの配列から、aria-controls属性を読み出します。aria-controlsには対応するパネルのIDが記述されています。 PanelのDOM要素をgetElementById()メソッドで取得します。パネルのDOM要素と、タブのIDをセットにしてMAPに挿入します。
panels["t-01"] のコードで t-01タブのパネルのDOM要素にアクセスできます。

  const panels = new Map();
  for (const t of tabs) {
    const panelId = t.getAttribute('aria-controls');
    const panelEl = document.getElementById(panelId);
    panels.set(t.id, panelEl);
  }


タブのボタンをクリックされるとactivate関数が呼び出されます。tabにはクリックされたタブのDOMオブジェクトが渡されます。 tabs配列をスキャンして t===tab であればaria-selected属性にtrueをセットし、そうでなければfalseをセットします。
同様にpanels変数もスキャンし、id === tab.id であれば、パネルにis-activeクラスを追加し、そうでなければis-activeクラスを外します。 toggleメソッドの第2引数は「強制フラグ」です。true ならクラスを必ず付ける、false なら必ず外す、という指定になります。

is-activeクラスがサブクラスに付与されることでパネルが画面に表示されます。

  function activate(tab) {
    tabs.forEach(t => t.setAttribute('aria-selected', t === tab ? 'true' : 'false'));
    panels.forEach((p, id) => p.classList.toggle('is-active', id === tab.id));
    tab.focus();
  }


tabs配列のclickイベントにactivate(t) の呼び出しをセットします。tは配列の各要素になりますので、id="t-02"のボタンであれば、activate((t-02のDOM要素))が呼び出されます。

  tabs.forEach(t => t.addEventListener('click', () => activate(t)));

実行結果

上記のHTMLファイルをWebブラウザで表示します。下図のページが表示されます。[タブ No.1]が選択された状態となります。
HTMLページでタブUIを実装する:画像1

[タブ No.2]をクリックすると、[タブ No.2]が選択され、下部のパネルの内容も変化します。
HTMLページでタブUIを実装する:画像2

同様に[タブ No.3]をクリックした場合は下図の表示となります。
HTMLページでタブUIを実装する:画像3

HTMLページでタブのUIを実装できました。

補足: form タグ内に配置する場合

タブのボタンはbuttonタグで実装されているため、form内に配置した場合、submitされてしまう場合があります。その場合は、type="button" を記述します。

  <div class="tabs" role="tablist" aria-label="タブ">
    <button type="button" class="tab-btn" role="tab" id="t-01" aria-controls="p-01" aria-selected="true">タブ No.1</button>
    <button type="button" class="tab-btn" role="tab" id="t-02" aria-controls="p-02" aria-selected="false">タブ No.2</button>
    <button type="button" class="tab-btn" role="tab" id="t-03" aria-controls="p-03" aria-selected="false">タブ No.3</button>
  </div>
AuthorPortraitAlt
著者
iPentecのプログラマー、最近はAIの積極的な活用にも取り組み中。
とっても恥ずかしがり。
作成日: 2025-12-24