タブメニューの作り方|クリック・ホバー・レスポンシブ対応
タブを切り替えて、限られたスペースに複数の内容を整理する方法を解説します。各セクションのHTML・CSS・JavaScriptは独立しており、3つをそのままコピーすればデモと同じ見た目と動作を再現できます。
HTML・CSS・JSコードプレビューツールでも動作を確認できます。
このページで作るもの
① クリック切り替え — 選んだ内容だけを表示する
必要な情報を3つのタブに整理し、選んだ内容だけを表示します。
HTML・CSS・JavaScriptだけで動き、外部ライブラリは不要です。
文章だけでなく、画像やボタンなどもタブ内に配置できます。
HTML
<div class="tm-click">
<div class="tm-click-tabs" role="tablist" aria-label="商品情報">
<button class="tm-click-tab is-on" type="button" data-pane="feature" aria-current="true">特徴</button>
<button class="tm-click-tab" type="button" data-pane="spec" aria-current="false">仕様</button>
<button class="tm-click-tab" type="button" data-pane="support" aria-current="false">サポート</button>
</div>
<div class="tm-click-panes">
<section class="tm-click-pane is-on" data-pane-area="feature" role="tabpanel">
<strong>使いやすさを重視した設計</strong>
<p>必要な情報を3つのタブに整理し、選んだ内容だけを表示します。</p>
</section>
<section class="tm-click-pane" data-pane-area="spec" role="tabpanel">
<strong>シンプルな構成</strong>
<p>HTML・CSS・JavaScriptだけで動き、外部ライブラリは不要です。</p>
</section>
<section class="tm-click-pane" data-pane-area="support" role="tabpanel">
<strong>内容を自由に変更</strong>
<p>文章だけでなく、画像やボタンなどもタブ内に配置できます。</p>
</section>
</div>
</div>
CSS
.tm-click {
width: 100%;
max-width: 680px;
margin: 0 auto;
color: #172033;
font-family: Arial, sans-serif;
}
.tm-click-tabs {
display: flex;
gap: 6px;
padding: 6px;
border: 1px solid #d8dee9;
border-radius: 12px 12px 0 0;
background: #eef2f7;
box-sizing: border-box;
}
.tm-click-tab {
flex: 1;
min-width: 0;
padding: 11px 14px;
border: 1px solid transparent;
border-radius: 8px;
background: transparent;
color: #526077;
font-size: 14px;
font-weight: 700;
cursor: pointer;
}
.tm-click-tab:hover {
color: #172033;
background: #f8fafc;
}
.tm-click-tab.is-on {
border-color: #c4b5fd;
background: #fff;
color: #6d28d9;
box-shadow: 0 3px 10px rgba(15, 23, 42, 0.08);
}
.tm-click-tab:focus-visible {
outline: 3px solid rgba(124, 58, 237, 0.3);
outline-offset: 2px;
}
.tm-click-panes {
min-height: 150px;
border: 1px solid #d8dee9;
border-top: 0;
border-radius: 0 0 12px 12px;
background: #fff;
box-shadow: 0 8px 22px rgba(15, 23, 42, 0.08);
}
.tm-click-pane {
display: none;
padding: 28px;
}
.tm-click-pane.is-on {
display: block;
}
.tm-click-pane strong {
display: block;
margin-bottom: 8px;
color: #172033;
font-size: 16px;
}
.tm-click-pane p {
margin: 0;
color: #64748b;
font-size: 14px;
line-height: 1.8;
}
JS
(function() {
'use strict';
var root = document.querySelector('.tm-click');
if (!root) return;
var tabs = root.querySelectorAll('.tm-click-tab');
var panes = root.querySelectorAll('.tm-click-pane');
function showPane(key) {
for (var tabNum = 0; tabNum < tabs.length; tabNum++) {
var tabOn = tabs[tabNum].getAttribute('data-pane') === key;
tabs[tabNum].className = tabOn ? 'tm-click-tab is-on' : 'tm-click-tab';
tabs[tabNum].setAttribute('aria-current', tabOn ? 'true' : 'false');
}
for (var paneNum = 0; paneNum < panes.length; paneNum++) {
var paneOn = panes[paneNum].getAttribute('data-pane-area') === key;
panes[paneNum].className = paneOn ? 'tm-click-pane is-on' : 'tm-click-pane';
}
}
for (var tabNum = 0; tabNum < tabs.length; tabNum++) {
tabs[tabNum].addEventListener('click', function() {
showPane(this.getAttribute('data-pane'));
});
}
})();
data属性を対応キーとして使う
ボタンの data-pane と表示領域の data-pane-area を同じ値にします。クリック時に全要素の状態を更新するため、常に1つのタブと1つの内容だけが選択状態になります。
② ホバー切り替え — タッチとキーボード操作も残す
料金や機能の違いを、ひとつの場所で比較できます。
必要な手順を短く整理し、迷わず進めるようにします。
事前に確認したい質問と回答をまとめて表示します。
HTML
<div class="tm-hover">
<div class="tm-hover-tabs" role="tablist" aria-label="サービス案内">
<button class="tm-hover-tab is-on" type="button" data-pane="plan" aria-current="true">プラン</button>
<button class="tm-hover-tab" type="button" data-pane="flow" aria-current="false">利用の流れ</button>
<button class="tm-hover-tab" type="button" data-pane="faq" aria-current="false">よくある質問</button>
</div>
<div class="tm-hover-panes">
<section class="tm-hover-pane is-on" data-pane-area="plan" role="tabpanel">
<span class="tm-hover-tag">PLAN</span>
<strong>目的に合わせて選べる3つのプラン</strong>
<p>料金や機能の違いを、ひとつの場所で比較できます。</p>
</section>
<section class="tm-hover-pane" data-pane-area="flow" role="tabpanel">
<span class="tm-hover-tag">FLOW</span>
<strong>申し込みから利用開始までを確認</strong>
<p>必要な手順を短く整理し、迷わず進めるようにします。</p>
</section>
<section class="tm-hover-pane" data-pane-area="faq" role="tabpanel">
<span class="tm-hover-tag">FAQ</span>
<strong>よくある疑問をすぐに解決</strong>
<p>事前に確認したい質問と回答をまとめて表示します。</p>
</section>
</div>
</div>
CSS
.tm-hover {
width: 100%;
max-width: 680px;
margin: 0 auto;
color: #172033;
font-family: Arial, sans-serif;
}
.tm-hover-tabs {
display: flex;
border-bottom: 1px solid #d8dee9;
}
.tm-hover-tab {
position: relative;
flex: 1;
min-width: 0;
padding: 14px 10px;
border: 0;
background: #f8fafc;
color: #64748b;
font-size: 14px;
font-weight: 700;
cursor: pointer;
}
.tm-hover-tab::after {
position: absolute;
right: 50%;
bottom: -1px;
left: 50%;
height: 3px;
background: #7c3aed;
content: "";
transition: right 0.2s, left 0.2s;
}
.tm-hover-tab:hover,
.tm-hover-tab:focus-visible,
.tm-hover-tab.is-on {
background: #fff;
color: #6d28d9;
}
.tm-hover-tab.is-on::after {
right: 18px;
left: 18px;
}
.tm-hover-tab:focus-visible {
z-index: 1;
outline: 3px solid rgba(124, 58, 237, 0.3);
outline-offset: -3px;
}
.tm-hover-panes {
min-height: 170px;
border: 1px solid #d8dee9;
border-top: 0;
border-radius: 0 0 12px 12px;
background: #fff;
box-shadow: 0 8px 22px rgba(15, 23, 42, 0.08);
}
.tm-hover-pane {
display: none;
padding: 30px;
}
.tm-hover-pane.is-on {
display: block;
}
.tm-hover-tag {
display: inline-block;
margin-bottom: 10px;
padding: 3px 9px;
border-radius: 20px;
background: #ede9fe;
color: #6d28d9;
font-size: 11px;
font-weight: 800;
letter-spacing: 0.08em;
}
.tm-hover-pane strong {
display: block;
margin-bottom: 8px;
color: #172033;
font-size: 16px;
}
.tm-hover-pane p {
margin: 0;
color: #64748b;
font-size: 14px;
line-height: 1.8;
}
JS
(function() {
'use strict';
var root = document.querySelector('.tm-hover');
if (!root) return;
var tabs = root.querySelectorAll('.tm-hover-tab');
var panes = root.querySelectorAll('.tm-hover-pane');
function showPane(key) {
for (var tabNum = 0; tabNum < tabs.length; tabNum++) {
var tabOn = tabs[tabNum].getAttribute('data-pane') === key;
tabs[tabNum].className = tabOn ? 'tm-hover-tab is-on' : 'tm-hover-tab';
tabs[tabNum].setAttribute('aria-current', tabOn ? 'true' : 'false');
}
for (var paneNum = 0; paneNum < panes.length; paneNum++) {
var paneOn = panes[paneNum].getAttribute('data-pane-area') === key;
panes[paneNum].className = paneOn ? 'tm-hover-pane is-on' : 'tm-hover-pane';
}
}
function changePane() {
showPane(this.getAttribute('data-pane'));
}
for (var tabNum = 0; tabNum < tabs.length; tabNum++) {
tabs[tabNum].addEventListener('mouseenter', changePane);
tabs[tabNum].addEventListener('focus', changePane);
tabs[tabNum].addEventListener('click', changePane);
}
})();
ホバーだけに限定しない
mouseenter に加えて focus と click でも同じ処理を実行します。マウスがないスマホや、キーボードで移動する場合にも内容を切り替えられます。
③ レスポンシブ対応 — PCの縦型タブをスマホで横並びにする
最初に目的と掲載する内容を整理します。
タブ名と各パネルの文章や画像を差し替えます。
PC幅とスマホ幅の両方で切り替えを確認します。
HTML
<div class="tm-responsive">
<div class="tm-responsive-tabs" role="tablist" aria-label="導入ガイド">
<button class="tm-responsive-tab is-on" type="button" data-pane="prepare" aria-current="true"><span>01</span>準備</button>
<button class="tm-responsive-tab" type="button" data-pane="setup" aria-current="false"><span>02</span>設定</button>
<button class="tm-responsive-tab" type="button" data-pane="start" aria-current="false"><span>03</span>開始</button>
</div>
<div class="tm-responsive-panes">
<section class="tm-responsive-pane is-on" data-pane-area="prepare" role="tabpanel">
<strong>必要な情報を準備する</strong>
<p>最初に目的と掲載する内容を整理します。</p>
</section>
<section class="tm-responsive-pane" data-pane-area="setup" role="tabpanel">
<strong>表示内容を設定する</strong>
<p>タブ名と各パネルの文章や画像を差し替えます。</p>
</section>
<section class="tm-responsive-pane" data-pane-area="start" role="tabpanel">
<strong>動作を確認して公開する</strong>
<p>PC幅とスマホ幅の両方で切り替えを確認します。</p>
</section>
</div>
</div>
CSS
.tm-responsive {
display: grid;
grid-template-columns: 210px minmax(0, 1fr);
width: 100%;
max-width: 680px;
min-height: 230px;
margin: 0 auto;
border: 1px solid #d8dee9;
border-radius: 12px;
overflow: hidden;
background: #fff;
color: #172033;
font-family: Arial, sans-serif;
box-shadow: 0 8px 22px rgba(15, 23, 42, 0.08);
box-sizing: border-box;
}
.tm-responsive-tabs {
display: flex;
flex-direction: column;
gap: 6px;
padding: 14px;
border-right: 1px solid #d8dee9;
background: #eef2f7;
}
.tm-responsive-tab {
display: flex;
align-items: center;
gap: 10px;
width: 100%;
padding: 12px;
border: 1px solid transparent;
border-radius: 8px;
background: transparent;
color: #526077;
font-size: 14px;
font-weight: 700;
text-align: left;
cursor: pointer;
}
.tm-responsive-tab span {
display: grid;
width: 28px;
height: 28px;
place-items: center;
border-radius: 50%;
background: #d8dee9;
color: #526077;
font-size: 11px;
}
.tm-responsive-tab:hover {
background: #f8fafc;
color: #172033;
}
.tm-responsive-tab.is-on {
border-color: #c4b5fd;
background: #fff;
color: #6d28d9;
box-shadow: 0 3px 10px rgba(15, 23, 42, 0.08);
}
.tm-responsive-tab.is-on span {
background: #7c3aed;
color: #fff;
}
.tm-responsive-tab:focus-visible {
outline: 3px solid rgba(124, 58, 237, 0.3);
outline-offset: 2px;
}
.tm-responsive-panes {
min-width: 0;
background: #fff;
}
.tm-responsive-pane {
display: none;
padding: 42px 34px;
}
.tm-responsive-pane.is-on {
display: block;
}
.tm-responsive-pane strong {
display: block;
margin-bottom: 10px;
color: #172033;
font-size: 17px;
}
.tm-responsive-pane p {
margin: 0;
color: #64748b;
font-size: 14px;
line-height: 1.8;
}
@media (max-width: 600px) {
.tm-responsive {
display: block;
min-height: 0;
}
.tm-responsive-tabs {
flex-direction: row;
padding: 8px;
border-right: 0;
border-bottom: 1px solid #d8dee9;
}
.tm-responsive-tab {
flex: 1;
justify-content: center;
gap: 6px;
padding: 10px 6px;
text-align: center;
}
.tm-responsive-tab span {
width: 24px;
height: 24px;
}
.tm-responsive-pane {
padding: 28px 22px;
}
}
JS
(function() {
'use strict';
var root = document.querySelector('.tm-responsive');
if (!root) return;
var tabs = root.querySelectorAll('.tm-responsive-tab');
var panes = root.querySelectorAll('.tm-responsive-pane');
function showPane(key) {
for (var tabNum = 0; tabNum < tabs.length; tabNum++) {
var tabOn = tabs[tabNum].getAttribute('data-pane') === key;
tabs[tabNum].className = tabOn ? 'tm-responsive-tab is-on' : 'tm-responsive-tab';
tabs[tabNum].setAttribute('aria-current', tabOn ? 'true' : 'false');
}
for (var paneNum = 0; paneNum < panes.length; paneNum++) {
var paneOn = panes[paneNum].getAttribute('data-pane-area') === key;
panes[paneNum].className = paneOn ? 'tm-responsive-pane is-on' : 'tm-responsive-pane';
}
}
for (var tabNum = 0; tabNum < tabs.length; tabNum++) {
tabs[tabNum].addEventListener('click', function() {
showPane(this.getAttribute('data-pane'));
});
}
})();
見た目だけをメディアクエリで切り替える
JavaScriptの処理はPCとスマホで共通です。幅600px以下ではCSSだけでタブを横並びに変え、パネルの余白も縮めます。動作とレイアウトを分けると保守しやすくなります。
あわせて読みたい


タブメニューのデザイン33種|コピペで実装できるコード付き
さまざまなデザインのタブメニューサンプルを作りましたので参考に! 実装コード(HTML / CSS / JavaScript)は個別ページで公開していますので、気に入ったデザインが…
当サイトで公開しているWebデザインやUIの実装例は、一覧として以下記事に纏めています。
あわせて読みたい


Webデザイン・UIコンポーネント集(HTML/CSS/JS)
HTML / CSS / JAVASCRIPT · 随時更新中 WebデザインとUI実装例集 Webサイトやアプリで使われるUIコンポーネント・デザインパターンをまとめました。それぞれのカ…
