「料金」「営業時間」「アクセス」のように、見出しを押すと下の中身が入れ替わる。このタブ切り替えは、ラジオボタン(丸い選択ボタン)と、CSSの :has() を組み合わせると、JavaScriptを書かずに作れます。
同じ種類の情報を、見る人が1つだけ選んで読めば済むときに使う。全部を比べて読んでほしい情報や、項目が2つしかないときは、タブにせず並べて見せる。
使うのは、選ぶ部分になる ラジオボタン(ふだんは見えなくしておく)、選ばれたものに当てはまる :checked、そして「中に〇〇がある要素」を選べる :has()(ハズ)です。
完成見本:タブ切り替え5種類
下の5つは、ページを開いた時点から「1つ目 → 2つ目 → 3つ目」と自動で切り替わり続けています。自動で動いている見本は押せません。実際に押して試せる見本は、後半の「実際に押して試す」に置いています。
カット 4,400円、カラー 6,600円です。表示はすべて税込みです。
平日は10時から19時、土日は9時から18時まで営業しています。
駅の東口から歩いて3分です。1階がパン屋さんの建物の2階です。
カット 4,400円、カラー 6,600円です。表示はすべて税込みです。
平日は10時から19時、土日は9時から18時まで営業しています。
駅の東口から歩いて3分です。1階がパン屋さんの建物の2階です。
カット 4,400円、カラー 6,600円です。表示はすべて税込みです。
平日は10時から19時、土日は9時から18時まで営業しています。
駅の東口から歩いて3分です。1階がパン屋さんの建物の2階です。
カット 4,400円、カラー 6,600円です。表示はすべて税込みです。
平日は10時から19時、土日は9時から18時まで営業しています。
駅の東口から歩いて3分です。1階がパン屋さんの建物の2階です。
月曜日は定休日です。
火曜日は10時から19時までです。
水曜日は10時から19時までです。
木曜日は10時から21時までです。夜の予約が取れます。
金曜日は10時から19時までです。
※ 見本の料金や営業時間は、架空のお店のものです。
止めて並べると、こうなります。
カット 4,400円、カラー 6,600円です。表示はすべて税込みです。
平日は10時から19時、土日は9時から18時まで営業しています。
駅の東口から歩いて3分です。1階がパン屋さんの建物の2階です。
カット 4,400円、カラー 6,600円です。表示はすべて税込みです。
平日は10時から19時、土日は9時から18時まで営業しています。
駅の東口から歩いて3分です。1階がパン屋さんの建物の2階です。
カット 4,400円、カラー 6,600円です。表示はすべて税込みです。
平日は10時から19時、土日は9時から18時まで営業しています。
駅の東口から歩いて3分です。1階がパン屋さんの建物の2階です。
カット 4,400円、カラー 6,600円です。表示はすべて税込みです。
平日は10時から19時、土日は9時から18時まで営業しています。
駅の東口から歩いて3分です。1階がパン屋さんの建物の2階です。
カット 4,400円、カラー 6,600円です。表示はすべて税込みです。
平日は10時から19時、土日は9時から18時まで営業しています。
駅の東口から歩いて3分です。1階がパン屋さんの建物の2階です。
カット 4,400円、カラー 6,600円です。表示はすべて税込みです。
平日は10時から19時、土日は9時から18時まで営業しています。
駅の東口から歩いて3分です。1階がパン屋さんの建物の2階です。
カット 4,400円、カラー 6,600円です。表示はすべて税込みです。
平日は10時から19時、土日は9時から18時まで営業しています。
駅の東口から歩いて3分です。1階がパン屋さんの建物の2階です。
カット 4,400円、カラー 6,600円です。表示はすべて税込みです。
平日は10時から19時、土日は9時から18時まで営業しています。
駅の東口から歩いて3分です。1階がパン屋さんの建物の2階です。
月曜日は定休日です。
火曜日は10時から19時までです。
水曜日は10時から19時までです。
木曜日は10時から21時までです。夜の予約が取れます。
金曜日は10時から19時までです。
月曜日は定休日です。
火曜日は10時から19時までです。
水曜日は10時から19時までです。
木曜日は10時から21時までです。夜の予約が取れます。
金曜日は10時から19時までです。
※ スマホやパソコンで「動きを減らす」設定をオンにしている方には、線が伸びる動きや、中身がふわっと出る動きをなくし、すぐに切り替わる形で表示しています。理由は後半の「見る人への配慮」で説明します。
コピペ用のコード
見本ごとに、HTMLとCSSを並べました。HTMLはタブを置きたい場所に、CSSはサイトのCSSファイルや、WordPressならテーマの「追加CSS」などに入れます。タブの名前と中身の文章は書きかえてください。
CSSの前半にある .kd-tab から始まる部分は、5種類に共通する指定です。同じページで何種類か使うときは、1回だけ入れれば足ります。クラス名の頭の kd-tab- は、サイトのほかのデザインと名前がぶつからないようにするための目印です。
1. 下線が伸びる
選んだタブの下に、青い線が中央から伸びます。いちばんすっきりした形で、ページの中の小さな切り替えに向いています。
カット 4,400円、カラー 6,600円です。表示はすべて税込みです。
平日は10時から19時、土日は9時から18時まで営業しています。
駅の東口から歩いて3分です。1階がパン屋さんの建物の2階です。
<div class="kd-tab kd-tab-line">
<div class="kd-tab-list">
<label><input type="radio" name="kd-tab-shop" checked>料金</label>
<label><input type="radio" name="kd-tab-shop">営業時間</label>
<label><input type="radio" name="kd-tab-shop">アクセス</label>
</div>
<div class="kd-tab-panels">
<div class="kd-tab-panel"><p>カット 4,400円、カラー 6,600円です。表示はすべて税込みです。</p></div>
<div class="kd-tab-panel"><p>平日は10時から19時、土日は9時から18時まで営業しています。</p></div>
<div class="kd-tab-panel"><p>駅の東口から歩いて3分です。1階がパン屋さんの建物の2階です。</p></div>
</div>
</div>/* 5種類に共通:タブの箱 */
.kd-tab {
color: #0F172A;
text-align: left;
}
/* タブの並び(押すところ) */
.kd-tab-list {
display: flex;
}
.kd-tab-list label {
position: relative;
padding: 10px 16px;
font-weight: 700;
line-height: 1.4;
white-space: nowrap;
cursor: pointer;
}
/* 丸いボタンは見えなくする(消さない。キーボードで選べるように残す) */
.kd-tab-list input {
position: absolute;
opacity: 0;
pointer-events: none;
}
/* キーボードで選んだときの枠。消さずに、はっきり見せる */
.kd-tab-list label:has(input:focus-visible) {
outline: 3px solid #0F172A;
outline-offset: -3px;
}
/* 切り替わる中身 */
.kd-tab-panel {
padding: 16px;
line-height: 1.8;
}
.kd-tab-panel p {
margin: 0;
}
/* 選ばれたタブの中身だけを見せる(:has() が使えないブラウザでは、全部見えたままになる) */
@supports selector(:has(*)) {
.kd-tab-panel {
display: none;
}
.kd-tab:has(.kd-tab-list > :nth-child(1) input:checked) .kd-tab-panels > :nth-child(1),
.kd-tab:has(.kd-tab-list > :nth-child(2) input:checked) .kd-tab-panels > :nth-child(2),
.kd-tab:has(.kd-tab-list > :nth-child(3) input:checked) .kd-tab-panels > :nth-child(3),
.kd-tab:has(.kd-tab-list > :nth-child(4) input:checked) .kd-tab-panels > :nth-child(4),
.kd-tab:has(.kd-tab-list > :nth-child(5) input:checked) .kd-tab-panels > :nth-child(5) {
display: block;
}
}
.kd-tab-line .kd-tab-list {
border-bottom: 1px solid #D8E6F0;
}
.kd-tab-line .kd-tab-list label::after {
content: "";
position: absolute;
left: 8px;
right: 8px;
bottom: -1px;
height: 3px;
border-radius: 3px;
background: #0284C7;
transform: scaleX(0);
transition: transform 0.25s ease;
}
.kd-tab-line .kd-tab-list label {
color: #5B6B78;
transition: color 0.25s ease;
}
.kd-tab-line .kd-tab-list label:has(input:checked) {
color: #0369A1;
}
.kd-tab-line .kd-tab-list label:has(input:checked)::after {
transform: scaleX(1);
}
/* 動きを減らす設定の人には、線が伸びる動きをなくして、すぐ切り替える */
@media (prefers-reduced-motion: reduce) {
.kd-tab-line .kd-tab-list label {
transition: none;
}
.kd-tab-line .kd-tab-list label::after {
transition: none;
}
}2. 見出しタブ
ファイルの見出しのような形です。選んだタブだけ白くなって下の箱とつながり、「今どれを開いているか」が形で分かります。
カット 4,400円、カラー 6,600円です。表示はすべて税込みです。
平日は10時から19時、土日は9時から18時まで営業しています。
駅の東口から歩いて3分です。1階がパン屋さんの建物の2階です。
<div class="kd-tab kd-tab-folder">
<div class="kd-tab-list">
<label><input type="radio" name="kd-tab-shop" checked>料金</label>
<label><input type="radio" name="kd-tab-shop">営業時間</label>
<label><input type="radio" name="kd-tab-shop">アクセス</label>
</div>
<div class="kd-tab-panels">
<div class="kd-tab-panel"><p>カット 4,400円、カラー 6,600円です。表示はすべて税込みです。</p></div>
<div class="kd-tab-panel"><p>平日は10時から19時、土日は9時から18時まで営業しています。</p></div>
<div class="kd-tab-panel"><p>駅の東口から歩いて3分です。1階がパン屋さんの建物の2階です。</p></div>
</div>
</div>/* 5種類に共通:タブの箱 */
.kd-tab {
color: #0F172A;
text-align: left;
}
/* タブの並び(押すところ) */
.kd-tab-list {
display: flex;
}
.kd-tab-list label {
position: relative;
padding: 10px 16px;
font-weight: 700;
line-height: 1.4;
white-space: nowrap;
cursor: pointer;
}
/* 丸いボタンは見えなくする(消さない。キーボードで選べるように残す) */
.kd-tab-list input {
position: absolute;
opacity: 0;
pointer-events: none;
}
/* キーボードで選んだときの枠。消さずに、はっきり見せる */
.kd-tab-list label:has(input:focus-visible) {
outline: 3px solid #0F172A;
outline-offset: -3px;
}
/* 切り替わる中身 */
.kd-tab-panel {
padding: 16px;
line-height: 1.8;
}
.kd-tab-panel p {
margin: 0;
}
/* 選ばれたタブの中身だけを見せる(:has() が使えないブラウザでは、全部見えたままになる) */
@supports selector(:has(*)) {
.kd-tab-panel {
display: none;
}
.kd-tab:has(.kd-tab-list > :nth-child(1) input:checked) .kd-tab-panels > :nth-child(1),
.kd-tab:has(.kd-tab-list > :nth-child(2) input:checked) .kd-tab-panels > :nth-child(2),
.kd-tab:has(.kd-tab-list > :nth-child(3) input:checked) .kd-tab-panels > :nth-child(3),
.kd-tab:has(.kd-tab-list > :nth-child(4) input:checked) .kd-tab-panels > :nth-child(4),
.kd-tab:has(.kd-tab-list > :nth-child(5) input:checked) .kd-tab-panels > :nth-child(5) {
display: block;
}
}
.kd-tab-folder .kd-tab-list {
gap: 4px;
padding: 0 8px;
}
.kd-tab-folder .kd-tab-list label {
margin-bottom: -1px;
border: 1px solid #D8E6F0;
border-top-width: 3px;
border-bottom: 0;
border-radius: 10px 10px 0 0;
background-color: #EEF3F7;
color: #33495B;
border-top-color: #D8E6F0;
transition: background-color 0.2s ease, color 0.2s ease, border-top-color 0.2s ease;
}
.kd-tab-folder .kd-tab-panels {
border: 1px solid #D8E6F0;
border-radius: 12px;
background: #FFFFFF;
}
.kd-tab-folder .kd-tab-list label:has(input:checked) {
background-color: #FFFFFF;
color: #0369A1;
border-top-color: #0284C7;
}
/* 動きを減らす設定の人には、色が変わる動きをなくして、すぐ切り替える */
@media (prefers-reduced-motion: reduce) {
.kd-tab-folder .kd-tab-list label {
transition: none;
}
}3. ボタン型
丸い枠の中に、ボタンを横に並べた形です。選んだものが青く塗られます。スマホのアプリでよく見る形なので、押せることが伝わりやすい見た目です。
カット 4,400円、カラー 6,600円です。表示はすべて税込みです。
平日は10時から19時、土日は9時から18時まで営業しています。
駅の東口から歩いて3分です。1階がパン屋さんの建物の2階です。
<div class="kd-tab kd-tab-pill">
<div class="kd-tab-list">
<label><input type="radio" name="kd-tab-shop" checked>料金</label>
<label><input type="radio" name="kd-tab-shop">営業時間</label>
<label><input type="radio" name="kd-tab-shop">アクセス</label>
</div>
<div class="kd-tab-panels">
<div class="kd-tab-panel"><p>カット 4,400円、カラー 6,600円です。表示はすべて税込みです。</p></div>
<div class="kd-tab-panel"><p>平日は10時から19時、土日は9時から18時まで営業しています。</p></div>
<div class="kd-tab-panel"><p>駅の東口から歩いて3分です。1階がパン屋さんの建物の2階です。</p></div>
</div>
</div>/* 5種類に共通:タブの箱 */
.kd-tab {
color: #0F172A;
text-align: left;
}
/* タブの並び(押すところ) */
.kd-tab-list {
display: flex;
}
.kd-tab-list label {
position: relative;
padding: 10px 16px;
font-weight: 700;
line-height: 1.4;
white-space: nowrap;
cursor: pointer;
}
/* 丸いボタンは見えなくする(消さない。キーボードで選べるように残す) */
.kd-tab-list input {
position: absolute;
opacity: 0;
pointer-events: none;
}
/* キーボードで選んだときの枠。消さずに、はっきり見せる */
.kd-tab-list label:has(input:focus-visible) {
outline: 3px solid #0F172A;
outline-offset: -3px;
}
/* 切り替わる中身 */
.kd-tab-panel {
padding: 16px;
line-height: 1.8;
}
.kd-tab-panel p {
margin: 0;
}
/* 選ばれたタブの中身だけを見せる(:has() が使えないブラウザでは、全部見えたままになる) */
@supports selector(:has(*)) {
.kd-tab-panel {
display: none;
}
.kd-tab:has(.kd-tab-list > :nth-child(1) input:checked) .kd-tab-panels > :nth-child(1),
.kd-tab:has(.kd-tab-list > :nth-child(2) input:checked) .kd-tab-panels > :nth-child(2),
.kd-tab:has(.kd-tab-list > :nth-child(3) input:checked) .kd-tab-panels > :nth-child(3),
.kd-tab:has(.kd-tab-list > :nth-child(4) input:checked) .kd-tab-panels > :nth-child(4),
.kd-tab:has(.kd-tab-list > :nth-child(5) input:checked) .kd-tab-panels > :nth-child(5) {
display: block;
}
}
.kd-tab-pill .kd-tab-list {
padding: 4px;
border-radius: 999px;
background: #EEF3F7;
}
.kd-tab-pill .kd-tab-list label {
flex: 1;
text-align: center;
border-radius: 999px;
background-color: #EEF3F7;
color: #33495B;
transition: background-color 0.2s ease, color 0.2s ease;
}
.kd-tab-pill .kd-tab-list label:has(input:focus-visible) {
outline-offset: 0;
}
.kd-tab-pill .kd-tab-panel {
padding: 14px 6px;
}
.kd-tab-pill .kd-tab-list label:has(input:checked) {
background-color: #0369A1;
color: #FFFFFF;
}
/* 動きを減らす設定の人には、色が変わる動きをなくして、すぐ切り替える */
@media (prefers-reduced-motion: reduce) {
.kd-tab-pill .kd-tab-list label {
transition: none;
}
}4. 中身がふわっと出る
タブを切り替えたとき、中身が少し下から、ふわっと現れます。切り替わった瞬間に中身だけが入れ替わる形と比べて、「中身が変わった」ことに気づきやすくなります。
カット 4,400円、カラー 6,600円です。表示はすべて税込みです。
平日は10時から19時、土日は9時から18時まで営業しています。
駅の東口から歩いて3分です。1階がパン屋さんの建物の2階です。
<div class="kd-tab kd-tab-fade">
<div class="kd-tab-list">
<label><input type="radio" name="kd-tab-shop" checked>料金</label>
<label><input type="radio" name="kd-tab-shop">営業時間</label>
<label><input type="radio" name="kd-tab-shop">アクセス</label>
</div>
<div class="kd-tab-panels">
<div class="kd-tab-panel"><p>カット 4,400円、カラー 6,600円です。表示はすべて税込みです。</p></div>
<div class="kd-tab-panel"><p>平日は10時から19時、土日は9時から18時まで営業しています。</p></div>
<div class="kd-tab-panel"><p>駅の東口から歩いて3分です。1階がパン屋さんの建物の2階です。</p></div>
</div>
</div>/* 5種類に共通:タブの箱 */
.kd-tab {
color: #0F172A;
text-align: left;
}
/* タブの並び(押すところ) */
.kd-tab-list {
display: flex;
}
.kd-tab-list label {
position: relative;
padding: 10px 16px;
font-weight: 700;
line-height: 1.4;
white-space: nowrap;
cursor: pointer;
}
/* 丸いボタンは見えなくする(消さない。キーボードで選べるように残す) */
.kd-tab-list input {
position: absolute;
opacity: 0;
pointer-events: none;
}
/* キーボードで選んだときの枠。消さずに、はっきり見せる */
.kd-tab-list label:has(input:focus-visible) {
outline: 3px solid #0F172A;
outline-offset: -3px;
}
/* 切り替わる中身 */
.kd-tab-panel {
padding: 16px;
line-height: 1.8;
}
.kd-tab-panel p {
margin: 0;
}
/* 選ばれたタブの中身だけを見せる(:has() が使えないブラウザでは、全部見えたままになる) */
@supports selector(:has(*)) {
.kd-tab-panel {
display: none;
}
.kd-tab:has(.kd-tab-list > :nth-child(1) input:checked) .kd-tab-panels > :nth-child(1),
.kd-tab:has(.kd-tab-list > :nth-child(2) input:checked) .kd-tab-panels > :nth-child(2),
.kd-tab:has(.kd-tab-list > :nth-child(3) input:checked) .kd-tab-panels > :nth-child(3),
.kd-tab:has(.kd-tab-list > :nth-child(4) input:checked) .kd-tab-panels > :nth-child(4),
.kd-tab:has(.kd-tab-list > :nth-child(5) input:checked) .kd-tab-panels > :nth-child(5) {
display: block;
}
}
.kd-tab-fade .kd-tab-list {
gap: 6px;
}
.kd-tab-fade .kd-tab-list label {
border-radius: 8px;
background-color: #FFFFFF;
color: #5B6B78;
transition: background-color 0.25s ease, color 0.25s ease;
}
.kd-tab-fade .kd-tab-panel {
margin-top: 8px;
border-radius: 12px;
background: #F2F8FC;
animation: kd-tab-fade 0.4s ease;
}
@keyframes kd-tab-fade {
from {
opacity: 0;
transform: translateY(8px);
}
to {
opacity: 1;
transform: none;
}
}
.kd-tab-fade .kd-tab-list label:has(input:checked) {
background-color: #E0F2FE;
color: #0369A1;
}
/* 動きを減らす設定の人には、色が変わる動きと、中身がふわっと出る動きをなくして、すぐ切り替える */
@media (prefers-reduced-motion: reduce) {
.kd-tab-fade .kd-tab-list label {
transition: none;
}
.kd-tab-fade .kd-tab-panel {
animation: none;
}
}5. 多いときは横にスクロール
タブが多くて横に入りきらないとき、折り返さずに、指で横にずらして選べるようにした形です。右端を少し薄くして、「まだ続きがある」ことを見せています。
月曜日は定休日です。
火曜日は10時から19時までです。
水曜日は10時から19時までです。
木曜日は10時から21時までです。夜の予約が取れます。
金曜日は10時から19時までです。
<div class="kd-tab kd-tab-scroll">
<div class="kd-tab-list">
<label><input type="radio" name="kd-tab-shop" checked>月</label>
<label><input type="radio" name="kd-tab-shop">火</label>
<label><input type="radio" name="kd-tab-shop">水</label>
<label><input type="radio" name="kd-tab-shop">木</label>
<label><input type="radio" name="kd-tab-shop">金</label>
</div>
<div class="kd-tab-panels">
<div class="kd-tab-panel"><p>月曜日は定休日です。</p></div>
<div class="kd-tab-panel"><p>火曜日は10時から19時までです。</p></div>
<div class="kd-tab-panel"><p>水曜日は10時から19時までです。</p></div>
<div class="kd-tab-panel"><p>木曜日は10時から21時までです。夜の予約が取れます。</p></div>
<div class="kd-tab-panel"><p>金曜日は10時から19時までです。</p></div>
</div>
</div>/* 5種類に共通:タブの箱 */
.kd-tab {
color: #0F172A;
text-align: left;
}
/* タブの並び(押すところ) */
.kd-tab-list {
display: flex;
}
.kd-tab-list label {
position: relative;
padding: 10px 16px;
font-weight: 700;
line-height: 1.4;
white-space: nowrap;
cursor: pointer;
}
/* 丸いボタンは見えなくする(消さない。キーボードで選べるように残す) */
.kd-tab-list input {
position: absolute;
opacity: 0;
pointer-events: none;
}
/* キーボードで選んだときの枠。消さずに、はっきり見せる */
.kd-tab-list label:has(input:focus-visible) {
outline: 3px solid #0F172A;
outline-offset: -3px;
}
/* 切り替わる中身 */
.kd-tab-panel {
padding: 16px;
line-height: 1.8;
}
.kd-tab-panel p {
margin: 0;
}
/* 選ばれたタブの中身だけを見せる(:has() が使えないブラウザでは、全部見えたままになる) */
@supports selector(:has(*)) {
.kd-tab-panel {
display: none;
}
.kd-tab:has(.kd-tab-list > :nth-child(1) input:checked) .kd-tab-panels > :nth-child(1),
.kd-tab:has(.kd-tab-list > :nth-child(2) input:checked) .kd-tab-panels > :nth-child(2),
.kd-tab:has(.kd-tab-list > :nth-child(3) input:checked) .kd-tab-panels > :nth-child(3),
.kd-tab:has(.kd-tab-list > :nth-child(4) input:checked) .kd-tab-panels > :nth-child(4),
.kd-tab:has(.kd-tab-list > :nth-child(5) input:checked) .kd-tab-panels > :nth-child(5) {
display: block;
}
}
.kd-tab-scroll .kd-tab-list {
gap: 8px;
padding: 4px 32px 8px 2px;
overflow-x: auto;
mask-image: linear-gradient(to right, #000 80%, transparent);
}
.kd-tab-scroll .kd-tab-list label {
min-width: 64px;
text-align: center;
border: 1px solid #D8E6F0;
border-radius: 999px;
background-color: #FFFFFF;
color: #33495B;
border-color: #D8E6F0;
transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease;
}
.kd-tab-scroll .kd-tab-panel {
padding: 12px 4px;
}
.kd-tab-scroll .kd-tab-list label:has(input:checked) {
background-color: #0369A1;
color: #FFFFFF;
border-color: #0369A1;
}
/* 動きを減らす設定の人には、色が変わる動きをなくして、すぐ切り替える */
@media (prefers-reduced-motion: reduce) {
.kd-tab-scroll .kd-tab-list label {
transition: none;
}
}※ 同じページにタブを2つ以上置くときは、ラジオボタンの name="kd-tab-shop" を、タブごとに別の名前にしてください。同じ名前のままだと、片方を押したときに、もう片方の選択が外れてしまいます。
仕組み:選ぶのはラジオボタン、見せ分けるのは :has()
タブの正体は、見えなくしたラジオボタン
ラジオボタンは、アンケートの「1つだけ選ぶ」丸いボタンです。同じ name(名前)を付けたラジオボタンは1つのグループになり、1つを選ぶと、ほかの選択は自動で外れます。「タブは常に1つだけ選ばれている」という動きと同じです。
見本では、ラジオボタンを <label>(ラベル=文字を押してもボタンを選べるようにする部品)の中に入れ、丸いボタンだけを opacity: 0 で見えなくしています。display: none で消してしまうと、キーボードで選べなくなるので、見えなくするだけにとどめています。
:has() で「選ばれたタブの中身」を見せる
中身(.kd-tab-panel)は、ふだんは display: none で隠しておきます。そのうえで、「2番目のラベルの中のラジオボタンが選ばれている .kd-tab の中の、2番目の中身」だけを表示します。これを書けるのが :has() です。
| 書いたもの | 意味 |
|---|---|
| .kd-tab-list label:has(input:checked) | 選ばれたラジオボタンを中に持つラベル=選ばれたタブ |
| .kd-tab:has(.kd-tab-list > :nth-child(2) input:checked) | 2番目のタブが選ばれているタブの箱 |
| … .kd-tab-panels > :nth-child(2) | その箱の中の、2番目の中身 |
タブを4つ、5つに増やすときは、:nth-child(4) の行も入れておけば、そのまま使えます。共通のCSSには5つ目までの行を入れてあります。
:has() が使えないブラウザでは、全部見せる
MDNでは、:has() は2023年12月から主要なブラウザで使える機能と示されています。一方、:has() を読めないブラウザでは、その書き方を含むルール全体が無視されます。中身を隠す指定だけが効いて、表示する指定が無視されると、どのタブを押しても何も出ない、という一番困る状態になります。
そこで見本では、隠す指定と表示する指定をまとめて @supports selector(:has(*)) の中に入れています。「:has() が使えるブラウザのときだけ、この中を使う」という意味です。使えないブラウザでは、タブの切り替えにはならず、中身が3つとも縦に並んで見えます。読めなくなるわけではありません。
「ふわっと」は animation で
4番目の見本は、中身に1回だけ再生される animation を付けています。display: none から表示に変わったとき、アニメーションは最初から再生されるので、タブを押すたびに中身がふわっと現れます。
数値を変えるとどうなるか
「下線が伸びる」の数値を1つずつ変えて並べました。ここの見本も、自動で切り替わり続けています。
速さ:線が伸びる秒数
カット 4,400円、カラー 6,600円です。表示はすべて税込みです。
平日は10時から19時、土日は9時から18時まで営業しています。
駅の東口から歩いて3分です。1階がパン屋さんの建物の2階です。
カット 4,400円、カラー 6,600円です。表示はすべて税込みです。
平日は10時から19時、土日は9時から18時まで営業しています。
駅の東口から歩いて3分です。1階がパン屋さんの建物の2階です。
(見本)
カット 4,400円、カラー 6,600円です。表示はすべて税込みです。
平日は10時から19時、土日は9時から18時まで営業しています。
駅の東口から歩いて3分です。1階がパン屋さんの建物の2階です。
0.05秒だと、線が伸びる途中はほとんど見えず、ぱっと切り替わります。0.8秒だと、中身はもう入れ替わっているのに、線がゆっくり追いかけてきて、遅れて見えます。中身は一瞬で入れ替わるので、線の動きは0.2〜0.3秒あたりにすると、ずれを感じにくくなります。
押せる範囲:タブの上下の余白
カット 4,400円、カラー 6,600円です。表示はすべて税込みです。
平日は10時から19時、土日は9時から18時まで営業しています。
駅の東口から歩いて3分です。1階がパン屋さんの建物の2階です。
カット 4,400円、カラー 6,600円です。表示はすべて税込みです。
平日は10時から19時、土日は9時から18時まで営業しています。
駅の東口から歩いて3分です。1階がパン屋さんの建物の2階です。
(見本)
カット 4,400円、カラー 6,600円です。表示はすべて税込みです。
平日は10時から19時、土日は9時から18時まで営業しています。
駅の東口から歩いて3分です。1階がパン屋さんの建物の2階です。
水色の部分が、押すと切り替わる範囲です。上下2pxだと、タブの高さが文字の高さとほとんど変わらず、指で押すには細くなります。W3Cのアクセシビリティの基準(WCAG 2.2)では、押す場所は少なくとも24×24 CSSピクセルが目安とされています。タブは横に並んで隣と接しているので、押し間違えないよう、上下にも左右にも余裕を持たせておきます。
スマホと、見る人への配慮
スマホでは、タブの数と文字の長さに気をつける
スマホの幅(390px前後)だと、横に並べられるタブは、短い言葉で3〜4つが目安です。それより多いときは、5番目の見本のように横にスクロールさせるか、タブをやめてアコーディオンにします。タブの文字が2行に折り返すと、どこまでが1つのタブか分かりにくくなるので、見本では white-space: nowrap(折り返さない指定)を入れています。
キーボードでも切り替えられる
ラジオボタンを残しているので、Tabキーでタブの並びに入り、左右の矢印キーで切り替えられます。見本では、選んだときの枠を消さずに、:focus-visible(フォーカス・ビジブル=キーボードなどで選んだ状態)で太く見せています。
読み上げでは「タブ」ではなく「ラジオボタン」と伝わる
ここは、CSSだけで作るタブの限界です。読み上げソフトでは、この見本は「タブ」ではなく、ラジオボタンの選択肢として読み上げられます。中身を選ぶことはできますが、「タブを切り替える部品だ」という形では伝わりません。W3Cがまとめているタブの作り方(tablist・tab・tabpanel という役割を付ける方法)は、矢印キーの動きなどをJavaScriptで作り込む前提になっています。
お店の情報を切り替える程度ならCSSだけの形で十分ですが、申し込みの手順や、読み飛ばされると困る説明をタブにするなら、JavaScriptで正式な形にするか、タブにせずに並べて見せるほうが確実です。
隠れた中身は、ページ内検索で見つからない
display: none で隠した中身は、ブラウザのページ内検索(Ctrl+F や「ページ内を検索」)では見つかりません。探しても出てこない情報は、ないのと同じに見えてしまいます。料金やキャンセルの条件など、全員に必ず読んでほしいことは、タブの中だけに書かないようにします。
「動きを減らす」設定の人には、すぐ切り替える
画面の動きが苦手な人は、スマホやパソコンで「動きを減らす」設定をオンにしていることがあります。CSSの prefers-reduced-motion を使うと、この設定の人にだけ別の見え方を出せます。見本では、線が伸びる動きや色の変化、中身がふわっと出る動きをなくしています。切り替えそのものは、そのまま使えます。

タブは見た目がすっきりする分、2つ目より後ろは押されないと読まれません。
「1つ目だけ読んで帰る人がいても困らないか」を、置く前に考えておきたい部品です。
実際に押して試す
ここの見本は自動では動きません。押して切り替えてみてください。キーボードなら、Tabキーでタブの並びに入り、左右の矢印キーで切り替えます。5番目は、スマホなら指で横にずらせます。
カット 4,400円、カラー 6,600円です。表示はすべて税込みです。
平日は10時から19時、土日は9時から18時まで営業しています。
駅の東口から歩いて3分です。1階がパン屋さんの建物の2階です。
カット 4,400円、カラー 6,600円です。表示はすべて税込みです。
平日は10時から19時、土日は9時から18時まで営業しています。
駅の東口から歩いて3分です。1階がパン屋さんの建物の2階です。
カット 4,400円、カラー 6,600円です。表示はすべて税込みです。
平日は10時から19時、土日は9時から18時まで営業しています。
駅の東口から歩いて3分です。1階がパン屋さんの建物の2階です。
カット 4,400円、カラー 6,600円です。表示はすべて税込みです。
平日は10時から19時、土日は9時から18時まで営業しています。
駅の東口から歩いて3分です。1階がパン屋さんの建物の2階です。
月曜日は定休日です。
火曜日は10時から19時までです。
水曜日は10時から19時までです。
木曜日は10時から21時までです。夜の予約が取れます。
金曜日は10時から19時までです。
AIに頼むなら
同じような部品は、Claudeなどの生成AIに頼んでもすぐ作れます。そのときは、見た目だけでなく、作り方・数値・配慮まで伝えると、直す回数が減ります。
お店の「料金・営業時間・アクセス」を切り替えるタブを、JavaScriptなしで作りたい。
ラジオボタンを label の中に入れて見えなくし、:has() で選ばれたタブの中身だけ表示して。:has() が使えないブラウザでは中身を全部見せる形に。
選んだタブの下に青い線を0.25秒で伸ばす。キーボードで選んだときの枠は消さない。動きを減らす設定の人には動きをなくして。AIが出したコードは、そのまま使う前に、次の3つを確かめます。
- ラジオボタンを display: none で消していないか。見た目は同じでも、キーボードで選べなくなります。Tabキーでタブの並びに入れるか、矢印キーで切り替わるかを試します
- name が、ほかのタブと重なっていないか。同じページに2つ置いたとき、片方を押すともう片方の選択が外れるなら、name が同じになっています
- スマホの幅で、タブが折り返したり、はみ出したりしていないか。パソコンの画面できれいでも、390px前後の幅で見るとタブが2段になることがあります。ブラウザの開発者ツールでスマホの幅にして確かめます
KAKERUならこう使う/使わない
最後に、作り手としての判断です。
使う場面
- お店や会社の「料金・営業時間・アクセス」の切り替えには「下線が伸びる」か「見出しタブ」。どれか1つを知りたい人が多く、全部を順に読む必要がない情報です
- 「個人のお客様/法人のお客様」のように、読む人が自分で入口を選ぶ場面には「ボタン型」。選ぶ部品だと分かりやすく、押したあとに自分向けの情報だけが残ります
- 曜日や月ごとの予定のように、数が多くて順番が決まっているものには「横にスクロール」。今日の曜日を最初に選んでおくと、見る人が探す手間が減ります
使わない場面
- 料金プランの比較。比べたい情報をタブで分けると、行ったり来たりしないと比べられません。表で並べて見せます
- タブが2つしかないとき、中身が1〜2行しかないとき。切り替える手間のほうが大きいので、最初から並べます
- スマホで中身が長いとき。タブの並びが画面の上に消えてしまい、切り替えるたびに戻ってくる必要があります。この場合は、前々回のアコーディオンのほうが向いています

タブとアコーディオンは、どちらも情報を畳む部品です。
「横に並んだ同じ種類の情報から1つ選ぶ」ならタブ、「縦に並んだ質問から気になるものを開く」ならアコーディオン、と分けて考えると迷いません。
まとめ
- タブ切り替えは、見えなくしたラジオボタンと、CSSの :has() で、JavaScriptなしで作れる
- ラジオボタンは display: none で消さず、opacity: 0 で見えなくするだけにして、キーボードで選べるように残す
- :has() が使えないブラウザに備えて、@supports で囲み、使えないときは中身を全部見せる
- 同じページに2つ以上置くときは、name をタブごとに変える
- 読み上げでは「タブ」として伝わらない。読み飛ばされると困る情報は、タブの中だけに書かない
まずは「下線が伸びる」のコードをコピーして、自分のサイトの情報に書きかえてみてください。HTMLとCSSだけで切り替わるのが分かります。
参考にした資料
いずれも2026年10月14日に確認しました。ブラウザの対応状況は変わることがあります。最新の情報は各ページでご確認ください。
- MDN「:has()」
https://developer.mozilla.org/ja/docs/Web/CSS/Reference/Selectors/:has
※中に条件に合うものがある要素を選ぶ書き方。使えないブラウザではそのルール全体が無視されること、対応状況(英語版で確認) - MDN「@supports」
https://developer.mozilla.org/ja/docs/Web/CSS/Reference/At-rules/@supports
※selector() で、その書き方が使えるブラウザだけにCSSを当てる方法 - MDN「<input type=”radio”>」
https://developer.mozilla.org/ja/docs/Web/HTML/Reference/Elements/input/radio
※同じ name のラジオボタンが1つのグループになり、1つ選ぶとほかの選択が外れること - MDN「:checked」
https://developer.mozilla.org/ja/docs/Web/CSS/Reference/Selectors/:checked
※選ばれているラジオボタンに当てはまること、対応状況 - MDN「:focus-visible」
https://developer.mozilla.org/ja/docs/Web/CSS/Reference/Selectors/:focus-visible
※キーボード操作などで選んだときに当てはまること、対応状況 - MDN「overflow-x」
https://developer.mozilla.org/ja/docs/Web/CSS/Reference/Properties/overflow-x
※横にはみ出した分をスクロールで見せる指定 - MDN「mask-image」
https://developer.mozilla.org/ja/docs/Web/CSS/Reference/Properties/mask-image
※右端を薄くする指定、対応状況 - MDN「transition」
https://developer.mozilla.org/ja/docs/Web/CSS/Reference/Properties/transition
※変化を滑らかにする指定、対応状況 - MDN「prefers-reduced-motion」
https://developer.mozilla.org/ja/docs/Web/CSS/Reference/At-rules/@media/prefers-reduced-motion
※値の意味、OSごとの設定、対応状況 - W3C「Tabs Pattern」(ARIA Authoring Practices Guide)
https://www.w3.org/WAI/ARIA/apg/patterns/tabs/
※読み上げソフトに「タブ」と伝える部品の作り方(tablist・tab・tabpanel の役割とキーボード操作) - W3C「Understanding SC 2.5.8: Target Size (Minimum)」
https://www.w3.org/WAI/WCAG22/Understanding/target-size-minimum.html
※押す場所は少なくとも24×24 CSSピクセル(まわりの余白で代えられる場合あり)
ふだんの制作の話や、作ったものの裏側は Instagram と X でも出しています。






















連載「WEBの技術」の9回目です。
前々回のアコーディオンに続いて、今回もページの部品です。どちらも「情報を畳む」部品なので、使い分けも最後に書きます。