アコーディオンはクリックでコンテンツが開閉する UI パターンで、FAQ ページやサイドメニューなどで広く使われます。jQuery を使わずVanilla JavaScript だけで、アニメーション付きのアコーディオンを実装する方法を解説します。
・HTML + CSS + Vanilla JS でアコーディオンを作る方法
・CSS transition でスムーズに開閉する方法
・1 つだけ開くパターン / 複数同時に開くパターン
・WAI-ARIA でアクセシビリティを確保する方法
・開閉アイコン(+ / -)の切り替え
・details / summary 要素との比較
・FAQ ページの実装パターン
基本のアコーディオン(HTML + CSS + JS)
HTML
<div class="accordion">
<div class="accordion-item">
<button class="accordion-header">セクション 1</button>
<div class="accordion-body">
<div class="accordion-content">
<p>セクション 1 の内容です。</p>
</div>
</div>
</div>
<div class="accordion-item">
<button class="accordion-header">セクション 2</button>
<div class="accordion-body">
<div class="accordion-content">
<p>セクション 2 の内容です。</p>
</div>
</div>
</div>
<div class="accordion-item">
<button class="accordion-header">セクション 3</button>
<div class="accordion-body">
<div class="accordion-content">
<p>セクション 3 の内容です。</p>
</div>
</div>
</div>
</div>
div や span ではなく
<button> を使うことで、キーボード操作(Enter / Space で開閉)が自動的に対応され、スクリーンリーダーにも「ボタン」として認識されます。アクセシビリティの基本です。CSS
.accordion-header {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
padding: 16px 20px;
border: none;
border-bottom: 1px solid #e2e8f0;
background: #f8fafc;
font-size: 16px;
font-weight: 600;
color: #1e293b;
cursor: pointer;
text-align: left;
}
.accordion-header:hover {
background: #f1f5f9;
}
/* 開閉アニメーション: max-height + overflow で制御 */
.accordion-body {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease;
}
.accordion-content {
padding: 16px 20px;
}
/* 開閉アイコン */
.accordion-header::after {
content: "+";
font-size: 20px;
font-weight: 400;
transition: transform 0.3s ease;
}
.accordion-item.is-open .accordion-header::after {
content: "\2212"; /* minus sign */
}
height: auto は CSS transition でアニメーションできません。max-height を 0 から scrollHeight(実際の高さ)に変化させることで、スムーズな開閉を実現します。JavaScript
document.querySelectorAll(".accordion-header").forEach(header => {
header.addEventListener("click", () => {
const item = header.parentElement;
const body = header.nextElementSibling;
// 開閉を切り替え
if (item.classList.contains("is-open")) {
// 閉じる
body.style.maxHeight = null;
item.classList.remove("is-open");
} else {
// 開く
body.style.maxHeight = body.scrollHeight + "px";
item.classList.add("is-open");
}
});
});
1 つだけ開くパターン(他を自動で閉じる)
document.querySelectorAll(".accordion-header").forEach(header => {
header.addEventListener("click", () => {
const item = header.parentElement;
const body = header.nextElementSibling;
const isOpen = item.classList.contains("is-open");
// 全アイテムを閉じる
document.querySelectorAll(".accordion-item.is-open").forEach(openItem => {
openItem.classList.remove("is-open");
openItem.querySelector(".accordion-body").style.maxHeight = null;
});
// クリックしたアイテムが閉じていた場合のみ開く
if (!isOpen) {
body.style.maxHeight = body.scrollHeight + "px";
item.classList.add("is-open");
}
});
});
| パターン | 動作 | 用途 |
|---|---|---|
| 複数同時に開ける | クリックしたアイテムだけ開閉。他は影響なし | FAQ ページ(複数の質問を同時に見たい場合) |
| 1 つだけ開く | クリックしたアイテムを開き、他は自動で閉じる | サイドメニュー / ナビゲーション |
WAI-ARIA でアクセシビリティを確保する
<div class="accordion-item">
<button class="accordion-header"
aria-expanded="false"
aria-controls="section1-body"
id="section1-header">
セクション 1
</button>
<div class="accordion-body"
id="section1-body"
role="region"
aria-labelledby="section1-header">
<div class="accordion-content">
<p>セクション 1 の内容です。</p>
</div>
</div>
</div>
header.addEventListener("click", () => {
const item = header.parentElement;
const body = header.nextElementSibling;
const isOpen = item.classList.contains("is-open");
// ARIA 属性を更新
header.setAttribute("aria-expanded", !isOpen);
if (isOpen) {
body.style.maxHeight = null;
item.classList.remove("is-open");
} else {
body.style.maxHeight = body.scrollHeight + "px";
item.classList.add("is-open");
}
});
| ARIA 属性 | 対象 | 値 |
|---|---|---|
| aria-expanded | button(ヘッダー) | true / false(開閉状態) |
| aria-controls | button(ヘッダー) | 制御する body 要素の id |
| role=”region” | body(コンテンツ) | 意味のある領域であることを示す |
| aria-labelledby | body(コンテンツ) | 対応するヘッダーの id |
スクリーンリーダーは
aria-expanded を読み上げて「展開済み」「折りたたみ」を伝えます。開閉時に setAttribute("aria-expanded", true/false) を更新してください。完成版コード(コピペ可能)
(() => {
const headers = document.querySelectorAll(".accordion-header");
if (!headers.length) return;
headers.forEach(header => {
header.addEventListener("click", () => {
const item = header.parentElement;
const body = header.nextElementSibling;
const isOpen = item.classList.contains("is-open");
// 他のアイテムを閉じる(排他的にする場合のみ)
document.querySelectorAll(".accordion-item.is-open").forEach(openItem => {
if (openItem !== item) {
openItem.classList.remove("is-open");
openItem.querySelector(".accordion-body").style.maxHeight = null;
openItem.querySelector(".accordion-header").setAttribute("aria-expanded", "false");
}
});
// クリックしたアイテムを切り替え
if (isOpen) {
body.style.maxHeight = null;
item.classList.remove("is-open");
header.setAttribute("aria-expanded", "false");
} else {
body.style.maxHeight = body.scrollHeight + "px";
item.classList.add("is-open");
header.setAttribute("aria-expanded", "true");
}
});
});
})();
「複数同時に開ける」にしたい場合は、「他のアイテムを閉じる」のブロックを削除してください。
details / summary 要素との比較
HTML 標準の <details> / <summary> 要素を使えば JavaScript なしでアコーディオンが作れます。
<!-- JavaScript 不要で開閉できる --> <details> <summary>セクション 1</summary> <p>セクション 1 の内容です。</p> </details> <details> <summary>セクション 2</summary> <p>セクション 2 の内容です。</p> </details>
| 項目 | details / summary | JS アコーディオン |
|---|---|---|
| JavaScript | 不要 | 必要 |
| アニメーション | 標準ではなし(CSS で可能だが制約あり) | 自由にカスタマイズ可能 |
| 排他的開閉(1 つだけ開く) | name 属性(Chrome 120+)で対応 | JS で制御 |
| アクセシビリティ | 標準で対応 | ARIA 属性の手動設定が必要 |
| デザインの自由度 | 低い(ブラウザデフォルトの三角アイコン) | 高い(完全カスタマイズ) |
アニメーション不要でデフォルトのデザインで十分なら、
details / summary が最もシンプルです。カスタムデザイン、アニメーション、排他的開閉が必要な場合は JavaScript 実装を使ってください。details / summary と jQuery での実装については「アコーディオン実装方法の完全比較」、CSS のみの実装は「CSS のみでアコーディオンを作る方法」を参照してください。
バリエーション
初期状態で特定のセクションを開いておく
// ページ読み込み時に最初のアイテムを開く
const firstItem = document.querySelector(".accordion-item");
if (firstItem) {
firstItem.classList.add("is-open");
firstItem.querySelector(".accordion-body").style.maxHeight =
firstItem.querySelector(".accordion-body").scrollHeight + "px";
firstItem.querySelector(".accordion-header").setAttribute("aria-expanded", "true");
}
矢印アイコンの回転アニメーション
.accordion-header::after {
content: "\25BC"; /* ▼ */
font-size: 12px;
transition: transform 0.3s ease;
}
.accordion-item.is-open .accordion-header::after {
transform: rotate(180deg); /* ▲ に回転 */
}
ハッシュ URL で特定のセクションを開く
// URL の #section2 でそのセクションを開く
if (location.hash) {
const target = document.querySelector(location.hash);
if (target) {
const item = target.closest(".accordion-item");
if (item) {
item.classList.add("is-open");
item.querySelector(".accordion-body").style.maxHeight =
item.querySelector(".accordion-body").scrollHeight + "px";
target.scrollIntoView({ behavior: "smooth" });
}
}
}
アニメーション無効化対応
@media (prefers-reduced-motion: reduce) {
.accordion-body {
transition: none;
}
.accordion-header::after {
transition: none;
}
}
実務パターン: FAQ ページの実装
<div class="faq-section">
<h2>よくある質問</h2>
<div class="accordion">
<div class="accordion-item">
<button class="accordion-header" aria-expanded="false">送料はいくらですか?</button>
<div class="accordion-body">
<div class="accordion-content">
<p>全国一律 550 円です。5,000 円以上のご注文で送料無料になります。</p>
</div>
</div>
</div>
<div class="accordion-item">
<button class="accordion-header" aria-expanded="false">返品はできますか?</button>
<div class="accordion-body">
<div class="accordion-content">
<p>商品到着後 7 日以内であれば返品可能です。</p>
</div>
</div>
</div>
</div>
</div>
よくある質問
auto は具体的な数値ではないため transition が効きません。代わりに max-height を 0 → scrollHeight(実際の高さ)に変化させることでアニメーションを実現します。details / summary で十分です。ただしスムーズなアニメーション、排他的開閉(古いブラウザ対応)、カスタムデザインが必要なら JavaScript 実装が適しています。scrollHeight は要素が非表示(display: none)の場合 0 を返します。本記事の実装は max-height: 0 + overflow: hidden で非表示にしているため、scrollHeight は正しく取得されます。display: none は使わないでください。scrollHeight が変わるためです。画像に width と height 属性を指定して事前にスペースを確保するか、ResizeObserver で高さの変化を検知して maxHeight を再設定してください。scrollHeight が変わるため、外側の maxHeight も再計算する必要があります。transitionend イベントで親のアコーディオンの maxHeight を更新するか、max-height: 9999px のような大きな値で逃げる方法があります。まとめ
JavaScript アコーディオンの実装ポイントをまとめます。
| ポイント | 内容 |
|---|---|
| 開閉の仕組み | max-height: 0 ⇔ scrollHeight + overflow: hidden |
| アニメーション | CSS transition: max-height 0.3s ease |
| ヘッダー要素 | button 要素を使う(キーボード・スクリーンリーダー対応) |
| ARIA | aria-expanded=”true/false” を開閉時に更新 |
| 1 つだけ開くパターン | 他の is-open を全て解除してから開く |
| アイコン | CSS ::after + content / transform: rotate で回転 |
| アニメーション無効化 | @media (prefers-reduced-motion: reduce) |
| JS 不要の代替 | details / summary 要素(アニメーション不要な場合) |
jQuery での実装は「jQuery アコーディオンの実装完全ガイド」、CSS のみの実装は「CSS のみでアコーディオンを作る方法」、実装方法の比較は「アコーディオン実装方法の完全比較」も併せて参照してください。

