FAQ(アコーディオン)

複数同時に開閉できるFAQアコーディオンUIです。クリックすると grid-template-rows を0fr ⇄ 1frで切り替えるCSSグリッドアニメーションが動作し、JavaScriptはクラスの付け外しのみのシンプルな構成です。QとAのラベルを付けて、見やすくFAQらしいデザインに仕上げています。

はい。display: gridgrid-template-rowsを使い、0frから1frに切り替えることで、中身の高さに応じたスムーズな開閉を実現しています。
height: autoはCSSのトランジションで直接アニメーションできないためです。grid-template-rowsを使うことで、中身の高さが可変でもスムーズにアニメーションできます。
はい。このコードは複数同時オープンが可能です。クリックした項目だけクラスを切り替えるため、他の項目は閉じません。

解説

このアコーディオンUIでは、CSSの display: grid と grid-template-rows を利用して開閉アニメーションを実現しています。height: auto ではトランジションできないため、0frから1frに切り替えることで中身の高さに応じてスムーズに展開できます。JavaScriptはクリック時に.is-openクラスを切り替えるだけなので、処理が軽くメンテナンス性も高い構造です。また、複数の質問を同時に開ける仕様にしているため、ユーザーは必要な情報を一度に比較できます。

📄 HTML

<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>

🎨 CSS

.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; 
}

🧠 JavaScript

document.addEventListener('DOMContentLoaded', () => {
  document.querySelectorAll('.faq__q').forEach(btn => {
    btn.addEventListener('click', () => {
      btn.closest('.faq__item').classList.toggle('is-open');
    });
  });
});

戻る