🐵

details要素でFAQを実装する : HTML/CSS

2023/07/18に公開

主要ブラウザで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を使用しないことを要件にしているため、ここに深くは触れません。

気をつけたいポイント

  1. 開閉クリッカプブルポイントは、details要素ではなく、summary要素です。そのため、details要素にpaddingを設定してしまうと、クリッカブルの範囲が限定的になり、扱いにくいものになってしまいます。details要素ではなく、summary要素でホワイトスペースの調整をすべきです。
  2. 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