CSSだけで作れる“ちょっと楽しい”タブUIの実装と応用
【初心者向けまとめ】
限られた画面スペースで情報をまとめることのできるタブUI、
実は JavaScript を使わずに実装できます。
ラジオボタンと :checked 、疑似クラスを使うことでクリック処理を CSS だけで実現し、
JavaScript なしで動作するタブUIの作り方と、知ると“ちょっと楽しい”
応用テクニックを紹介します。
HTML の仕組み
<div class="tabs">
<input type="radio" name="tab" id="tab1" checked>
<input type="radio" name="tab" id="tab2">
<input type="radio" name="tab" id="tab3">
<div class="tab-labels">
<label for="tab1">タブ1</label>
<label for="tab2">タブ2</label>
<label for="tab3">タブ3</label>
</div>
<div class="tab-content">
<div id="content1" class="content">
<p>タブ1の内容です。</p>
</div>
<div id="content2" class="content">
<p>タブ2の内容です。</p>
</div>
<div id="content3" class="content">
<p>タブ3の内容です。</p>
</div>
</div>
</div>
クリックすると対応するinputをチェック状態にします。labelをクリック=ラジオボタンを
押すというHTMLのネイティブ機能を利用します。
このままでは各要素がバラバラなので、CSS で整えます。
CSS の実装
.tabs input[type="radio"] {
display: none;
}
.tab-labels {
display: flex;
gap: 0;
border-bottom: 3px solid #e0e0e0;
}
.tab-labels label {
padding: 15px 30px;
cursor: pointer;
background-color: transparent;
border: none;
border-bottom: 3px solid transparent;
transition: all 0.3s ease;
user-select: none;
font-weight: 500;
color: #666;
}
.tab-labels label:hover {
color: #333;
}
#tab1:checked ~ .tab-labels label[for="tab1"],
#tab2:checked ~ .tab-labels label[for="tab2"],
#tab3:checked ~ .tab-labels label[for="tab3"] {
color: #3498db;
border-bottom-color: #3498db;
}
.content {
display: none;
padding: 30px;
background-color: #fafafa;
border-radius: 0 0 4px 4px;
min-height: 150px;
}
#tab1:checked ~ .tab-content #content1,
#tab2:checked ~ .tab-content #content2,
#tab3:checked ~ .tab-content #content3 {
display: block;
animation: slideIn 0.4s ease;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
:checked + 兄弟セレクタ(~)の組み合わせを使います。
input → tab-labels → tab-content の順番が重要です、
input が先にあるのは、CSS の兄弟セレクタ(~)が “後ろにある要素”
にしか作用しないため、ラジオボタンより後にラベルやコンテンツを置く必要があります。
inputが先頭にあって初めて兄弟として後続要素を操作できます。
動作の仕組み
- ユーザーがラベルをクリック
- ラベルの
for属性が、対応するラジオボタンのidと一致 - ラジオボタンが自動的にチェック状態になる
- CSS の
:checked疑似クラスが反応 - 対応するタブコンテンツが表示される
タブUIの様々な応用
1.アイコン付き + 縦型タブ
ちょっと変わった「縦に並ぶタブ」で、ダッシュボードや設定画面に便利です。
<div class="tabs vertical">
<input type="radio" name="vtab" id="vtab1" checked>
<input type="radio" name="vtab" id="vtab2">
<input type="radio" name="vtab" id="vtab3">
<div class="tab-labels">
<label for="vtab1"><span class="icon">🏠</span>ホーム</label>
<label for="vtab2"><span class="icon">⚙️</span>設定</label>
<label for="vtab3"><span class="icon">📊</span>統計</label>
</div>
<div class="tab-content">
<div id="vcontent1" class="content">ホームの内容...</div>
<div id="vcontent2" class="content">設定の内容...</div>
<div id="vcontent3" class="content">統計の内容...</div>
</div>
</div>
.tabs.vertical {
display: flex;
gap: 30px;
}
.tabs.vertical .tab-labels {
flex-direction: column;
border-bottom: none;
border-right: 2px solid #eee;
width: 180px;
}
.tabs.vertical .tab-labels label {
margin: 0;
margin-bottom: 8px;
border-radius: 8px 0 0 8px;
border: 1px solid #ddd;
border-right: none;
text-align: right;
padding: 16px 20px;
background: #f9f9f9;
transition: all 0.25s;
}
.tabs.vertical .tab-labels label:hover {
background: #f0f8ff;
}
.tabs.vertical .tab-labels label .icon {
margin-right: 12px;
font-size: 1.3em;
}
#vtab1:checked ~ .tab-labels label[for="vtab1"],
#vtab2:checked ~ .tab-labels label[for="vtab2"],
#vtab3:checked ~ .tab-labels label[for="vtab3"] {
background: #3498db;
color: white;
border-color: #3498db;
border-right-color: transparent;
position: relative;
}
.tabs.vertical .tab-content {
flex: 1;
}
.tabs.vertical .content {
display: none;
padding: 24px;
border: 1px solid #ddd;
border-radius: 8px;
background: white;
}
#vtab1:checked ~ .tab-content #vcontent1,
#vtab2:checked ~ .tab-content #vcontent2,
#vtab3:checked ~ .tab-content #vcontent3 {
display: block;
animation: fadeInLeft 0.4s ease;
}
@keyframes fadeInLeft {
from { opacity: 0; transform: translateX(-15px); }
to { opacity: 1; transform: translateX(0); }
}
※ CSSの実装 に追加して使用してください。
2.カード風タブ + ホバーでプレビュー表示
タブをクリックする前から中身をチラ見せできる、遊び心のあるデザイン。
<div class="tabs card-style">
<input type="radio" name="ctab" id="ctab1" checked>
<input type="radio" name="ctab" id="ctab2">
<input type="radio" name="ctab" id="ctab3">
<div class="tab-labels">
<label for="ctab1" class="tab-card">
<div class="preview">カード1のプレビュー...</div>
<span>カード1</span>
</label>
<label for="ctab2" class="tab-card">
<div class="preview">カード2のプレビュー...</div>
<span>カード2</span>
</label>
<label for="ctab3" class="tab-card">
<div class="preview">カード3のプレビュー...</div>
<span>カード3</span>
</label>
</div>
<div class="tab-content">
<div id="ccontent1" class="content">本編カード1...</div>
<div id="ccontent2" class="content">本編カード2...</div>
<div id="ccontent3" class="content">本編カード3...</div>
</div>
</div>
.card-style .tab-labels {
display: flex;
gap: 20px;
flex-wrap: wrap;
}
.card-style .tab-card {
width: 220px;
height: 200px;
background: #fff;
border: 2px solid #eee;
border-radius: 12px;
overflow: hidden;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
text-align: center;
padding-top: 160px;
box-shadow: 0 4px 10px rgba(0,0,0,0.08);
}
.card-style .tab-card:hover {
transform: translateY(-8px);
box-shadow: 0 12px 24px rgba(0,0,0,0.12);
}
.card-style .tab-card:hover .preview {
opacity: 1;
}
.card-style .preview {
opacity: 0;
transition: opacity 0.3s;
position: absolute;
inset: 0;
background: linear-gradient(135deg, #84fab0 0%, #8fd3f4 100%);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.1em;
padding: 20px;
text-align: center;
}
.card-style .tab-card span {
font-weight: bold;
color: #444;
}
#ctab1:checked ~ .tab-labels label[for="ctab1"],
#ctab2:checked ~ .tab-labels label[for="ctab2"],
#ctab3:checked ~ .tab-labels label[for="ctab3"] {
border-color: #3498db;
box-shadow: 0 0 0 4px rgba(52,152,219,0.2);
}
.card-style .content {
display: none;
margin-top: 30px;
padding: 30px;
background: white;
border-radius: 12px;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
}
#ctab1:checked ~ .tab-content #ccontent1,
#ctab2:checked ~ .tab-content #ccontent2,
#ctab3:checked ~ .tab-content #ccontent3 {
display: block;
animation: popIn 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
@keyframes popIn {
0% { transform: scale(0.92); opacity: 0; }
60% { transform: scale(1.05); }
100% { transform: scale(1); opacity: 1; }
}
カーソルを合わせると変化します。
※ CSSの実装 に追加して使用してください。
注意点
ラジオボタンの関係性を保つため、同じ name 属性を持つ必要があります。
これが外れてしまうと複数タブ同時選択できて壊れます。
<!-- OK:同じ name -->
<input type="radio" name="tab" id="tab1" checked>
<input type="radio" name="tab" id="tab2">
<!-- NG:異なる name -->
<input type="radio" name="tab1" id="tab1" checked>
<input type="radio" name="tab2" id="tab2">
まとめ
CSS だけで作るタブ UI は軽量でシンプル、そして保守性も高いのが魅力です。
ラジオボタンと :checked を組み合わせることで、JavaScript を使わずに
クリックによるタブ切り替えを実現できます。
基本構造を理解すれば、縦型タブやカード型タブなど、
デザインを自由にアレンジすることも可能です。
シンプルなタブ UI を実装したい場合は、ぜひ CSS のみの手法を活用してみてください!

