CSSで作るタブUIデザイン集【コピペOK】

タブUIは、限られたスペースの中で複数の情報を切り替えて表示できるUIコンポーネントです。商品の仕様や料金プラン、設定画面など、さまざまな場面で使われています。
この記事では、CSSだけで作れるタブから、アクセシビリティに対応したJavaScript版まで、実務で使えるタブUIデザインを11パターン紹介します。すべてコピペOKです。
タブUIとは?
タブUIとは、見出し(タブ)を選ぶと、対応するコンテンツ(パネル)だけが表示されるUIのことです。
- 商品ページの「概要・仕様・レビュー」
- 料金ページの「月額・年額」の切り替え
- マイページや管理画面の設定メニュー
- 記事一覧のカテゴリ切り替え
など、情報を整理して見せたい場面でよく利用されています。
CSSだけでタブを切り替える仕組み
この記事のCSS版タブは、ラジオボタンの「どれか1つだけ選べる」という性質を利用しています。
- タブごとに
input type="radio"を用意し、同じnameを付けて1つのグループにする labelのfor属性でラジオボタンと結び付け、タブの見た目はlabelで作る:checkedと兄弟セレクタ(+・~)、または:has()で、選ばれたタブとパネルにスタイルを当てる
ラジオボタン本体は display: none ではなく opacity: 0 で見えなくしています。display: none にするとキーボードで操作できなくなるためです。opacity: 0 であればフォーカスを受け取れるので、Tabキーでタブへ移動し、矢印キーで切り替えられます。
:has() は、2023年12月以降、主要なブラウザ(Chrome・Edge・Safari・Firefox)の最新版で利用できます。
基本のタブUI
① シンプルなタブ
<div class="tabs-basic">
<input type="radio" name="tabs-basic" id="tabs-basic-1" checked>
<label for="tabs-basic-1">HTML</label>
<input type="radio" name="tabs-basic" id="tabs-basic-2">
<label for="tabs-basic-2">CSS</label>
<input type="radio" name="tabs-basic" id="tabs-basic-3">
<label for="tabs-basic-3">JavaScript</label>
<div class="tabs-basic-panel">HTMLは、Webページの構造を作るマークアップ言語です。</div>
<div class="tabs-basic-panel">CSSは、Webページの見た目を整えるスタイルシート言語です。</div>
<div class="tabs-basic-panel">JavaScriptは、Webページに動きを加えるプログラミング言語です。</div>
</div>.tabs-basic {
position: relative;
display: flex;
flex-wrap: wrap;
}
.tabs-basic input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.tabs-basic label {
padding: 10px 20px;
background: #eee;
cursor: pointer;
}
.tabs-basic input:checked + label {
color: #fff;
background: #3498db;
}
.tabs-basic input:focus-visible + label {
outline: 2px solid #3498db;
outline-offset: 2px;
}
.tabs-basic-panel {
display: none;
width: 100%;
padding: 20px;
border: 1px solid #ddd;
}
#tabs-basic-1:checked ~ .tabs-basic-panel:nth-of-type(1),
#tabs-basic-2:checked ~ .tabs-basic-panel:nth-of-type(2),
#tabs-basic-3:checked ~ .tabs-basic-panel:nth-of-type(3) {
display: block;
}最も基本的なタブです。input・label・パネルをすべて同じ階層に並べ、:checked ~ で選ばれた番号のパネルだけを表示しています。親要素を flex-wrap: wrap にし、パネルを width: 100% にすることで、タブの下にパネルが回り込みます。
② 下線タブ
<div class="tabs-line">
<input type="radio" name="tabs-line" id="tabs-line-1" checked>
<label for="tabs-line-1">新着</label>
<input type="radio" name="tabs-line" id="tabs-line-2">
<label for="tabs-line-2">人気</label>
<input type="radio" name="tabs-line" id="tabs-line-3">
<label for="tabs-line-3">おすすめ</label>
<div class="tabs-line-panel">新着記事の一覧を表示します。</div>
<div class="tabs-line-panel">よく読まれている記事を表示します。</div>
<div class="tabs-line-panel">編集部のおすすめ記事を表示します。</div>
</div>.tabs-line {
position: relative;
display: flex;
flex-wrap: wrap;
}
.tabs-line input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.tabs-line label {
padding: 12px 20px;
color: #666;
border-bottom: 3px solid transparent;
cursor: pointer;
transition: color 0.3s, border-color 0.3s;
}
.tabs-line label:hover {
color: #3498db;
}
.tabs-line input:checked + label {
color: #3498db;
font-weight: bold;
border-bottom-color: #3498db;
}
.tabs-line input:focus-visible + label {
outline: 2px solid #3498db;
outline-offset: -2px;
}
.tabs-line-panel {
display: none;
width: 100%;
padding: 20px 0;
border-top: 1px solid #ddd;
}
#tabs-line-1:checked ~ .tabs-line-panel:nth-of-type(1),
#tabs-line-2:checked ~ .tabs-line-panel:nth-of-type(2),
#tabs-line-3:checked ~ .tabs-line-panel:nth-of-type(3) {
display: block;
}選択中のタブに下線を引くデザインです。ニュースサイトやブログの記事一覧でよく使われます。パネルの上線とタブの下線が重なり、タブとパネルのつながりが分かりやすくなります。
③ ピル型タブ
<div class="tabs-pill">
<input type="radio" name="tabs-pill" id="tabs-pill-1" checked>
<label for="tabs-pill-1">月額</label>
<input type="radio" name="tabs-pill" id="tabs-pill-2">
<label for="tabs-pill-2">年額</label>
<input type="radio" name="tabs-pill" id="tabs-pill-3">
<label for="tabs-pill-3">法人</label>
<div class="tabs-pill-panel">月額プラン:1,000円/月。いつでも解約できます。</div>
<div class="tabs-pill-panel">年額プラン:10,000円/年。2か月分お得です。</div>
<div class="tabs-pill-panel">法人プラン:人数に応じてお見積もりします。</div>
</div>.tabs-pill {
position: relative;
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.tabs-pill input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.tabs-pill label {
padding: 8px 24px;
background: #f0f2f5;
border-radius: 999px;
cursor: pointer;
transition: color 0.3s, background 0.3s;
}
.tabs-pill input:checked + label {
color: #fff;
background: #3498db;
}
.tabs-pill input:focus-visible + label {
outline: 2px solid #3498db;
outline-offset: 2px;
}
.tabs-pill-panel {
display: none;
width: 100%;
margin-top: 8px;
padding: 20px;
background: #f7f9fc;
border-radius: 12px;
}
#tabs-pill-1:checked ~ .tabs-pill-panel:nth-of-type(1),
#tabs-pill-2:checked ~ .tabs-pill-panel:nth-of-type(2),
#tabs-pill-3:checked ~ .tabs-pill-panel:nth-of-type(3) {
display: block;
}角を完全に丸めたピル型のタブです。border-radius: 999px を指定すると、高さに関係なく両端が半円になります。料金プランの切り替えなど、選択肢の少ないタブに向いています。
アニメーション付きタブ
④ 下線がスライドするタブ
<div class="tabs-slide">
<div class="tabs-slide-nav">
<input type="radio" name="tabs-slide" id="tabs-slide-1" checked>
<label for="tabs-slide-1">概要</label>
<input type="radio" name="tabs-slide" id="tabs-slide-2">
<label for="tabs-slide-2">仕様</label>
<input type="radio" name="tabs-slide" id="tabs-slide-3">
<label for="tabs-slide-3">レビュー</label>
<span class="tabs-slide-indicator"></span>
</div>
<div class="tabs-slide-body">
<div class="tabs-slide-panel">商品の概要を表示します。</div>
<div class="tabs-slide-panel">サイズや重量などの仕様を表示します。</div>
<div class="tabs-slide-panel">購入者のレビューを表示します。</div>
</div>
</div>.tabs-slide-nav {
position: relative;
display: grid;
grid-template-columns: repeat(3, 1fr);
border-bottom: 1px solid #ddd;
}
.tabs-slide-nav input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.tabs-slide-nav label {
padding: 12px;
color: #666;
text-align: center;
cursor: pointer;
transition: color 0.3s;
}
.tabs-slide-nav input:checked + label {
color: #3498db;
font-weight: bold;
}
.tabs-slide-nav input:focus-visible + label {
outline: 2px solid #3498db;
outline-offset: -2px;
}
.tabs-slide-indicator {
position: absolute;
bottom: -1px;
left: 0;
width: calc(100% / 3);
height: 3px;
background: #3498db;
transition: translate 0.3s ease;
}
.tabs-slide:has(#tabs-slide-2:checked) .tabs-slide-indicator {
translate: 100% 0;
}
.tabs-slide:has(#tabs-slide-3:checked) .tabs-slide-indicator {
translate: 200% 0;
}
.tabs-slide-panel {
display: none;
padding: 20px 0;
}
.tabs-slide:has(#tabs-slide-1:checked) .tabs-slide-panel:nth-child(1),
.tabs-slide:has(#tabs-slide-2:checked) .tabs-slide-panel:nth-child(2),
.tabs-slide:has(#tabs-slide-3:checked) .tabs-slide-panel:nth-child(3) {
display: block;
}選んだタブへ下線が滑らかに移動するタブです。下線は1本の span で作り、:has() でどのタブが選ばれているかを判定して translate で動かしています。タブを grid-template-columns: repeat(3, 1fr) で等幅にしているため、移動量は下線の幅の100%・200%で指定できます。
:has() を使うと、input とパネルを別の要素に分けて配置できます。パネルの順番は .tabs-slide-body の中での :nth-child() で指定しています。
⑤ フェードインで切り替わるタブ
<div class="tabs-fade">
<input type="radio" name="tabs-fade" id="tabs-fade-1" checked>
<label for="tabs-fade-1">STEP 1</label>
<input type="radio" name="tabs-fade" id="tabs-fade-2">
<label for="tabs-fade-2">STEP 2</label>
<input type="radio" name="tabs-fade" id="tabs-fade-3">
<label for="tabs-fade-3">STEP 3</label>
<div class="tabs-fade-panel">アカウントを作成します。</div>
<div class="tabs-fade-panel">プロフィールを設定します。</div>
<div class="tabs-fade-panel">サービスの利用を開始します。</div>
</div>.tabs-fade {
position: relative;
display: flex;
flex-wrap: wrap;
gap: 4px;
}
.tabs-fade input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.tabs-fade label {
padding: 10px 20px;
color: #3498db;
border: 1px solid #3498db;
border-radius: 6px 6px 0 0;
cursor: pointer;
}
.tabs-fade input:checked + label {
color: #fff;
background: #3498db;
}
.tabs-fade input:focus-visible + label {
outline: 2px solid #3498db;
outline-offset: 2px;
}
.tabs-fade-panel {
display: none;
width: 100%;
padding: 24px;
border: 1px solid #3498db;
}
#tabs-fade-1:checked ~ .tabs-fade-panel:nth-of-type(1),
#tabs-fade-2:checked ~ .tabs-fade-panel:nth-of-type(2),
#tabs-fade-3:checked ~ .tabs-fade-panel:nth-of-type(3) {
display: block;
animation: tabs-fade-in 0.4s ease;
}
@keyframes tabs-fade-in {
from {
opacity: 0;
translate: 0 10px;
}
to {
opacity: 1;
translate: 0 0;
}
}
@media (prefers-reduced-motion: reduce) {
.tabs-fade-panel {
animation: none !important;
}
}パネルが下からふわっと表示されるタブです。display が none から block に変わったタイミングで @keyframes のアニメーションが再生されます。通常の transition では display の切り替えをアニメーションできないため、ここでは animation を使っています。
動きを減らす設定をしている利用者向けに、prefers-reduced-motion でアニメーションを止めています。表示用のセレクタはIDを含み詳細度が高いため、!important で上書きしています。
モダンデザインのタブ
⑥ Glassmorphismタブ
<div class="tabs-glass">
<input type="radio" name="tabs-glass" id="tabs-glass-1" checked>
<label for="tabs-glass-1">Design</label>
<input type="radio" name="tabs-glass" id="tabs-glass-2">
<label for="tabs-glass-2">Develop</label>
<input type="radio" name="tabs-glass" id="tabs-glass-3">
<label for="tabs-glass-3">Release</label>
<div class="tabs-glass-panel">デザインカンプを作成し、UIの方向性を決めます。</div>
<div class="tabs-glass-panel">HTML・CSS・JavaScriptで画面を実装します。</div>
<div class="tabs-glass-panel">テストを行い、本番環境へ公開します。</div>
</div>.tabs-glass {
position: relative;
display: flex;
flex-wrap: wrap;
gap: 8px;
padding: 40px 24px;
overflow: hidden;
background: linear-gradient(135deg, #4facfe, #8e54e9);
border-radius: 16px;
}
.tabs-glass::before,
.tabs-glass::after {
content: "";
position: absolute;
border-radius: 50%;
}
.tabs-glass::before {
top: -40px;
left: -40px;
width: 160px;
height: 160px;
background: #ff9a9e;
}
.tabs-glass::after {
right: -30px;
bottom: -50px;
width: 200px;
height: 200px;
background: #fad0c4;
}
.tabs-glass input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.tabs-glass label {
position: relative;
z-index: 1;
padding: 10px 20px;
color: #fff;
border: 1px solid rgba(255, 255, 255, 0.4);
border-radius: 999px;
cursor: pointer;
transition: background 0.3s;
}
.tabs-glass input:checked + label {
background: rgba(255, 255, 255, 0.3);
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
}
.tabs-glass input:focus-visible + label {
outline: 2px solid #fff;
outline-offset: 2px;
}
.tabs-glass-panel {
position: relative;
z-index: 1;
display: none;
width: 100%;
padding: 24px;
color: #fff;
background: rgba(255, 255, 255, 0.15);
border: 1px solid rgba(255, 255, 255, 0.4);
border-radius: 16px;
-webkit-backdrop-filter: blur(12px);
backdrop-filter: blur(12px);
}
#tabs-glass-1:checked ~ .tabs-glass-panel:nth-of-type(1),
#tabs-glass-2:checked ~ .tabs-glass-panel:nth-of-type(2),
#tabs-glass-3:checked ~ .tabs-glass-panel:nth-of-type(3) {
display: block;
}すりガラスのような質感のタブです。背景のグラデーションと装飾の円の上に、半透明の背景と backdrop-filter: blur() を重ねています。Safariの古いバージョン向けに -webkit-backdrop-filter も指定しています。
ガラス表現の詳しい作り方は「CSSで作るガラスUI(Glassmorphism)完全ガイド」で解説しています。
⑦ タブごとに色が変わるタブ
<div class="tabs-color">
<input type="radio" name="tabs-color" id="tabs-color-1" checked>
<label for="tabs-color-1">お知らせ</label>
<input type="radio" name="tabs-color" id="tabs-color-2">
<label for="tabs-color-2">イベント</label>
<input type="radio" name="tabs-color" id="tabs-color-3">
<label for="tabs-color-3">キャンペーン</label>
<div class="tabs-color-panel">メンテナンスのお知らせを掲載しています。</div>
<div class="tabs-color-panel">オンライン勉強会を開催します。</div>
<div class="tabs-color-panel">期間限定のキャンペーンを実施中です。</div>
</div>.tabs-color {
--tab-color: #3498db;
position: relative;
display: flex;
flex-wrap: wrap;
gap: 4px;
}
.tabs-color:has(#tabs-color-2:checked) {
--tab-color: #e67e22;
}
.tabs-color:has(#tabs-color-3:checked) {
--tab-color: #27ae60;
}
.tabs-color input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.tabs-color label {
padding: 10px 20px;
background: #eee;
border-radius: 8px 8px 0 0;
cursor: pointer;
transition: color 0.3s, background 0.3s;
}
.tabs-color input:checked + label {
color: #fff;
background: var(--tab-color);
}
.tabs-color input:focus-visible + label {
outline: 2px solid var(--tab-color);
outline-offset: 2px;
}
.tabs-color-panel {
display: none;
width: 100%;
margin-top: -4px;
padding: 24px;
background: #fff;
border-top: 4px solid var(--tab-color);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
}
#tabs-color-1:checked ~ .tabs-color-panel:nth-of-type(1),
#tabs-color-2:checked ~ .tabs-color-panel:nth-of-type(2),
#tabs-color-3:checked ~ .tabs-color-panel:nth-of-type(3) {
display: block;
}選んだタブに合わせて、タブとパネルの色がまとめて切り替わるデザインです。親要素にCSS変数 --tab-color を定義し、:has() で選ばれたタブに応じて変数の値だけを書き換えています。色を使う箇所はすべて var(--tab-color) を参照しているため、タブを増やしても変数を1行追加するだけで済みます。
CSS変数の基本は「CSS変数(カスタムプロパティ)の使い方と活用例」で解説しています。
実務で使える応用パターン
⑧ 縦タブ(サイドメニュー型)
<div class="tabs-vertical">
<div class="tabs-vertical-nav">
<input type="radio" name="tabs-vertical" id="tabs-vertical-1" checked>
<label for="tabs-vertical-1">アカウント</label>
<input type="radio" name="tabs-vertical" id="tabs-vertical-2">
<label for="tabs-vertical-2">通知</label>
<input type="radio" name="tabs-vertical" id="tabs-vertical-3">
<label for="tabs-vertical-3">セキュリティ</label>
<input type="radio" name="tabs-vertical" id="tabs-vertical-4">
<label for="tabs-vertical-4">お支払い</label>
</div>
<div class="tabs-vertical-body">
<div class="tabs-vertical-panel">ユーザー名やメールアドレスを変更できます。</div>
<div class="tabs-vertical-panel">メール通知の受け取り方法を設定できます。</div>
<div class="tabs-vertical-panel">パスワードや2段階認証を設定できます。</div>
<div class="tabs-vertical-panel">お支払い方法と請求履歴を確認できます。</div>
</div>
</div>.tabs-vertical {
display: grid;
grid-template-columns: 180px 1fr;
border: 1px solid #ddd;
border-radius: 8px;
overflow: hidden;
}
.tabs-vertical-nav {
position: relative;
display: flex;
flex-direction: column;
background: #f7f8fa;
border-right: 1px solid #ddd;
}
.tabs-vertical-nav input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.tabs-vertical-nav label {
padding: 14px 20px;
border-left: 3px solid transparent;
cursor: pointer;
}
.tabs-vertical-nav input:checked + label {
color: #3498db;
font-weight: bold;
background: #fff;
border-left-color: #3498db;
}
.tabs-vertical-nav input:focus-visible + label {
outline: 2px solid #3498db;
outline-offset: -2px;
}
.tabs-vertical-panel {
display: none;
padding: 24px;
}
.tabs-vertical:has(#tabs-vertical-1:checked) .tabs-vertical-panel:nth-child(1),
.tabs-vertical:has(#tabs-vertical-2:checked) .tabs-vertical-panel:nth-child(2),
.tabs-vertical:has(#tabs-vertical-3:checked) .tabs-vertical-panel:nth-child(3),
.tabs-vertical:has(#tabs-vertical-4:checked) .tabs-vertical-panel:nth-child(4) {
display: block;
}
@media (max-width: 600px) {
.tabs-vertical {
grid-template-columns: 1fr;
}
.tabs-vertical-nav {
flex-direction: row;
overflow-x: auto;
scrollbar-width: none;
border-right: none;
border-bottom: 1px solid #ddd;
}
.tabs-vertical-nav::-webkit-scrollbar {
display: none;
}
.tabs-vertical-nav label {
flex-shrink: 0;
border-left: none;
border-bottom: 3px solid transparent;
}
.tabs-vertical-nav input:checked + label {
border-bottom-color: #3498db;
}
}タブを左側に縦に並べたデザインです。設定画面やマイページなど、項目が多いメニューに向いています。画面幅が600px以下になると、タブが上に横並びになり、収まらない場合は横にスクロールできます。
⑨ アイコン付きタブ
<div class="tabs-icon">
<input type="radio" name="tabs-icon" id="tabs-icon-1" checked>
<label for="tabs-icon-1">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3 11l9-8 9 8v10h-6v-6h-6v6H3z"/></svg>
<span>ホーム</span>
</label>
<input type="radio" name="tabs-icon" id="tabs-icon-2">
<label for="tabs-icon-2">
<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="8" r="4"/><path d="M4 21c0-4 4-6 8-6s8 2 8 6"/></svg>
<span>マイページ</span>
</label>
<input type="radio" name="tabs-icon" id="tabs-icon-3">
<label for="tabs-icon-3">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 16v-5a6 6 0 0 1 12 0v5l2 2H4z"/><path d="M10 21h4"/></svg>
<span>通知</span>
</label>
<div class="tabs-icon-panel">ホーム画面の内容を表示します。</div>
<div class="tabs-icon-panel">登録情報や履歴を表示します。</div>
<div class="tabs-icon-panel">新しいお知らせを表示します。</div>
</div>.tabs-icon {
position: relative;
display: flex;
flex-wrap: wrap;
}
.tabs-icon input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.tabs-icon label {
display: flex;
flex: 1;
justify-content: center;
align-items: center;
gap: 8px;
padding: 12px;
color: #888;
border-bottom: 2px solid #ddd;
cursor: pointer;
transition: color 0.3s, border-color 0.3s;
}
.tabs-icon svg {
width: 20px;
height: 20px;
fill: none;
stroke: currentColor;
stroke-width: 2;
stroke-linejoin: round;
}
.tabs-icon input:checked + label {
color: #3498db;
border-bottom-color: #3498db;
}
.tabs-icon input:focus-visible + label {
outline: 2px solid #3498db;
outline-offset: -2px;
}
.tabs-icon-panel {
display: none;
width: 100%;
padding: 20px;
}
#tabs-icon-1:checked ~ .tabs-icon-panel:nth-of-type(1),
#tabs-icon-2:checked ~ .tabs-icon-panel:nth-of-type(2),
#tabs-icon-3:checked ~ .tabs-icon-panel:nth-of-type(3) {
display: block;
}
@media (max-width: 600px) {
.tabs-icon label {
flex-direction: column;
gap: 4px;
font-size: 12px;
}
}アイコンとテキストを組み合わせたタブです。アイコンはSVGで作り、stroke: currentColor を指定することで文字色と一緒に色が変わります。画面幅が600px以下になると、アイコンの下にテキストを配置するアプリ風のレイアウトに切り替わります。
アイコンは装飾なので aria-hidden="true" を付け、読み上げではテキストだけが伝わるようにしています。
⑩ 横スクロールできるタブ
<div class="tabs-scroll">
<div class="tabs-scroll-nav">
<input type="radio" name="tabs-scroll" id="tabs-scroll-1" checked>
<label for="tabs-scroll-1">すべて</label>
<input type="radio" name="tabs-scroll" id="tabs-scroll-2">
<label for="tabs-scroll-2">HTML</label>
<input type="radio" name="tabs-scroll" id="tabs-scroll-3">
<label for="tabs-scroll-3">CSS</label>
<input type="radio" name="tabs-scroll" id="tabs-scroll-4">
<label for="tabs-scroll-4">JavaScript</label>
<input type="radio" name="tabs-scroll" id="tabs-scroll-5">
<label for="tabs-scroll-5">PHP</label>
<input type="radio" name="tabs-scroll" id="tabs-scroll-6">
<label for="tabs-scroll-6">WordPress</label>
<input type="radio" name="tabs-scroll" id="tabs-scroll-7">
<label for="tabs-scroll-7">デザイン</label>
<input type="radio" name="tabs-scroll" id="tabs-scroll-8">
<label for="tabs-scroll-8">ツール</label>
</div>
<div class="tabs-scroll-body">
<div class="tabs-scroll-panel">すべてのカテゴリの記事を表示します。</div>
<div class="tabs-scroll-panel">HTMLの記事を表示します。</div>
<div class="tabs-scroll-panel">CSSの記事を表示します。</div>
<div class="tabs-scroll-panel">JavaScriptの記事を表示します。</div>
<div class="tabs-scroll-panel">PHPの記事を表示します。</div>
<div class="tabs-scroll-panel">WordPressの記事を表示します。</div>
<div class="tabs-scroll-panel">デザインの記事を表示します。</div>
<div class="tabs-scroll-panel">ツールの記事を表示します。</div>
</div>
</div>.tabs-scroll-nav {
position: relative;
display: flex;
gap: 8px;
padding-bottom: 4px;
overflow-x: auto;
scroll-snap-type: x mandatory;
scrollbar-width: none;
mask-image: linear-gradient(to right, #000 85%, transparent);
}
.tabs-scroll-nav::-webkit-scrollbar {
display: none;
}
.tabs-scroll-nav input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.tabs-scroll-nav label {
flex-shrink: 0;
padding: 8px 16px;
white-space: nowrap;
border: 1px solid #ddd;
border-radius: 999px;
cursor: pointer;
scroll-snap-align: start;
}
.tabs-scroll-nav input:checked + label {
color: #fff;
background: #3498db;
border-color: #3498db;
}
.tabs-scroll-nav input:focus-visible + label {
outline: 2px solid #3498db;
outline-offset: -2px;
}
.tabs-scroll-panel {
display: none;
padding: 20px 0;
}
.tabs-scroll:has(#tabs-scroll-1:checked) .tabs-scroll-panel:nth-child(1),
.tabs-scroll:has(#tabs-scroll-2:checked) .tabs-scroll-panel:nth-child(2),
.tabs-scroll:has(#tabs-scroll-3:checked) .tabs-scroll-panel:nth-child(3),
.tabs-scroll:has(#tabs-scroll-4:checked) .tabs-scroll-panel:nth-child(4),
.tabs-scroll:has(#tabs-scroll-5:checked) .tabs-scroll-panel:nth-child(5),
.tabs-scroll:has(#tabs-scroll-6:checked) .tabs-scroll-panel:nth-child(6),
.tabs-scroll:has(#tabs-scroll-7:checked) .tabs-scroll-panel:nth-child(7),
.tabs-scroll:has(#tabs-scroll-8:checked) .tabs-scroll-panel:nth-child(8) {
display: block;
}タブの数が多いときに、横にスクロールして選べるタブです。スマホのカテゴリ切り替えなどでよく見かけます。
scroll-snap-typeとscroll-snap-alignで、スクロールを止めたときにタブの端で揃えるscrollbar-width: noneと::-webkit-scrollbarでスクロールバーを隠すmask-imageで右端をフェードさせ、続きがあることを示す
⑪ アクセシビリティ対応のJavaScriptタブ
<div class="tabs-aria">
<div class="tabs-aria-list" role="tablist" aria-label="料金プラン">
<button type="button" role="tab" id="tabs-aria-tab-1" aria-controls="tabs-aria-panel-1" aria-selected="true">ライト</button>
<button type="button" role="tab" id="tabs-aria-tab-2" aria-controls="tabs-aria-panel-2" aria-selected="false" tabindex="-1">スタンダード</button>
<button type="button" role="tab" id="tabs-aria-tab-3" aria-controls="tabs-aria-panel-3" aria-selected="false" tabindex="-1">プレミアム</button>
</div>
<div class="tabs-aria-panel" role="tabpanel" id="tabs-aria-panel-1" aria-labelledby="tabs-aria-tab-1" tabindex="0">ライトプラン:個人の学習向け。月額500円です。</div>
<div class="tabs-aria-panel" role="tabpanel" id="tabs-aria-panel-2" aria-labelledby="tabs-aria-tab-2" tabindex="0" hidden>スタンダードプラン:小規模チーム向け。月額1,500円です。</div>
<div class="tabs-aria-panel" role="tabpanel" id="tabs-aria-panel-3" aria-labelledby="tabs-aria-tab-3" tabindex="0" hidden>プレミアムプラン:企業向け。月額3,000円です。</div>
</div>.tabs-aria-list {
display: flex;
border-bottom: 1px solid #ddd;
}
.tabs-aria-list button {
padding: 12px 20px;
font: inherit;
color: #666;
background: none;
border: none;
border-bottom: 3px solid transparent;
cursor: pointer;
}
.tabs-aria-list button[aria-selected="true"] {
color: #3498db;
font-weight: bold;
border-bottom-color: #3498db;
}
.tabs-aria-list button:focus-visible {
outline: 2px solid #3498db;
outline-offset: -2px;
}
.tabs-aria-panel {
padding: 20px 0;
}
.tabs-aria-panel:focus-visible {
outline: 2px solid #3498db;
}document.addEventListener('DOMContentLoaded', function () {
document.querySelectorAll('.tabs-aria').forEach(function (tabs) {
const tabList = Array.from(tabs.querySelectorAll('[role="tab"]'));
function selectTab(tab) {
tabList.forEach(function (item) {
const selected = item === tab;
item.setAttribute('aria-selected', selected);
item.tabIndex = selected ? 0 : -1;
document.getElementById(item.getAttribute('aria-controls')).hidden = !selected;
});
tab.focus();
}
tabList.forEach(function (tab, index) {
tab.addEventListener('click', function () {
selectTab(tab);
});
tab.addEventListener('keydown', function (e) {
let next = null;
if (e.key === 'ArrowRight') next = tabList[(index + 1) % tabList.length];
if (e.key === 'ArrowLeft') next = tabList[(index - 1 + tabList.length) % tabList.length];
if (e.key === 'Home') next = tabList[0];
if (e.key === 'End') next = tabList[tabList.length - 1];
if (next) {
e.preventDefault();
selectTab(next);
}
});
});
});
});WAI-ARIAのタブパターンに沿って、JavaScriptで切り替えるタブです。タブに role="tab"、パネルに role="tabpanel" を指定し、選択状態を aria-selected で伝えます。スクリーンリーダーでも「タブ」として読み上げられ、選択中かどうかが伝わります。
- 左右の矢印キーで前後のタブへ移動し、Home・Endキーで最初・最後のタブへ移動する
- 選ばれていないタブは
tabindex="-1"にし、Tabキーではタブ列からパネルへ直接移動できるようにする - パネルの表示・非表示は
hidden属性で切り替える
パネルのCSSで display を指定すると hidden 属性より優先されて非表示にならないため、パネルには display を指定していません。
スマホ対応のポイント
- タブの数が多い場合は、折り返すより横スクロールにしたほうが一覧しやすい(⑩)
- 縦タブは画面幅に応じて横並びに切り替える(⑧)
- タップしやすいよう、タブの高さは44px程度を目安に余白を確保する
- 文字が長いタブは、アイコンを併用したり文言を短くしたりして1行に収める(⑨)
アクセシビリティ対応
- ラジオボタンは
display: noneにせず、キーボードで操作できる状態を保つ :focus-visibleで、キーボード操作中のタブにフォーカスの枠を表示する- 選択中のタブは色だけでなく、太字や下線など色以外の変化でも区別できるようにする
- CSS版のタブは、スクリーンリーダーでは「ラジオボタン」として読み上げられる。重要な画面では⑪のようにWAI-ARIAに沿ったタブを使う
まとめ
- ラジオボタンと
:checkedを使えば、JavaScriptなしでタブを作れる :has()を使うと、HTMLの構造を自由にしたまま、下線の移動や色の切り替えができる- 縦タブや横スクロールタブで、スマホでも使いやすくできる
- 正しく読み上げる必要がある画面では、WAI-ARIAに沿ったJavaScript版を使う
用途やデザインに合わせてタブUIを選び、情報を分かりやすく整理しましょう。










