divで作ったボタンが失う七つのもの
一言でいうと
<button>の代わりに<div onclick>を使うと、ブラウザーが代わりにやってくれていた7つのことを失います。そして、そのうち5つは、たいてい作り直されません。
なぜ問題になるのか: 見えないユーザー
マウスで作ってマウスで確認していると、この事故は最後まで表面化しません。<div onclick>は、マウスでは完璧に動作するからです。QAもマウスで確認するため、そのままデプロイされます。
壊れるのは、キーボードだけを使うユーザーの側です。手首を痛めている人、スクリーンリーダーを使う人、あるいは単にTabキーのほうが速い人です。その人たちにとって、そのボタンは、画面に見えているのに押せない絵です。それが決済ボタンなら、その人は決済ができません。
直すコストも、時間がたつほど大きくなります。タグ1つを変えるだけでは済まず、その上に積み重なったCSSセレクター・イベント委譲・テストのセレクターをすべて追いかけなければならなくなるからです。そのため、この文章は「あとで確認するリスト」ではなく、タグを選ぶ瞬間の判断を扱います。
失うもの
<div onclick="save()">저장</div>(韓国語で「保存」を意味する語です)が失うものは、次のとおりです。
- Tabキーで移動できません:
tabindex="0"が必要です - EnterキーやSpaceキーで押せません: キーハンドラーを自分で付ける必要があります
- スクリーンリーダーが「ボタン」と読み上げません: ただのテキストです
- 無効化がありません:
disabledが効きません - フォームを送信できません:
type="submit"がありません - フォーカスリングがありません: キーボードユーザーが今どこにいるかわかりません
- ブラウザーの標準動作がありません: コンテキストメニュー、自動化ツールによる認識
<button>と書くだけで、すべてが無料でついてきます。アクセシビリティは、あとから載せる機能ではなく、タグを選ぶ瞬間にほとんど決まります。
ランドマーク: ページの地図
スクリーンリーダーのユーザーは、ページを上から読んでいくわけではありません。ランドマークの間を飛びます。
<header> 사이트 머리 </header>
<nav> 탐색 </nav>
<main> 본문 — 페이지에 하나만 </main>
<aside> 곁다리 </aside>
<footer> 꼬리 </footer>
<main>があれば、「本文へスキップ」が動作します。<div class="main">には何の意味もありません。クラス名は、機械に何も教えてくれないからです。
見出しは目次です
h1–h6は文字の大きさではありません。文書の目次です。スクリーンリーダーは、見出しだけを走査して構造を把握します。
h1は1つだけにします。ページが何についてのものかを示すためです- 飛ばしません:
h2の次にh4が来てはいけません - 大きさを変えたいときは、CSSで変更します。タグは変えません
見た目がよいからという理由でh3を使うのが、アクセシビリティの問題の中で最もよくあるものです。
altは「説明」ではありません
altは、その画像が表示されなくなったときに、その場所を埋めるテキストです。
| 画像 | alt |
|---|---|
| 製品写真 | alt="빨간 등산 배낭 45L"(韓国語の文は「赤い登山用バックパック45L」という意味です) |
| リンク内のロゴ | alt="홈으로"(韓国語で「ホームへ」を意味する語です)。画像ではなくリンクの目的を書きます |
| 装飾用の区切り線 | alt="": 空にしておきます |
| 隣に同じ説明がある画像 | alt="": 2回読み上げられると邪魔になります |
alt属性そのものを省くこととalt=""は別です。省くと、スクリーンリーダーがファイル名を読み上げます(IMG_20240103.jpg)。空にしておくと、静かに読み飛ばされます。装飾画像には、必ずalt=""を明示します。
ラベルのない入力欄は名前を持たない
<!-- ❌ placeholder 는 라벨이 아니다 -->
<input placeholder="이메일">
<!-- ✅ -->
<label for="email">이메일</label>
<input id="email" type="email" autocomplete="email">
placeholderは、入力すると消えます。何を書く欄だったのかを確認する方法がなくなります。弱視のユーザーにとっては、コントラストも不足しています。
ラベルを関連付けると、文字をクリックしても入力欄にフォーカスが移ります。モバイルでは特に大きな違いです。
また、typeとautocompleteを正しく指定すると、モバイルのキーボードが切り替わり、オートコンプリートが動作します。たとえばtype="email"、type="tel"、autocomplete="one-time-code"です。
リンクとボタンは異なります
リンクは移動し、ボタンは何かをします。
- アドレスが変わるなら →
<a href>。新しいタブで開く、ブックマーク、戻るが動作します - 状態が変わるなら →
<button>
<a href="#" onclick="...">は、どちらでもありません。そして、リンクのテキストは、それ自体で目的を伝える必要があります。スクリーンリーダーのユーザーは、リンクだけを抜き出して一覧で見ます。「ここ」や「もっと見る」ばかりが20個並んだ一覧は、役に立ちません。
表には見出しセルが必要です
<table>
<caption>월별 매출</caption>
<thead><tr><th scope="col">월</th><th scope="col">매출</th></tr></thead>
<tbody><tr><th scope="row">1월</th><td>1,200</td></tr></tbody>
</table>
scopeがあって初めて、スクリーンリーダーが「1月、売上、1200」のように読み上げます。ないと、数字だけがだらだらと読み上げられます。
レイアウトのために表を使いません。それはCSS Gridの仕事です。
langを書き忘れない
<html lang="ko">
ないと、スクリーンリーダーが韓国語を英語の発音規則で読み上げます。1文字を直すだけで得られるものの中で、最も効果が大きいものです。
ARIAは最後の手段です
悪いARIAは、ないほうがましです。
role="button"を付けても、キーボード操作は生まれません。見かけの名前だけが変わり、実際の動作はそのままです。そのため、ルールは1つです。
適切なHTML要素があれば、それを使います。ARIAは、HTMLに対応する要素がないとき(タブパネル、ツリー、ライブリージョン)に使います。
現場での確認方法
自動検査ツールは、問題の30–40%だけを検出します。コントラストが足りない、altがないといった、機械が見られるものだけを検出するからです。残りは、人が見る必要があります。
- キーボードだけで最後までやってみます。Tab、Shift+Tab、Enter、Space、Esc。マウスをどけて5分あれば、ほとんどが明らかになります
- フォーカスが今どこにあるかが常に見えているか
- 200%に拡大しても使えるか
この3つは、自動ツールよりも多くを検出します。
実務では、これをPRチェックリストの1行として固めておくほうが長続きします。「新しく作ったインタラクティブ要素をキーボードだけで使ってみたか」の1つで十分です。人が毎回覚えていることを期待するルールは、結局守られません。