🐵
details要素でFAQを実装する : HTML/CSS
主要ブラウザでdetails要素が使えるようになって、簡単にコンテンツの開閉表現ができるようになりました。今回は、以下の要件に従って、なるべくスマートにFAQを実装します。
要件
- アクセシビリティを確保することを最大の優先事項とする
- JavaScriptを使用しないこととする
details要素で実装するメリット
従来のdisplay:none
を切り替える方法と比較して、以下のアクセシビリティが確保されます。
- html/cssだけで実装できる(JavaScriptを用いない)ため、セマンティックに表現しやすい
(無駄なaria属性を量産する必要がない) - キーボードによる操作が可能になる、あるいはその実装コストが不要になる
- ページ内検索が可能になる(display:noneしないため)
- 検索botの不利になりにくい(display:noneしないため)
課題
コンテンツ開閉時にアニメーションを付与したい場合は、html/cssだけで解決できることが少ない。details要素には開閉時にopen属性が付与されるが、属性セレクタを用いてtransitionやanimationを設定しても意図通りには働きません。今後のCSSアップデートで改善する可能性はあるものの、現時点(2023年6月)ではできないようです。今回はJavaScriptを使用しないことを要件にしているため、ここに深くは触れません。
気をつけたいポイント
- 開閉クリッカプブルポイントは、details要素ではなく、summary要素です。そのため、details要素にpaddingを設定してしまうと、クリッカブルの範囲が限定的になり、扱いにくいものになってしまいます。details要素ではなく、summary要素でホワイトスペースの調整をすべきです。
- summary要素に開閉クリッカブルポイントが限定されるということは、FAQでいうところの、回答コンテンツへのクリック・タッチでは開閉させることができません。デザイン要件次第では、この箇所への開閉が必要になるケースもあるでしょう。その対応には、多少の工夫が必要です。
クリッカブルの範囲を全体に拡げる
open時にsummary要素を拡大することで、details要素のどこをクリックしても開閉できるように工夫はできますが、同時にこれには、「details内のテキストを選択できなくなる」という問題が残ります。summary要素を最前面にしてクリッカブル範囲を拡げているためです。これは、ひとつのユーザビリティを犠牲にして、ひとつのユーザービリティを確保する行為なので、制作・開発ポリシーを確認して、取り組みたいところです。
クリッカブル拡大部のCSS
details {
position: relative;
}
/* .deitals要素全体にsummary要素の疑似要素を拡大*/
deitals[open] summary:after {
content: "";
position: absolute;
inset: 0;
display: block;
}
構成
サンプル
デモ
HTML
<article class="faq">
<h1 class="faq-title">よくある質問</h1>
<details class="faq-content">
<summary class="faq-q">ウェブサイトの制作にはどのくらいの期間がかかりますか?</summary>
<p class="faq-a">ウェブサイトの規模や機能により異なりますが、一般的な小規模なウェブサイトの場合、数週間から数ヶ月程度かかる場合があります。</p>
</details>
</article>
CSS
.faq {
display: grid;
gap: 1.5em;
padding-block-start: 2em;
max-inline-size: 60em;
margin: auto;
}
.faq-title {
font-size: 2em;
text-align: center;
}
.faq-title:before {
content: "- FAQ -";
display: block;
font-size: 0.5em;
font-weight: normal;
color: #666;
}
/* FAQ content */
.faq-content {
--accent-color: #3388ff;
--v-rythm: 1.6em;
--gap: 1em;
position: relative;
background: #eee;
border-radius: 1em;
cursor: pointer;
-webkit-user-select: none;
user-select: none;
}
.faq-content:after {
content: "";
position: absolute;
inset-block-start: 1.25rem;
inset-inline-end: 0.75rem;
display: block;
inline-size: 1rem;
aspect-ratio: 1;
background: #666;
overflow: hidden;
rotate: 0deg;
transition: rotate 0.4s 0s ease;
clip-path: polygon(25% 10%, 25% 90%, 75% 50%);
}
.faq-content[open]:after {
rotate: 90deg;
}
.faq-content[open] .faq-q:after {
content: "";
position: absolute;
inset: 0;
display: block;
}
/* Q */
.faq-q {
font-weight: bold;
line-height: var(--v-rythm);
display: flex;
gap: var(--gap);
padding: 1rem 2rem 1rem 1.5rem;
}
.faq-q::-webkit-details-marker {
display: none;
}
.faq-q:hover {
color: var(--accent-color);
}
.faq-q:before {
content: "Q.";
display: block;
color: var(--accent-color);
font-size: var(--v-rythm);
}
/* A */
.faq-a {
margin: 0;
display: flex;
gap: var(--gap);
padding: 0 1.5rem 1rem;
}
.faq-a:before {
content: "A.";
font-weight: bold;
color: var(--accent-color);
font-size: var(--v-rythm);
}
Discussion