FAQやメニューをクリックで開閉する「アコーディオン」を作りたいけれど、HTML・CSS・jQueryをどう組み合わせればよいのか分からないことがありますよね。
jQueryでは、質問ボタンのクリックをon('click')で受け取り、回答をslideDown()とslideUp()で開閉すると実装できます。
この記事では、そのまま試せるFAQアコーディオンをHTML、CSS、jQueryの順に作ります。スマートフォン表示やキーボード操作、1つだけ開く方法、動かない原因まで確認しましょう。
今回作るjQueryアコーディオン
最初はすべての回答を閉じ、質問をクリックすると対応する回答だけが開くFAQを作ります。閉じた状態は次のように表示されます。

質問を押すと回答が下へ開き、右端のプラス記号が閉じる記号へ変化します。

質問部分にはbutton要素を使います。これにより、マウスだけでなくTabキーで移動し、Enterキーやスペースキーでも開閉できます。
アコーディオンのHTMLを作る
まず、index.htmlのbody内へ質問と回答を記述します。1つの質問と回答をfaq-itemでまとめ、必要な数だけ繰り返してください。
<div class="faq">
<section class="faq-item">
<h2>
<button
class="faq-question"
type="button"
aria-expanded="false"
aria-controls="faq-answer-1"
>
利用に料金はかかりますか?
<span class="faq-icon" aria-hidden="true">+</span>
</button>
</h2>
<div class="faq-answer" id="faq-answer-1" hidden>
<p>基本機能は無料で利用できます。</p>
</div>
</section>
<section class="faq-item">
<h2>
<button
class="faq-question"
type="button"
aria-expanded="false"
aria-controls="faq-answer-2"
>
スマートフォンでも使えますか?
<span class="faq-icon" aria-hidden="true">+</span>
</button>
</h2>
<div class="faq-answer" id="faq-answer-2" hidden>
<p>はい。画面幅に合わせて見やすく表示されます。</p>
</div>
</section>
</div>aria-controlsには、対応する回答のidを指定します。質問ごとに重複しない値を付けてください。
aria-expanded="false"は閉じている状態、trueは開いている状態を支援技術へ伝えます。回答のhidden属性は、初期状態で非表示にする指定です。
CSSでアコーディオンの見た目を整える
次に、style.cssへ質問と回答の装飾を追加します。質問ボタンを横幅いっぱいに広げ、開いているときはプラス記号を回転させます。
.faq {
display: grid;
gap: 14px;
}
.faq-item {
overflow: hidden;
border: 1px solid #cbd9e7;
border-radius: 14px;
background: #fff;
}
.faq-item h2 {
margin: 0;
}
.faq-question {
display: flex;
width: 100%;
align-items: center;
gap: 14px;
padding: 20px;
border: 0;
background: #fff;
color: #203047;
font: inherit;
font-weight: 700;
text-align: left;
cursor: pointer;
}
.faq-question::before {
content: 'Q';
display: grid;
flex: 0 0 34px;
height: 34px;
place-items: center;
border-radius: 50%;
background: #2474b5;
color: #fff;
}
.faq-icon {
margin-left: auto;
color: #2474b5;
font-size: 24px;
transition: transform 0.2s;
}
.faq-question[aria-expanded='true'] .faq-icon {
transform: rotate(45deg);
}
.faq-answer {
padding: 0 22px 22px 68px;
line-height: 1.8;
}
.faq-answer p {
margin: 0;
padding-top: 18px;
border-top: 1px solid #d8e3ed;
}aria-expanded='true'をCSSの条件に使うため、jQueryが状態を更新すると見た目も連動します。状態を表すclassを別に管理する必要がありません。
スマートフォンの余白を調整する
画面幅が狭い場合は、質問と回答の左右余白を小さくします。先ほどのstyle.cssの末尾へ追加してください。
@media (max-width: 560px) {
.faq-question {
padding: 16px;
}
.faq-answer {
padding: 0 16px 18px;
}
}jQueryでクリック時に回答を開閉する
最後に、jQuery本体を読み込んだ後のscript.jsへ開閉処理を記述します。
jQuery(function ($) {
$('.faq-answer[hidden]').removeAttr('hidden').hide();
$('.faq-question').on('click', function () {
const $button = $(this);
const answerId = $button.attr('aria-controls');
const $answer = $('#' + answerId);
const willOpen = $button.attr('aria-expanded') === 'false';
$button.attr('aria-expanded', String(willOpen));
if (willOpen) {
$answer.removeAttr('hidden').slideDown(200);
} else {
$answer.slideUp(200, function () {
$answer.attr('hidden', '');
});
}
});
});最初の行は、hiddenが付いた回答をjQueryのアニメーションで扱える状態にしてから非表示にしています。
クリック時はaria-controlsから対応する回答を取得し、現在のaria-expandedを見て開くか閉じるかを判断します。slideDown(200)とslideUp(200)の200は、アニメーション時間をミリ秒で指定した値です。
1つ開いたら他の回答を閉じる
常に1つだけ開く形式にしたい場合は、新しい回答を開く前に、開いているほかの回答を閉じます。先ほどのクリック処理を次の内容へ変更してください。
jQuery(function ($) {
$('.faq-answer[hidden]').removeAttr('hidden').hide();
$('.faq-question').on('click', function () {
const $button = $(this);
const $answer = $('#' + $button.attr('aria-controls'));
const willOpen = $button.attr('aria-expanded') === 'false';
$('.faq-question[aria-expanded="true"]')
.not($button)
.attr('aria-expanded', 'false');
$('.faq-answer:visible')
.not($answer)
.slideUp(200, function () {
$(this).attr('hidden', '');
});
$button.attr('aria-expanded', String(willOpen));
if (willOpen) {
$answer.removeAttr('hidden').slideDown(200);
} else {
$answer.slideUp(200, function () {
$answer.attr('hidden', '');
});
}
});
});.not($button)と.not($answer)により、いま操作した質問と回答を閉じる対象から除外しています。
最初から1つ開いて表示する
最初の回答を開いた状態にする場合は、その質問をaria-expanded="true"にし、回答からhiddenを外します。
<button
class="faq-question"
type="button"
aria-expanded="true"
aria-controls="faq-answer-1"
>
利用に料金はかかりますか?
<span class="faq-icon" aria-hidden="true">+</span>
</button>
<div class="faq-answer" id="faq-answer-1">
<p>基本機能は無料で利用できます。</p>
</div>初期状態のHTMLとaria-expandedを一致させることが大切です。見た目だけ開いているのに値がfalseだと、支援技術へ誤った状態が伝わります。
アコーディオンが動かないときの確認ポイント
- jQuery本体より後に
script.jsを読み込んでいるか aria-controlsと回答のidが同じか- 回答ごとの
idが重複していないか script.jsをHTMLの読み込み前に実行していないか- 別のCSSで
display: block !importantを指定していないか
表示・非表示を切り替えるjQueryメソッドの違いは、次の記事でtoggle()やslideToggle()の例とともに解説しています。
クリックイベントと動的要素への設定方法を深く知りたい場合は、次の記事も参考にしてください。
まとめ
- 質問にはキーボードでも操作できる
button要素を使う aria-controlsで質問と回答を対応させるaria-expandedを開閉状態に合わせて更新するslideDown()とslideUp()で回答をアニメーション表示する- スマートフォンでは左右の余白を調整する
まずは2つの質問で基本コードを動かし、問題なく開閉できたらFAQの数や色、開閉ルールを調整してみてください。
