CSSだけで使いやすく汎用性の高いタブ切り替え(複数設置対応)を実装する

CSSだけで使える汎用性の高いタブ切り替え

先日コメントにてCocoonで使える[ショートコード]のオプションと使い方まとめで実装しているタブ切り替えについて質問をいただきました。

これ↓

タブ切り替えのスクリーンショット

このタブ切り替えは、ラジオボタンを利用してCSSだけで切り替えを実装出来るこちらのページを参考にカスタマイズしたものです。

コメント欄でもこんなコードで実装出来るよ~ってやり取りがあって色々考えたんですけど、
ふともっと汎用性の高いコーディング出来るんじゃ?と思い立ったので回答したコードの改良版を紹介します。

今回は無駄に小話を挟んでるので、コードだけ見たい人はスキップ

スポンサーリンク

よくあるタブ切り替えのHTML

See the Pen よくあるタブ切り替え by 草村 (@kusamura_mono) on CodePen.0

<div class="tab-wrap">
    <input id="TAB-01" type="radio" name="TAB" class="tab-switch" /><label class="tab-label" for="TAB-01" checked="checked">ボタン 1</label>
    <input id="TAB-02" type="radio" name="TAB" class="tab-switch" /><label class="tab-label" for="TAB-02">ボタン 2</label>
    <input id="TAB-03" type="radio" name="TAB" class="tab-switch" /><label class="tab-label" for="TAB-03">ボタン 3</label> 
    <div class="tab-content" id="TAB-01-content">
        コンテンツ 1
    </div>
    <div class="tab-content" id="TAB-02-content">
        コンテンツ 2
    </div>
    <div class="tab-content" id="TAB-03-content">
        コンテンツ 3
    </div>
</div>

ざっくり言うとこういう並びのHTMLです。

ラジオボタン1 + ラベル1
ラジオボタン2 + ラベル2 
ラジオボタン3 + ラベル3 
コンテンツ1
コンテンツ2
コンテンツ3

私も参考にさせてもらったタブ切り替えで、よく見かけるHTMLでの実装方法かなと思います。

これの面倒くさい所は、コンテンツを内包する要素にidを設定する&設定したidをCSSに記述する必要がある所ですね。
なぜかというとそれぞれのラジオボタンとコンテンツの紐付けができないから。

CSSがこんな感じで間接セレクタ(~)とidで記述しています。

#TAB-01:checked ~ #TAB-01-content,
#TAB-02:checked ~ #TAB-02-content,
#TAB-03:checked ~ #TAB-03-content {
    display: block;
}

これでは一つのページに複数設置がしづらいので、私は:nth-of-type(n)で対応する方法をとっていました。

.tab-switch:first-of-type:checked ~ .tab-content:first-of-type,
.tab-switch:last-of-type:checked ~ .tab-content:last-of-type,
.tab-switch:nth-of-type(2):checked ~ .tab-content:nth-child(2) { 
    display: block; 
}

これなら複数のタブ切り替えに対応可能です。

ただ、コードも長いしタブが増えたら:nth-of-type(n)を追加するって形がどうも気持ち悪いな~と心の片隅で引っかかっていた所、タブ切り替えについてコメントを頂いて再考する機会がありました。

残念ながら回答の段階では思いつかなかったんですが汗

紐付けしやすい理想のHTML

ラジオボタンとコンテンツがなぜ紐付けし辛いかっていうと隣接してないからですよね。

こんな風になってるHTMLだと

ラジオボタン1 + ラベル1
ラジオボタン2 + ラベル2 
ラジオボタン3 + ラベル3 
コンテンツ1
コンテンツ2
コンテンツ3

前述のように間接セレクタ(~)を使う必要がある。
でも間接セレクタだと後方にある要素(コンテンツ1~3)全部に当てはまるので、それぞれにidを付ける必要がある。

だから隣接するように、こう出来ればいい。

ラジオボタン1 + ラベル1 + コンテンツ1
ラジオボタン2 + ラベル2 + コンテンツ2
ラジオボタン3 + ラベル3 + コンテンツ3

こう出来なかった理由は、上部にメニューを並べて下にコンテンツが表示されるのが理想なのでレイアウト的に都合が悪かったから。

ここで唐突にひらめく。

orderで入れ替えちゃえば解決するじゃん!?

もとよりラジオボタンは非表示なんで放って置いて、ラベルにorder:-1;を設定するだけでいとも簡単にラベルだけ並べる事が出来ました。Amazing…。

って事で、理想のHTML・CSSにラジオボタン:checked+ラベル+コンテンツだけで切り替えが出来るようになりました。

スポンサーリンク

【暫定】汎用性の高いタブ切り替え

このコードの良い点
  • タブが増えてもCSSの追加が必要ない
  • CSSを追加せずに同じページで複数のタブ切り替えが使える
  • アコーディオンメニュー的なレイアウトにも出来る

これだけのコードで出来るよって事で最低限のスタイルのみ適用してます。

See the Pen タブ切り替え(汎用版) by 草村 (@kusamura_mono) on CodePen.0

HTML

<div class="tab-wrap">
    <input id="TAB-01" type="radio" name="TAB" class="tab-switch" checked="checked" /><label class="tab-label" for="TAB-01">ボタン 1</label>
    <div class="tab-content">
        コンテンツ 1
    </div>
    <input id="TAB-02" type="radio" name="TAB" class="tab-switch" /><label class="tab-label" for="TAB-02">ボタン 2</label>
    <div class="tab-content">
        コンテンツ 2
    </div>
    <input id="TAB-03" type="radio" name="TAB" class="tab-switch" /><label class="tab-label" for="TAB-03">ボタン 3</label>
    <div class="tab-content">
        コンテンツ 3
    </div>
</div>

CSS

.tab-wrap {
    display: flex;
    flex-wrap: wrap;
}
.tab-label {
    color: White;
    background: LightGray;
    margin-right: 5px;
    padding: 3px 12px;
    order: -1;
}
.tab-content {
    width: 100%;
    display: none;
}
/* アクティブなタブ */
.tab-switch:checked+.tab-label {
    background: DeepSkyBlue;
}
.tab-switch:checked+.tab-label+.tab-content {
     display: block;
}
/* ラジオボタン非表示 */
.tab-switch {
    display: none;
}

デザインパターン

HTMLは基本のまま、CSSだけで多様なデザインが可能です。参考にどうぞ!

ボタンが幅に合わせて伸縮する

タブが増減してもタブ幅が勝手に調整されるよ。

See the Pen タブ切り替え(汎用版/装飾) by 草村 (@kusamura_mono) on CodePen.0

アコーディオンメニュー風

2019-06-27: HTMLがボタン+コンテンツになってるのでアコーディオンメニューみたいにも出来る

See the Pen タブ切り替え(汎用版/アコーディオン) by 草村 (@kusamura_mono) on CodePen.0

左右2カラムにする(ボタン縦)

2019-06-27: flex-direction: column;で2カラムに出来ます。height/widthの指定が必要。

See the Pen タブ切り替え(汎用版/コンテンツ横並び) by 草村 (@kusamura_mono) on CodePen.0

使い方・コード解説

超ざっくり解説

HTML解説

<input id="TAB-01" type="radio" name="TAB" checked="checked" /><label for="TAB-01">ボタン 1</label>
<input id="TAB-02" type="radio" name="TAB" /><label for="TAB-02">ボタン 2</label>
<input id="TAB-03" type="radio" name="TAB" /><label for="TAB-03">ボタン 3</label>

※ボタンになる部分を見やすいように余計な記述を削除して抜粋しています。

  1. <input>name属性に同じ名前を付ける。
  2. 1行の<input>id属性<label>for属性に同じ名前を付ける。

名前は他と被らなければなんでも大丈夫です。

<input>checked=”checked”が指定されたタブが初期状態でアクティブになります。

同じページに複数タブ切り替えがある場合気をつける事

<!-- 1つめのタブ切り替え -->
<input id="TAB01-01" type="radio" name="TAB01" /><label for="TAB01-01">ボタン 1</label>
<input id="TAB01-02" type="radio" name="TAB01" /><label for="TAB01-02">ボタン 2</label>
<input id="TAB01-03" type="radio" name="TAB01" /><label for="TAB01-03">ボタン 3</label>

<!-- 2つめのタブ切り替え -->
<input id="TAB02-01" type="radio" name="TAB02" /><label for="TAB02-01">ボタン 1</label>
<input id="TAB02-02" type="radio" name="TAB02" /><label for="TAB02-02">ボタン 2</label>
<input id="TAB02-03" type="radio" name="TAB02" /><label for="TAB02-03">ボタン 3</label>

※ボタンになる部分を見やすいように余計な記述を削除して抜粋しています。

  1. タブ切り替え毎に<input>name属性にそれぞれ別の名前を付ける。
  2. <input>id属性<label>for属性別のタブ切り替えとかぶらないようにする。

CSS解説

デフォルトでコンテンツを非表示にしています。

.tab-content {
    display: none;
}

:checkedを使いラジオボタンが選択された時に表示します。

.tab-switch:checked+.tab-label+.tab-content {
    display: block;
}

ラベルは上に並ぶようにしています。

.tab-wrap { 
    display: flex;
    flex-wrap: wrap;
}
.tab-label {
    order: -1;
}

もっとHTMLをシンプルに

ボタン周辺のコード長くて見づらい…って感じならclass属性撤廃しても大体大丈夫です。

<input id="TAB-01" type="radio" name="TAB" /><label for="TAB-01">ボタン 1</label>

CSSは次のように変更

  1. .tab-switch.tab-wrap>input[type="radio"]に変更
  2. .tab-label.tab-wrap>labelに変更
スポンサーリンク

checkedがなくても初期状態でタブを表示する

何らかの事情でcheckedを指定出来ない場面で使える手法です。

See the Pen タブ切り替え(汎用版/checked無し対応) by 草村 (@kusamura_mono) on CodePen.0

HTML

HTMLはこちらのclassにtab-orderを追加します。

最後のタブを左に移動したくない場合は追加しなくてOKです。

<div class="tab-wrap tab-order">
    <input id="TAB-01" type="radio" name="TAB" class="tab-switch" /><label class="tab-label" for="TAB-01">ボタン 1</label>
    <div class="tab-content">
        コンテンツ 1
    </div>
    <input id="TAB-02" type="radio" name="TAB" class="tab-switch" /><label class="tab-label" for="TAB-02">ボタン 2</label>
    <div class="tab-content">
        コンテンツ 2
    </div>
    <input id="TAB-03" type="radio" name="TAB" class="tab-switch" /><label class="tab-label" for="TAB-03">ボタン 3</label>
    <div class="tab-content">
        コンテンツ 3
    </div>
</div>

CSS

.tab-wrap {
    display: flex;
    flex-wrap: wrap;
}
.tab-label,
.tab-switch:checked~.tab-switch:last-of-type+.tab-label {
    color: White;
    background: LightGray;
    margin-right: 5px;
    padding: 3px 12px;
}
.tab-label{
    order: -1;
}
.tab-content,
.tab-switch:checked~.tab-content:last-of-type { 
    width: 100%;
    display: none;
}
/* アクティブなタブ */
.tab-switch:checked+.tab-label,
.tab-label:last-of-type {
    background: DeepSkyBlue;
}
.tab-switch:checked+.tab-label+.tab-content,
.tab-content:last-of-type {
    display: block;
}
/* 最後のタブを左に */ 
.tab-order .tab-label:last-of-type {
    order: -2;
}
/* ラジオボタン非表示 */
.tab-switch {
    display: none;
}

どうなってる?

デフォルトで最後のタブをアクティブ状態に。

/* 最後のラベル */
.tab-label:last-of-type { /* アクティブ */ }

/* 最後のコンテンツ */
.tab-content:last-of-type { /* アクティブ */ }

最後のタブ以外がアクティブなら、最後のタブを非アクティブに。

/* 選択されたラジオボタンがある場合 ~ 最後のラジオボタン + のラベル */
.tab-switch:checked ~ .tab-switch:last-of-type + .tab-label { /* 非アクティブ */ }

/* 選択されたラジオボタンがある場合 ~ 最後のコンテンツ */
.tab-switch:checked ~ .tab-content:last-of-type { /* 非アクティブ */ }

デフォルトで最初のタブがアクティブなように見せるために「最後のタブを左」へ移動。これもorderで解決…。

.tab-order .tab-label:last-of-type {
    order: -2;
}

WordPressで使う際の注意点

クラシックエディタでこのHTMLコードを使おうとすると勝手に<p>で囲まれるので使えません。

TinyMCE Advancedプラグインで段落を保持するように設定すると使えるかもしれません。

ブロックエディタではカスタムHTMLに記述すれば使えました。

Cocoonサイドバーウィジェットで利用する場合

2019-09-19追記:

初期状態でタブがアクティブにならない場合があります。
その場合#checkedがなくても初期状態でタブを表示するを利用する等の工夫が必要になります。

上記のCSSでは最後のタブがアクティブになります。
HTMLのchecked=”checked”を最後のタブに指定するか、指定自体なくすと良いです。

Cocoonサイドバーで意図した動作にならない理由について
もっと詳しい解説を知りたい方は#コメント欄にてご確認ください。

あとがき

まるで世紀の大発見のような気持ちだったけど、実はもう一般的だったらちょっと恥ずかしい…。

flexが使えるようになってレイアウトの幅が広がったなぁと感じます。

HTML的にも見出し+コンテンツのような順で記述出来てコードもシンプルになったのでちょっと気分がいいです。

汎用性は高くなったと思うんですが、実際使うとレイアウトの自由度が制限される事が出てきたりするんでしょうかね。

Edge未対応ですが<details>なんてタグも存在してたりするのでいつかCSSすら不要な時代もくるかもしれませんね。

結論:orderマジ便利。

コメント

  1. Thanks! より:

     タブ切り換えの機能を使いたかったので参考にさせていただきました。ありがとうございます! ただ、私のサイトに導入させていただいたのですが、上手くいかないところがあるため質問させていただきます。

     草村さんの記事上のタブは「ボタン1」がデフォルトでアクティブになっています。しかし、私のサイトでは「ボタン1」がアクティブになりません。ページを読み込んだ直後はコンテンツが隠れた状態になっています。
     私のサイトでも「ボタン1」をデフォルトでアクティブにしたいです。解決方法があれば、教えていただけると助かります。

    —————-
    ■利用環境など
    ・利用ブラウザ:Google Chrome
    ・cocoonバージョン:1.9.7
    ・さくらインターネット スタンダード
    ・サイトURL:h ttps://majikanote.com/

    ■試したこと
    ・Chrome ―― cocoon「Lazy Load機能」停止 → Chromeキャッシュクリア → 「ボタン1」アクティブにならず
    ・Firefox ―― 表示確認 → 「ボタン1」アクティブ状態ではない

    ■設定状況
    ・タブ設定箇所 → サイドバー。ウィジェット名「ランキング」
    ・「ボタンが幅に合わせて伸縮」する、ボタン4つのタイプのソースをそのまま使用(ソースは ほぼ変更なし)
    ・ボタンは4つ「Day(ボタン1)」「Week(ボタン2)」「Month(ボタン3)」「Year(ボタン4)」
    ・ランキングはショートコードを利用して表示

    ■やりたいこと
    ・デフォルトで「Day」ボタンが選択された状態にし、tab-content のランキングも表示された状態にしたい
    —————-

    お手数おかけいたしますが、よろしくお願いします。

    • Thanks! より:

      「checkedがなくても初期状態でタブを表示する」の内容を応用し、「.tab-label:last-of-type {order: -3;}」 としたところ解決できました。

      • 草村草村 より:

        こんにちは!ご質問の件、解決出来たようでよかったです!

        解決方法も書き込んでいただけたので、同じように困っている方の助けになると思います!ありがとうございます!

        ▼一応解説すると、
        Cocoonテーマをお使いの場合、サイドバーウィジェットはスマホのサイドバー用に同じウィジェットが複製されるため同じname属性のタブ切り替えが2箇所存在してしまうんですよね^^;

        同じname属性グループ内に複数checkedの指定があると後に記述された方が優先されるため、スマホでは意図したボタンがアクティブになるんですがPCではアクティブなボタンが存在しない状態になります(スマホサイドバー側のボタンがアクティブになるので)。

        同じname属性グループ
        PCサイドバーのタブ切り替え

        [◎ボタン1] [ボタン2] [ボタン3]

        スマホサイドバーのタブ切り替え(複製)

        [●ボタン1] [ボタン2] [ボタン3]

        ◎●=「checked=”checked”」を指定したタブ

        • name属性が同じなのでPCとスマホ2箇所のタブ切り替えがグループ化されてしまう
        • [◎ボタン1]よりも後に記述された[●ボタン1]のcheckedが優先されアクティブになる
        • PCサイドバーのタブ切り替えにアクティブ(checked)が存在しない=表示されない

        なので「checkedがなくても初期状態でタブを表示する」で.tab-wrapで囲まれた最後のタブをアクティブにする事で解決するという感じです。
        今回の場合最後のボタンにcheckedを指定するか、あえてどこにも指定しない事で初期アクティブタブをPC/スマホで一致させる事が出来るかと思います。

        解説が複雑で上手く伝えられないのですが、同じようにお困りの方は「#checkedがなくても初期状態でタブを表示する」をお試しください。

        Thanks!さん有用なコメントありがとうございました!

        *2019-09-19記事内容を少し変更しました。

      • Thanks! より:

        コメント欄を読んだ方が誤解してはいけないので訂正します。
        私は、「.tab-label:last-of-type {order: -3;}」で解決できたと思っていましたが勘違いでした。
        草村さんのコードの「.tab-label:last-of-type {order: -2;}」で、現在は正常に動作しています。
        訂正して、お詫びいたします。

        • 草村草村 より:

          コードを変更せずとも動作したという事ですね。
          自分も少し自信がない部分だったので正常動作してるとの事、ご報告いただけて安心いたしました(*^^*)
          わざわざコメントくださりありがとうございました!

タイトルとURLをコピーしました