HTMLとCSSで切り替えられるタブの作り方

ソフトウェアのUIによくある、複数のコンテンツをコンパクトに表示できるタブ切り替えを、JavaScriptは使わず、HTMLとCSSだけで作る方法を紹介します。

完成するタブ切り替えは次のような感じになります。

タブ切り替えのHTMLコード

<div class="__tabs" style="--has-nth-child: 4;">
  <input id="tab-1" type="radio" name="tab" class="__tabs_radio" checked="checked" />
  <label for="tab-1" class="__tabs_tab">タブ1</label>
  <div class="__tabs_tabpanel">
    <p>タブ1のパネル</p>
  </div>
  <input id="tab-2" type="radio" name="tab" class="__tabs_radio" />
  <label for="tab-2" class="__tabs_tab">タブ2</label>
  <div class="__tabs_tabpanel">
    <p>タブ2のパネル</p>
  </div>
  <input id="tab-3" type="radio" name="tab" class="__tabs_radio" />
  <label for="tab-3" class="__tabs_tab">タブ3</label>
  <div class="__tabs_tabpanel">
    <p>タブ3のパネル</p>
  </div>
  <input id="tab-4" type="radio" name="tab" class="__tabs_radio" />
  <label for="tab-4" class="__tabs_tab">タブ4</label>
  <div class="__tabs_tabpanel">
    <p>タブ4のパネル</p>
  </div>
</div>

注目ポイント1
ラジオボタンとラベルを使用する

共通の name 属性でグループ化したラジオボタン (.__tabs_radio) を使用し、選択された状態によって表示方法を制御します。最初に選択しておきたいラジオボタンには、checked 属性を追加します。ラジオボタンには id 属性を設定し、for 属性でラベル (.__tabs_tab) と関連付けします。

注目ポイント2
タブの総数をCSS変数で設定する

ラジオボタン (.__tabs_radio) とラベル (.__tabs_tab) とパネル (.__tabs_tabpanel) の一組からなるタブの総数を、親要素 (.__tabs) の style 属性値に「--has-nth-child」というCSS変数で設定します。でも、これは必須ではありません。

タブ切り替えのCSSコード

.__tabs {
  --has-nth-child: 6;
  display: grid;
    grid: none / repeat(var(--has-nth-child), 1fr);
    gap: 2em 2px;
  overflow: hidden;
  padding-inline: 1em;
}
.__tabs_radio {
  clip-path: inset(50%);
  position: fixed;
    inset-block-start: 0;
}
.__tabs_tab {
  border: solid 1px;
  border-block-end: none;
  cursor: pointer;
  display: block;
  grid-row: 1;
    grid-column: auto;
  padding: 1em;
  position: relative;
  text-align: center;
}
.__tabs_radio:focus + .__tabs_tab {
  text-decoration: underline;
}
.__tabs_radio:is(:focus, :checked) + .__tabs_tab::after {
  block-size: 1px;
  border-inline: solid 10000px;
  content: "";
  pointer-events: none;
  position: absolute;
    inset-block: auto 0;
    inset-inline: -10000px;
}
.__tabs_tabpanel {
  grid-row: 2;
    grid-column: 1 / span var(--has-nth-child);
}
.__tabs_radio:not(:focus, :checked) + * + .__tabs_tabpanel {
  visibility: hidden;
}

注目ポイント1
ラジオボタンを隠す

スタイリングするうえで、ラジオボタン (.__tabs_radio) は邪魔になるので、position: fixed; で通常のレイアウトフローから外し、clip-path: inset(50%); で隠します。ラジオボタンが選択されたとき、スクロールが発生しないよう、inset-block-start: 0; も忘れずに。

display: none;visibility: hidden; で隠すと、フォーカスされずキーボード操作ができない状態になります。これはアクセシビリティ的によくありませんので、オヌヌメしません。

注目ポイント2
グリッドレイアウトを使用する

タブ (.__tabs_tab) を一列に並ばせる必要があります。しかし、HTMLの構造的には、タブとタブの間にあるパネル (.__tabs_tabpanel) が邪魔です。そこで、グリッドレイアウトを使用します。

タブは grid-row: 1; でグリッドの一行目に、パネルは grid-row: 2; で2行目に配置します。

タブを並べるためには、グリッドの列の数を設定する必要があります。そこで、CSS変数の「--has-nth-child」に設定された数を使用します。

--has-nth-child」が設定されなかった場合を想定して、--has-nth-child: 6; のように初期値を設定しておきます。これが必須ではない理由です。

パネルは grid-column: 1 / span var(--has-nth-child); で、全ての列にまたがって、パネル同士が重なるように配置します。

注目ポイント3
兄弟セレクタでタブとパネルのスタイルを切り替える

タブ (.__tabs_tab) とパネル (.__tabs_tabpanel) は .__tabs_radio:is(:focus, :checked).__tabs_radio:not(:focus, :checked) を基点とした隣接兄弟セレクタ (+) を使用してスタイリングします。:focus 擬似クラスも使用することで、フォーカス時に方向キーでの操作が可能になります。

タブは、下以外にボーダーを設定し、選択しているタブだけ ::after 擬似要素で下ボーダーを追加します。下ボーダーは左右に 10000px のボーダーと高さ 1px を設定しています。

パネルは、選択されていない場合に visibility: hidden; が適用され、表示されなくなりますが、表示される際の高さは保持されるので、選択したパネルによって全体の高さが変わるといったことがなくなります。パネル同士が重なるように配置した理由はこのためです。

選択したパネルによって高さが変わっても良い場合は、visibility: hidden; の代わりに display: none; を使用します。

おまけ: タブを垂直に並べる

タブを垂直に並べたい場合は、次のようなCSSコードを追加し、

.__tabs.vertical {
  grid: repeat(var(--has-nth-child), auto) 1fr / min(8em, 25%) 1fr;
    gap: 2px 2em;
  padding: 0;
  padding-block: 1em;
}
.__tabs.vertical > .__tabs_tab {
  border: solid 1px;
  border-inline-end: none;
  grid-row: auto;
    grid-column: 1;
  text-align: start;
}
.__tabs.vertical > .__tabs_radio:is(:focus, :checked) + .__tabs_tab::after {
  block-size: auto;
    inline-size: 1px;
  border-block: solid 10000px;
  border-inline: none;
  inset-block: -10000px;
  inset-inline: auto 0;
}
.__tabs.vertical > .__tabs_tabpanel {
  grid-row: 1 / span calc(var(--has-nth-child) + 1);
    grid-column: 2;
}

HTMLコードに vertical を追加してください。タブの大きさは min(8em, 25%) の部分で調整できます。

<div class="__tabs vertical" style="--has-nth-child: 4;">
  …
</div>

CodePen

See the Pen Simple tabs by nov (@numerofive) on CodePen.

ラジオボタンと兄弟セレクタを使って、タブデザインを作る方法は割と知られていますが、グリッドレイアウトはあまり使われていないようだったので作ってみました。

関連記事