アイコンは飾り、意味はラベルで伝える
開発環境
- HTML / React 19
前提
アイコンボタンを実装するとき、スクリーンリーダー対応で aria-hidden="true" という属性を見かけることがあります。
最初は「とりあえず付けておくもの」くらいの認識だったのですが、付ける場所を間違えるとボタンの存在ごと読み上げられなくなる、という事故を起こしかけました。
「どこに付けると正しくて、どこに付けるとまずいのか」を整理したかったので、アウトプットとしてまとめます。
スクリーンリーダーは macOS の VoiceOver で挙動を確認しています。
本題
aria-hidden="true" は「読み上げから隠す」属性
aria-hidden="true" は、その要素(と中身すべて)をスクリーンリーダーのアクセシビリティツリーから取り除く属性です。
見た目はそのまま表示されますが、スクリーンリーダーからは存在しないものとして扱われます。display: none のように見た目まで消すわけではない、というのがポイントです。
- 視覚的には見える
- スクリーンリーダーには読まれない
アイコンは純粋に視覚的なので読み上げる必要がない
⚠️ たとえば閉じるボタンを、アイコンだけで実装したとします。
<button>
<XIcon />
</button>このアイコンが SVG やアイコンフォントで描かれている場合、スクリーンリーダーはその中身を読もうとします。うまくいけば無音ですが、環境によっては「画像」「パス」といった意味のない読み上げが発生したり、そもそもボタンが何のためのものか伝わりません。
アイコンそのものは「✕という形」という純粋に視覚的な表現であって、目で見えるユーザーのためだけのものです。スクリーンリーダーのユーザーに必要なのは「閉じる」という意味であって、アイコンの見た目ではありません。
🛠 そこで、アイコンには aria-hidden="true" を付けて読み上げから隠し、ボタンには aria-label で意味を持たせます。
<button aria-label="閉じる">
<XIcon aria-hidden="true" />
</button>👉 こうすると、目で見えるユーザーには「✕アイコン」が見え、スクリーンリーダーのユーザーには「閉じる、ボタン」と読み上げられます。それぞれに必要な情報だけが届く状態です。
隠すのはアイコン、ラベルはボタン側に
ここで間違えやすいのが、aria-hidden="true" をボタン自体に付けてしまうパターンです。
{/* NG:ボタンごと読み上げから消える */}
<button aria-hidden="true" aria-label="閉じる">
<XIcon />
</button>aria-hidden="true" は子要素もまとめて隠すため、ボタンに付けるとボタンの存在ごとスクリーンリーダーから消えます。aria-label を付けていても無意味です。
- ボタンに付ける → ボタンごと消える(NG)
- アイコンに付ける → アイコンだけ隠れてボタンは残る(OK)
隠したいのは「視覚的な飾り」であるアイコンだけ、と意識すると間違えにくくなります。
テキストとアイコンが両方あるなら aria-label はいらない
ボタンの中にテキストラベルがすでにある場合は、アイコンを隠すだけで十分です。
<button>
<TrashIcon aria-hidden="true" />
削除
</button>「削除」というテキストがスクリーンリーダーに読まれるので、aria-label を重ねて付ける必要はありません。アイコンは飾りとして隠し、意味はテキストに任せます。
逆に、ここで aria-label="削除" も付けてしまうと、aria-label がテキストを上書きするため意図せぬ読み上げになることがあります。ラベルは一箇所で持たせるのが基本です。
同じパターンが使える場所
このパターンは閉じるボタンに限った話ではありません。「スクリーンリーダーには意味を伝えたいが、目で見えるユーザーには見せる必要のない(あるいはアイコンで十分な)あらゆる場所」に当てはまります。
- 閉じるボタン(✕アイコン)
- メニューボタン(ハンバーガーアイコン)
- アイコントグル(お気に入りの星、再生・一時停止など)
- テキストにうまく変換できない視覚的表現全般
たとえばお気に入りのトグルなら、状態を aria-label 側で表現し、アイコンは隠します。
<button aria-label={isFavorite ? "お気に入りから外す" : "お気に入りに追加"}>
<StarIcon aria-hidden="true" />
</button>「見た目はアイコン、意味はラベル」という役割分担を、そのまま横展開できます。
さいごに
aria-hidden="true" は「読み上げから隠す」属性ですが、ポイントは隠す対象を間違えないことだと思います。
隠すのはあくまで視覚的な飾りであるアイコンで、ボタンそのものではありません。そして意味は aria-label かテキストのどちらか一箇所で持たせる、と整理しておくと迷わなくなりました。
アイコンボタンを書くときは「このアイコンはただの見た目か?」「意味はどこで伝わるか?」の2つを確認する、と覚えておくとよさそうです。