開発記録
ネイティブ details:一つだけ開く設計とキーボード操作、スクリプトなしの備え
About ページの経歴カードを例に、details、summary、同時に一つだけ開くグループ、アニメーション用スクリプトの役割を整理します。JavaScript がなくてもキーボードで内容を開けるかを確認したノートです。
- HTML
- プログレッシブエンハンスメント
- キーボード操作
- インタラクションの検証
並んだ経歴カードを折りたたむと、ページは確かに短くなります。ただ、各項目の内容と開き方、次の項目を開くと前の項目が消えるのかも、読者に分かるようにする必要があります。開くアニメーションを考える前に、まずこれらを決めておきます。
当サイトの About ページでは、ネイティブの details と summary を使い、少量のスクリプトでアニメーションとスクロールを補っています。2026 年 10 月 25 日に、スクリプトを有効にした場合、ページのスクリプトを無効にした場合、reduced-motion をエミュレートした場合を分けて操作を確認しました。確かめたかったのは、アニメーション層を外しても内容を読むための入口が残るかどうかです。
summary では、何が開くのかを伝える
details は、開いて確認できる補足情報を載せるのに適した要素です。最初の summary 子要素が、その内容の要約やラベルになります。これは HTML 自体に備わる開閉コントロールなので、先に div を独自のボタンに作り替える必要はありません。[1]
当時の経歴カードの summary には、時期、会社名、肩書きが含まれていました。閉じた状態でもどの経歴かを識別でき、中身を知るためだけに開く必要はありません。右側の矢印は開閉状態だけを示す装飾なので、もう一つのコントロール名にならないよう、aria-hidden を設定していました。
ビジュアル作品がある項目には、作品へのリンクも別に用意していました。DOM 上では、このリンクは details の外側にある兄弟要素で、summary の中には置いていませんでした。経歴を展開することと作品を開くことは、別の目的です。見た目では近くにあっても、操作の役割は分けます。
この区別は、ほかのカードにも当てはまります。タイトルで内容を展開し、隣のアイコンで別のページへ移動するなら、同じ横一列に並んでいるという理由だけで、二つの操作を一つのコントロールにまとめるべきではありません。
一つだけ開く設計は、アコーディオンの必須条件ではありません
この経歴一覧の details には、共通の name="career" が指定されていました。HTML の同名グループでは、同時に開く項目は一つまでです。別の項目を開くと、ブラウザが元の項目を閉じます。ほかのカードを自分で順番にたどり、一つずつ open を取り除く必要はありません。[1]
「一つまで」には、すべて閉じた状態も含まれます。サイトの初期 HTML では一つだけ開く設定でしたが、読者はそれも閉じられます。どれか一つを必ず選択状態にしておく必要はありません。
この構成は一件ずつ読む場合には合いますが、どんな内容にも適しているわけではありません。HTML 仕様でも、排他的な展開は、複数の情報を同時に比較したい人の妨げになる可能性があると注意しています。[1] プランや仕様、時期による違いを見比べる内容なら、複数を同時に開けるようにすべきか検討し直します。ネイティブ要素に付いているからといって、一つだけ開く仕組みを必ず採用するわけではありません。
もう一つ間違えやすいのが、open はブール属性だという点です。判定されるのは属性が存在するかどうかであり、文字列が true や false に見えるかどうかではありません。閉じた状態を出力するなら、open="false" を残すのではなく、属性自体を省きます。[1]
アニメーションより先に、キーボードで操作できるようにする
ローカルでの操作では、まず二つ目の summary にフォーカスを当て、Enter を押しました。その項目が開き、元の項目は閉じました。開いている summary で Space を押すと、すべて閉じた状態に戻りました。別の項目をマウスでクリックした場合も、クリックした項目だけが開いたままになりました。
これらはブラウザの入力イベントを使った操作であり、コードで直接 open を true に設定したわけではありません。状態を直接変える方法はレイアウトの確認には使えますが、それだけで読者がキーボードで実際に操作できるとは示せません。
summary をさらに button で囲んだり、方向キーで項目を移動するための独自コントロールを設けたりはしていませんでした。提供していたのはネイティブの開閉です。見た目がアコーディオンに似ているという理由で、複合ウィジェットの別のキーボード操作体系まで備えているとは言えません。
ページには共通の focus-visible スタイルが残されていました。ただし、フォーカスの見た目があることと、支援技術で十分に操作できることは別です。この Enter、Space、マウスによる観察には、スクリーンリーダー、フォーカス順序の網羅的な確認、ブラウザ横断の検証は含まれていません。
スクリプトが加えるのは、トランジションと表示位置の調整
About ページのスクリプトは、開閉状態をすべて引き受けていたわけではありません。開いているカードを通常の操作で閉じるときは、まずデフォルトの動作を抑え、is-closing を付けていました。内容をフェードアウトさせて少し移動させたあと、閉じる処理を完了する流れでした。コードには、処理を終えるための別経路として 200 ミリ秒のタイマーも用意されていました。これは実装上の設定であり、測定した操作遅延ではありません。
別の項目が開くと、スクリプトはその項目がナビゲーションバーの下付近に来るようスクロールを予約していました。これはビューポートの位置を調整するもので、キーボードフォーカスを本文へ移す処理ではありません。一つだけ開くというルールを実現しているのも、この処理ではありません。
そのため、この操作を評価するときは、details が実際に開いているかと、画面のトランジションが終わったかを分けて見ます。ある瞬間にまだフェードアウト中だからといって、すぐに開閉状態の切り替えに失敗したと判断することはできません。反対に、フェードアウトが見えたというだけで、最後に open が本当に取り除かれたかを見落としてもいけません。
reduced-motion が有効な場合、閉じる側の処理はネイティブの動作をそのまま使い、CSS もこの内容の開くアニメーションを無効にしていました。このメディア条件をエミュレートしたところ、カードを閉じられ、is-closing も残らないことを確認しました。これはブラウザ内でのメディア条件のエミュレーションであり、あらゆる OS 設定や端末を試したわけではありません。
JavaScript がなくても、展開した内容を読める
別途、ページのスクリプト実行を禁止して About ページを読み込み直し、Enter と Space で異なる項目を開きました。同名グループでは引き続き同時に一つまでしか開かず、内容もページの元の HTML のままでした。
このフォールバックが成立するのは、summary と経歴の本文が最初から存在し、クリック後にスクリプトで生成されるものではないからです。スクリプトがないと使えなくなるのは、閉じるときの独自トランジションと、開いたあとのスクロールです。読者が内容にアクセスする入口は残ります。
| 状況 | この確認で観察した動作 |
|---|---|
| 通常モードで Enter | 対象の項目が開き、それまでの項目が閉じる |
| 通常モードで Space | 開いていた項目が閉じ、すべて閉じた状態にできる |
| summary をマウスでクリック | 対象の項目が開き、同時に開くのは一つまでに保たれる |
| ページのスクリプトを無効化 | Enter、Space、ネイティブの一つだけ開く仕組みが引き続き機能する |
| reduced-motion をエミュレート | 正常に閉じられ、閉じる途中の class が残らない |
これはローカルの Edge で行った一度の機能観察であり、バックフォワードキャッシュ(BFCache)、支援技術、モバイルでの読み心地についての主観的な評価は含まれていません。ネイティブ要素は使える出発点を用意してくれますが、内容、フォーカス、読み方に関する設計をすべて代わりに済ませてくれるわけではありません。
ボタンに見えても、<button> にすべきとは限りませんというノートでも、同じ選択を扱っています。まず HTML で操作の目的を表すことです。このカードで開示するのは経歴の補足情報なので、開閉は details に任せたうえで、どのアニメーションを加える価値があるかを決めます。
参考資料
[1] https://html.spec.whatwg.org/multipage/interactive-elements.html