複数同時に開閉できるFAQアコーディオンUIです。クリックすると grid-template-rows を0fr ⇄ 1frで切り替えるCSSグリッドアニメーションが動作し、JavaScriptはクラスの付け外しのみのシンプルな構成です。QとAのラベルを付けて、見やすくFAQらしいデザインに仕上げています。
display: grid
とgrid-template-rows
を使い、0frから1frに切り替えることで、中身の高さに応じたスムーズな開閉を実現しています。このアコーディオンUIでは、CSSの display: grid と grid-template-rows を利用して開閉アニメーションを実現しています。height: auto ではトランジションできないため、0frから1frに切り替えることで中身の高さに応じてスムーズに展開できます。JavaScriptはクリック時に.is-openクラスを切り替えるだけなので、処理が軽くメンテナンス性も高い構造です。また、複数の質問を同時に開ける仕様にしているため、ユーザーは必要な情報を一度に比較できます。
<div class="faq">
<div class="faq__item is-open">
<button type="button" class="faq__q">このアコーディオンはgridでアニメーションしているのですか?<span class="faq__icon" aria-hidden="true"></span></button>
<div class="faq__a"><div class="faq__a-inner">はい。<code>display: grid</code>と<code>grid-template-rows</code>を使い、0frから1frに切り替えることで、中身の高さに応じたスムーズな開閉を実現しています。</div></div>
</div>
<div class="faq__item">
<button type="button" class="faq__q">なぜheight: 0やautoではなく、この方法を使っているのですか?<span class="faq__icon"></span></button>
<div class="faq__a"><div class="faq__a-inner">height: autoはCSSのトランジションで直接アニメーションできないためです。grid-template-rowsを使うことで、中身の高さが可変でもスムーズにアニメーションできます。</div></div>
</div>
<div class="faq__item">
<button type="button" class="faq__q">複数の項目を同時に開くことはできますか?<span class="faq__icon"></span></button>
<div class="faq__a"><div class="faq__a-inner">はい。このコードは複数同時オープンが可能です。クリックした項目だけクラスを切り替えるため、他の項目は閉じません。</div></div>
</div>
</div>
.faq {
border: 1px solid #eee;
border-radius: 10px;
background: #fff;
}
.faq__item {
border-top: 1px solid #eee;
}
.faq__item:first-child {
border-top: none;
}
.faq__icon {
position: absolute;
right: 16px;
top: 50%;
transform: translateY(-50%);
width: 24px;
height: 24px;
}
.faq__icon::before,
.faq__icon::after {
content: "";
width: 16px;
height: 2px;
position: absolute;
left: 50%;
top: 50%;
background: #666;
transform: translate(-50%, -50%);
}
.faq__icon::after {
width: 2px;
height: 16px;
transition:
opacity .25s ease;
}
.faq__item.is-open
.faq__icon::after {
opacity: 0;
}
.faq__q {
width: 100%;
background: #f8f3eb;
border: none;
text-align: left;
padding: 18px 56px 18px 56px;
cursor: pointer;
position: relative;
font-size: 16px;
font-weight: 600;
}
.faq__q::before {
content: "Q";
position: absolute;
left: 16px;
top: 50%;
transform: translateY(-50%);
width: 28px;
height: 28px;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: 14px;
color: #fff;
background: #f59e0b;
}
.faq__a-inner {
line-height: 1.9;
overflow: hidden;
padding: 0 20px 0 56px;
position: relative;
transition: padding .45s ease;
}
.faq__item.is-open .faq__a-inner {
padding: 20px 20px 20px 56px;
}
.faq__a-inner::before {
content: "A";
position: absolute;
left: 16px; top: 20px;
width: 28px;
height: 28px;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: 14px;
color: #fff;
background: #3b82f6;
}
/* gridによるアニメーション */
.faq__a {
display: grid;
grid-template-rows: 0fr;
background: #f7f7f7;
transition: grid-template-rows .45s ease;
position: relative;
}
.faq__item.is-open .faq__a {
grid-template-rows: 1fr;
}
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.faq__q').forEach(btn => {
btn.addEventListener('click', () => {
btn.closest('.faq__item').classList.toggle('is-open');
});
});
});