書籍『Every Layout』の著者の一人でもある、Andy Bell 氏の「A (more) Modern CSS Reset」という記事を読んでいて気になるコードがありました。
紹介されているリセット CSS の、リストに対する指定を以下に抜粋します。
記事によると、Safari 上で VoiceOver を使用したときに、list-style: none
が指定されていると、リストがリストとして認識されなくなるとのことです。
このとき、<ul role="list">
や <ol role="list">
と、ARIA ロールを指定するとリストとして認識されるので、この問題を解消できます。さきほどのコードは、これらの指定がある場合のみ list-style: none
が有効になるように意図されたもののようです。
筆者はこの現象については勉強不足で知りませんでした。すでに議論され尽くされているかもしれませんが、この記事では、なぜ Safari(WebKit)ではこのような実装になっているかの背景や意図を理解し、検証したうえで、本サイトでの方針を考えていきます。
背景
見出し「背景」この挙動がバグとして報告されたのは 2017 年の 3 月のようで、以下に Apple の James Craig 氏とのやりとりが記録されています。
コメント欄の最後(2023-01-09)に、過去の James Craig 氏 のツイートへのリンクがあり、そのスレッド内で詳しい背景や意図が語られています。
簡単にまとめると、VoiceOver を利用するユーザからのフィードバックをまとめたところ、ウェブ上にはリストが多すぎて何度もリストの情報が読み上げられるのがわずらわしく感じる、といった意見が寄せられたようです。
加えて、デザイナーが視覚的に不要だと判断してリストマーカーを無効にしているのにも関わらず、スクリーンリーダーのユーザにはリストのセマンティクスを残して、負担を強いる必要があるのかという趣旨の疑問を呈しています。
これらの点を踏まえて、デフォルトのリストの見た目を無効(list-style: none
)にした場合には、リストとして認識しないように意図的に変更されたようです。
もし、list-style: none
を設定した要素をリストとして認識させたい場合には、role="list"
を加えることで明示的にロールを上書きできることも提示されています。
これらの一連のコメントやツイートや関連する記事を読んで、経緯や意図については理解できました。また、自身のマークアップを振り返り、そこまで深く考えずに <ul>
を使用してきた点は反省しなければなりません。
その一方で、リスト要素のデフォルトの見え方であるリストマーカーを CSS で無効にしているからといって、視覚的にリストとして認識しないかと問われると、そこには疑問が残ります。
加えて、リストマーカーは CSS の制約が大きく、細かく位置やサイズを調整するのが難しいため、list-style
を前提としたデザインや実装が避けられるのも事実です。
その対策として、リスト要素に role="list"
を指定する方法が提示されているのですが、ネイティブな HTML 要素が持っている暗黙のロールを同一のロールで上書きするのはルールから逸脱するので、この方法には若干の抵抗があります。
このように、実装者の考えと相容れない部分もありますが、実際の VoiceOver ユーザの声を聞いたうえでの決定なので、そこには一定の説得力があるのかもしれません1。
リストの検証
見出し「リストの検証」Safari と VoiceOver の組み合わせで、リストとして認識されるパターンを調べました。検証環境は Safari 16.3 ですが、環境によっては結果が異なるかもしれません。
試したパターンは以下のとおりです。
- A. デフォルト
- B.
list-style: none
を指定 - C.
<ul role="list">
を指定 - D.
<nav>
の直下にリストを配置 - E.
<li>
に::before
擬似要素を指定 - F.
list-style: url()
を指定 - G.
list-style: ''
を指定
ライブデモは以下よりご確認いただけます。
A. デフォルト
見出し「A. デフォルト」何もスタイルを指定しない場合には、当然リストとして認識されます。
B. list-style: none
を指定
見出し「B. 」この <ul>
に list-style: none
を指定すると、ここまでの説明のとおり、リストとして認識されなくなります。
C. <ul role="list">
を指定
見出し「C. 」list-style: none
を指定した <ul>
に role="list"
を指定すると、再びリストとして認識されます。
D. <nav>
の配下にリストを配置
見出し「D. 」<ul>
に list-style: none
が指定されていても、<nav>
配下に置かれている場合にはリストとして認識されます。
なお、実際のケースではあり得ませんが、親要素が <div role="navigation">
でも同様の結果になり、<nav role="presentation">
であればリストとして認識されなくなります。
また、<nav>
と <ul>
の間に別の要素が存在していてもリストとして認識されます。
つまり、リストに list-style: none
が指定されていても、navigation
ロールを持つ祖先要素が存在する場合にはリストとして認識されます。
E. <li>
に ::before
擬似要素を指定
見出し「E. 」<ul>
に list-style: none
が指定されている状態で、<li>
に ::before
擬似要素を指定します。この擬似要素はレイアウトに影響を及ぼさないようにしたいので、ゼロ幅スペースである \200B
を指定します2。
このテクニックは MDN で紹介されている方法ですが、リストとして認識されました。
以下のように、<ol>
に list-style: none
を指定し、<li>
に ::before
擬似要素でカウンターを指定する場合も同様にリストとして認識されました。
なお、content: ''
のように空文字を指定した場合にはリストとして認識されません。
F. list-style: url()
を指定
見出し「F. 」こちらも同様に MDN で紹介されている方法です。list-style
に dataURL で空の SVG を指定するテクニックで、同様にリストとして認識されました。
G. list-style: ''
を指定
見出し「G. 」list-style: ''
で空文字を指定するパターンです。MDN のページでは空文字では有効にならないと説明されているのですが、リストとして認識されました。
本サイトでの方針
見出し「本サイトでの方針」本サイトでは、リセット時にすべてのリスト要素に list-style-type: none
を指定しています。そのため、Safari と VoiceOver の組み合わせでは、本来リストとして読み上げて欲しい要素であっても、リストとして認識されていないことを確認しました。
これらを修正する方法を検討しましたが、結果的にはあえて何も対応しないことにしました。
理由としては、role="list"
でロールを上書きするのには抵抗がありますし、CSS ハックで対応する方法についても副作用への懸念が残るためです。
また、リストとして認識されなくても致命的ではなく、情報自体は取得できるので、釈然としない気持ちは残りますが、余計なことはせずにブラウザ側に解釈を任せることにしました。
ただ、これとは別に、マークアップ自体の妥当性については、見直していきたいと考えています。
参考文献
見出し「参考文献」本記事の作成にあたり、以下のウェブページを参考にしました。
- 170179 | VoiceOver does not announce a list for groups of links when list-style: none;(外部リンクを開く)
- "Fixing" Lists | scottohara.me(外部リンクを開く)
- Much Ado About No Lists | Eric Eggert(外部リンクを開く)
- list-style | MDN(外部リンクを開く)
脚注
-
ツイートでは、HTML Design Principles の Priority of Constituencies が引用されています。 ↩