📜

【details/summary】アコーディオンの見出しを“正しい”二段組にする

に公開

WEBサイトではおなじみのUIのひとつ、アコーディオン。
さまざまなタグでマークアップが可能なこのUIですが、最近は<details><summary>を使用したものが増えてきたのではないでしょうか。
今回は、これらを使用したアコーディオンの中でもやや凝ったデザインのものを実装する際に発生した問題点と、その解決策を共有します。

ちなみに記事タイトルの“正しい”には、“HTML Living StandardとWAI-ARIAの仕様書を踏まえた、構造的に正しく適切に情報を届けることのできる”という意味を込めています。

この記事でわかること

  • 複数の要素を含むアコーディオンの見出し(<summary>)をマークアップする方法
    • ルールに沿ったセマンティックなHTML構造
    • 適切なARIAの属性付与

やりたいこと(課題)と結論をざっくりと紹介

🤔やりたいこと(課題):タイトルとサブタイトルを二段組にしたアコーディオンを実装したいけど、<summary>の子要素にできるタグが少なく難しい
💡結論:<summary>内のデザインは<span><hgroup>で組み立て、aria-labelledbyで仕様の穴を埋める

まず先に、完成したアコーディオンをお見せします。

構成を図で表すと次のようになっています。

次のセクションから、課題とその解決の解説を含め、完成に至るまでの実装を順番に書いていきます。

実装の流れと解説

<details> <summary>要素を使用したアコーディオンを作成する

基本の骨組みとして、<details><summary>を使用し最低限の要素でアコーディオンをマークアップします。

こちらの骨組みに対して、今回実現したい内容を反映させていきます。

②内容を反映し、CSSでスタイリング

ここで一度、今回の課題を確認します。

🤔やりたいこと(課題):タイトルとサブタイトルを二段組にしたアコーディオンを実装したいけど、<summary>の子要素にできるタグが少なく難しい

そう、<summary>の子要素として使用できるタグは多くありません。
具体的に言うと、<div> <p> <ul> などのフローコンテンツは例外を除き使用できません。
<span> <a> <img>などのフレージングコンテンツと、任意でh要素が使用可能です(このh要素こそが、例外として使用できるフローコンテンツにあたります)。

今回のデザイン案は、

  • 見出し全体を3つのエリアに分割し、
  • CSSで横並びにする。
  • タイトルとサブタイトルは、分割されたエリアの中でさらに上下二段に並ぶ。

という構成になっています。
先ほど確認した通り、<summary>の中で汎用的にグルーピングに使える要素は<span>となります。
こちらを踏まえてHTMLを肉付けしていくと次のようになります。

デザイン案と同じ、”3つのエリアの横並び・タイトル二段組”の作りにできました。
(エリアの区切りがわかりやすいよう、CSSで仮の背景色を付けています)

③タイトルをh要素でマークアップする

ところで今回は、このアコーディオンのタイトルを<h3>とする要件がありました。
タイトルの<span>をそのまま<h3>と書き換えたいところですが、<span>h要素のタグを入れることはできないルールがあります。
そして、<summary><div>を入れることもまたできません。

<!-- spanの子要素にh要素は使えない -->
<summary>
  <span>
    <h3>タイトル</h3><span>サブタイトル</span>
  </span>
</summary>

<!-- summaryの子要素にdivは使えない -->
<summary>
  <div><h3>タイトル</h3>
    <span>サブタイトル</span>
  </div>
</summary>

💡ここで登場するのが<hgroup>要素です。
<hgroup>は、<h1><h2>などと同じh要素の一つです。
<summary>は直接の子要素にh要素を入れられるため、タイトルとサブタイトルを囲む<span><hgroup>とすれば、グルーピングを維持したまま実装が可能です。

ひとつポイントとして、<hgroup>の子要素にできるのは <h1><h6>h要素と<p>に限るという決まりがあるため、サブタイトルを囲む<span><p>にする必要があります。

<summary>
  <hgroup>⬅️
    <h3>タイトル</h3>
    <p>サブタイトル</p>⬅️
  </hgroup>
</summary>

💣目には見えない<summary>の落とし穴

ここまでで問題点がバッチリ解決、とはいかないのが悲しいところ。
実は<summary>には落とし穴とも言える、WAI-ARIAロール属性の特殊な仕様があります。

要素名 ロール
<summary> ロールなし
※ただし、多くのユーザーエージェントはrole="button"として公開する
出典:ARIA in HTML #summary 以下原文引用

No corresponding role
Note: Many, but not all, user agents expose the summary element with an implicit ARIA role=button.

ARIA in HTML

この仕様から<summary>role="button"として扱うとしたほうが良いでしょう。そして、role="button"は子要素にh要素を含むことはない(できない)ため、自身の内にあるh要素を見出しとして認識しません。

その結果たとえば、HTMLの構文としては正しく<h3>を記述しているにもかかわらず、スクリーンリーダーがそれを見出しとして読み上げない、という現象が起きます。

この解決のために、認識させるための属性値を<summary>に付与する必要があります。

④スクリーンリーダーに読み上げ内容を認識させる

💡aria-labelledbyを使用する:

  1. 認識させたいh要素にidを付与
  2. <summary>aria-labelledbyを付与し、その値にh要素のidの値を指定
 <summary aria-labelledby="heading">⬅️
  <hgroup>
    <h3 id="heading">タイトル</h3>⬅️
    <p>サブタイトル</p>
  </hgroup>
</summary>

これでスクリーンリーダーがタイトルを読み上げるようになります!

⑤そして完成へ🎉

ここまでの内容を反映したものがこちらになります。

②の段階と見た目こそほぼ変わりませんが、適切なHTMLとARIAを用いて書かれています。

こちらを元に、アイコンなど必要な記述を足したり、CSSやJavaScriptでコーディングを行ったものが、冒頭にも上げた完成形となります。

GitHubにもアップしました。
https://github.com/DESIGN-SG/details-summary-accordion

💡結論:<summary>内のデザインは<span><hgroup>で組み立て、aria-labelledbyで仕様の穴を埋める

最後にもう一度、実装のポイントまとめ。

  • 見出し部分は、<summary>の子要素に<span>を使用して(必要に応じて<h1><h6><hgroup>も)組んでいく
  • <hgroup>を使用する場合、子要素に使えるのは<h1><h6><p>
  • <h1><h6>を使用する場合、aria-labelledbyも併せて使う

実際に支援技術を使用して確認することが重要

論を結んだところでちゃぶ台返しのようなことを言ってしまうのですが、スクリーンリーダーで音声読み上げの検証を行ったところ、想定した内容と異なっていたという事実は隠さず書いておきます。

検証について

  • 検証内容・・・・・・<summary>aria-labelledbyを付与した場合・しなかった場合の音声読み上げ比較
  • 使用ソフトウェア・・・・・・NVDA
  • 使用ブラウザ・・・・・・Microsoft Edge(Chromeではソフトが動かず)

結果、aria-labelledby付与した場合、h3タイトルの読み上げはするものの、「heading level 3」という見出し情報の読み上げがされませんでした。
逆にaria-labelledby付与しない場合、「heading level 3」とh3タイトルの両方が読み上げられました。

これは、<summary>role="button"ではなくロール無しとして認識されている...?

確かに<summary>にロールは無いと仕様書には書いてあります。
しかし、多くのユーザーエージェントはrole="button"として公開すると補足があり、何ならMDNでは<summary>のロールはbuttonだと説明されています(本来はロール無しとは一言も書かれていない)。

要件にあった実装を

残念ながら、この現象について明確な原因を見つけることはできませんでした。
検証環境や使用ソフト、使用ブラウザによって挙動が変わることも考えられます。
読み上げを重視するのであれば多角的に検証を行い、結果によっては正しいコードにこだわるよりも利用者に寄り添った実装が必要なのかもしれません。

参考

あとがき

今回記事を執筆した理由は、<details><summary>を使用したアコーディオンで凝ったデザインを行う際の情報が見当たらなかったためです。
いつもZennは記事を参考にするのみで、自分が投稿することに迷いがありましたが似たような実装を行う際に同じ壁にぶつかる方がほかにもいるのではないかと思い、その一助となるべく筆を執りました。

この記事は個人がひとりで書いたもので、仕様書に裏打ちされた正確な情報をお届けするという使命感をもって執筆しましたが、コードのレビュワーもこの記事についての添削者もいないため内容に誤りが含まれるかもしれません。
ご指摘の点やご意見などございましたらぜひコメントをお願いします。

ここまで読んでいただきありがとうございました。

おまけ

今回作ったdetailsのアコーディオン、CSSだけでヌルヌルアニメーションが可能というホットな記事がつい最近アップされていました。そちらの記事を参考に、JavaScriptを使わないver.も作ってみました。
JavaScriptの記述をすべて削除して、cssにセレクタブロックを2つ追加するだけです。(今回のコードだと//開閉アニメーションより下の部分)

うおお、本当にヌルヌル動く!

参考にした素晴らしい記事はこちら↓↓
https://zenn.dev/zozotech/articles/b9fabb31b3e876

Discussion