トピックを検索
キーボードでセルを移動・編集できないデータグリッド
値を並べた表のセルが、クリックしたときだけ入力欄に変わります。role="grid" があってもなくても、キーボードではセルに届かず、クリックで開く入力欄にも名前がありません。
cell.onclick = () => edit(cell); 問題
キーボード利用者は表のセルに届かず値を変えられず、スクリーンリーダー利用者には、値を変えられることも、開いた入力欄が何のためのものかも伝わりません。
あるお店の管理画面に在庫表があります。「品目」「価格」「在庫数」の3列で、商品を1行ずつ並べた表です。見出しは scope を付けた th で、表の作りそのものに問題はありません。価格をクリックすると入力欄に変わり、新しい値を入れてEnterキーを押すと、表に反映されます。マウスを使わずにTabキーを押すと、フォーカスは表を素通りして「変更を保存」へ進みます。セルはフォーカスを受けないため、価格は一つも変えられません。値を変えられることも、どこにも示されていません。Chromeでは、各価格はただの表のセルです。表のセルは内容のためのロールで、操作部品のロールではありません。クリックでセルを開いても、スクリプトがそこに入れる入力欄には独自の名前がなく、どの品目のどの列を編集しているのかは、コードのどこにも書かれていません。
そこで、チームは手早い方法で表をグリッドにします。表に role="grid" を付け、それ以外は何もしません。すると、Chromeでは価格がグリッドのセルになります。Tabの移動先が1つで、矢印キーでセルからセルへ動けるウィジェットだと約束するロールです。ところが、その約束を果たすものは何もありません。どのセルもフォーカスを受けず、Tabキーは今も表を飛ばし、矢印キーを押す場所もありません。クリックで開く入力欄にも、名前がないままです。
達成基準2.1.1「キーボード」は、マウスでできることはすべてキーボードでもできることを求めています。価格を変える方法は、フォーカスを受けないセルのクリックだけのため、マウスなしでは値を一つも変えられません。role="grid" を足しても、この点は変わりません。セルは今もフォーカスを受けず、グリッドが約束する矢印キーは何もしません。
達成基準4.1.2「名前(name)・役割(role)及び値(value)」は、操作部品の名前と役割をソフトウェアが判別できることを求めています。価格はクリックで入力欄に変わる操作部品です。最初の表では、各価格は表のセルとしてしか伝わらず、変えられることを示すものがありません。W3Cの失敗例では、F59(スクリプトで要素を操作部品にしながら役割を与えないもの。F59が例に挙げるのはdivとspan)がこれに当たり、より広くはF15(アクセシビリティAPIを使わない独自の操作部品)にも当たります。role="grid" を付けると、セルは操作できることもある役割を得ますが、どのキーもセルに届きません。どちらの表でも、クリックでセルに入る入力欄はスクリプトがラベルなしで作るため、名前がありません。スクリーンリーダーが入力欄そのものから得られるのは編集欄と数字だけで、どのセルなのかは分かりません。
このような表は、多くの管理画面にあります。在庫と価格、勤務表、予算、座席表、注文の一括編集などです。値を変える手段がこの表しかないことも多く、キーボードで入れない表があると、その仕事そのものができない人が出てきます。
10グループ中2グループに影響
axeはどちらの表も合格にします。クリック処理を付けただけの td には規則が確かめられるものがなく、見出しのセルを持つ表に role="grid" を付けるのは正しいマークアップだからです。セルがフォーカスを受けるか、矢印キーでフォーカスが動くかを確かめる規則はありません。クリックで開く入力欄はaxeの label の規則なら指摘できますが、クリックするまでページにないため、読み込んだときのページを調べても見つかりません。
この診断ツールはキーを押します。各グリッドで、セルかセルの中の要素がTabでフォーカスを受けるかを確かめ、どれも受けなければ、どのセルにも届かないとして報告します。1つのセルがTabの移動先なら、そこにフォーカスを置いて矢印キー、Home、Endを押し、フォーカスがまったく動かなければ、ほかのセルに届かないとして、これも報告します。人が確認する項目として渡すものは3つあります。複数のセルがTabの移動先なのに矢印キーが何もしないグリッド。ポインターやセル、テキストの形のカーソルが出るのに、フォーカスを受けず入力欄もないセルを持つ表。そして、gridやsheetと名付けた、divで作った要素のうち、そうしたセルを持つものです。クリックできるセルでも、セル自体にカーソルの指定がなければ、その表は見つけられません。セルを編集のために開いて入力欄の名前を確かめるチェックもないため、そこは人が確かめます。
2つ目の表は、AIによる修正でできることがある形で、role="grid" を加えたところで止まっています。すべてのセルに tabindex="0" を付ける修正もありえます。どのセルにもTabで止まれるようにはなりますが、セルを開くキーはないままです。
試してみる
キーボードだけで、紙コップの価格を0.15に変えてください。
- 問題1:Tabキーを押します。フォーカスは表を素通りして「変更を保存」へ進むため、どの価格にも届きません。価格をクリックすると入力欄が開きますが、品目も列も読まれません。
- 問題2:同じ表に role="grid" を足しただけです。Tabキーは今もすべてのセルを飛ばし、矢印キーを押そうにもフォーカスの置き場所がありません。クリックで開く入力欄も、品目と列は読まれないままです。
- 修正を適用します。グリッドでは、Tabキーを1回押して矢印キーで移動し、EnterキーかF2キーで編集、Enterキーで確定、Escapeキーで取り消します。
問題あり 修正できますか?
在庫表
| 品目 | 価格 | 在庫数 |
|---|---|---|
| オーツミルク | 2.40 | 36 |
| 紙コップ | 0.12 | 500 |
| エスプレッソ豆 | 14.00 | 8 |
スクリーンリーダーの読み上げ:
(Tabで表へ移動するか、価格をクリック)
修正方法
編集できるセルはすべてキーボードで届いて開けるようにし、セルを編集する入力欄に行と列から名前を付けます。
- 1編集できるセルはすべて、キーボードで届いて編集できる。
- 2セルを編集する入力欄には、行と列から名前を付ける。
- 3role="grid" を使うなら、ロールが示すとおり、Tabの移動先1つ、矢印キー、各セルに入るキーをすべてそろえる。そろえないなら、セルごとに入力欄を置く。
クリックでしか開かないセルの表
<h2 id="stock-title">在庫表</h2><table class="sheet" aria-labelledby="stock-title"> <thead> <tr> <th scope="col">品目</th> <th scope="col">価格</th> <th scope="col">在庫数</th> </tr> </thead> <tbody> <tr> <th scope="row">オーツミルク</th> <td>2.40</td> <td>36</td> </tr> <tr> <th scope="row">紙コップ</th> <td>0.12</td> <td>500</td> </tr> <tr> <th scope="row">エスプレッソ豆</th> <td>14.00</td> <td>8</td> </tr> </tbody></table><button type="button">変更を保存</button>セルごとに入力欄を置く
<h2 id="stock-title">在庫表</h2><table class="sheet" aria-labelledby="stock-title"> <thead> <tr> <th scope="col" id="col-item">品目</th> <th scope="col" id="col-price">価格</th> <th scope="col" id="col-stock">在庫数</th> </tr> </thead> <tbody> <tr> <th scope="row" id="row-oat">オーツミルク</th> <td> <input value="2.40" aria-labelledby="row-oat col-price"> </td> <td> <input value="36" aria-labelledby="row-oat col-stock"> </td> </tr> <tr> <th scope="row" id="row-cups">紙コップ</th> <td> <input value="0.12" aria-labelledby="row-cups col-price"> </td> <td> <input value="500" aria-labelledby="row-cups col-stock"> </td> </tr> <tr> <th scope="row" id="row-beans">エスプレッソ豆</th> <td> <input value="14.00" aria-labelledby="row-beans col-price"> </td> <td> <input value="8" aria-labelledby="row-beans col-stock"> </td> </tr> </tbody></table><button type="button">変更を保存</button>この方法を選ぶ理由:まずHTMLの要素を使う
セルは click にしか反応せず、フォーカスも受けないため、どのキーでも届きません。td は表のセルなので、変えられることを示すものはなく、スクリプトが作る入力欄にもラベルがありません。
各値は表のセルの中にあるHTML標準の入力欄のため、Tabキーで読む順にすべてに届き、入力すれば値が変わります。あとはブラウザーが受け持つので、クリックのスクリプトは要らなくなります。表のセルに置いた入力欄は、表の見出しを名前として受け取らないため、aria-labelledby で行と列の見出しから名前を付けます。読み上げは「紙コップ 価格、編集、0.12」のようになります。入力欄は見える枠を残しているので、入力できる場所だと見て分かります。
どちらも基準を満たします。セルごとに入力欄を置く方法は、スクリプトも新しく覚えるキーも要りません。どの入力欄もTabの移動先になり、入力、文字の選択、ブラウザーの取り消しも初めから使えます。この例のような数行の表や、表の形に並べたフォームにはこちらを選んでください。グリッドは表全体をTabの移動先1つにまとめるため、何百行もある注文の一覧のように、入力欄を1つずつTabでたどっていられない長い表や、利用者が表計算ソフトのキー操作を期待するアプリケーションに向いています。そのときは、Tabの移動先1つ、すべてのキー、開くセルごとの名前付きの入力欄をそろえます。
Authoring Practicesは、表計算ソフトのように、セルで文字や数字を入力すると編集が始まる操作や、長いグリッドでのPage UpとPage Downも勧めています。この例にはどちらもありません。ほかの列から計算する合計のように変えられない列は、フォーカスは受けたまま、そのセルに aria-readonly="true" を付けます。aria-selected は、フォーカスとは別にセルや行を選べるグリッドのためのものです。
キーを扱う代わりに、グリッドのすべてのセルに tabindex="0" を付けるのはやめてください。Tabですべてのセルに届くようにはなりますが、グリッドのセルは矢印キーを約束しており、セルを開くキーもありません。セルからセルへTabで移動させたいなら、最初の修正のように、セルごとに入力欄を置きます。
表がすでに持っている構造は、別の問題です。divで作ったグリッドには各行のセルを囲む行が必要で、見えている行だけを描画するグリッドには、スクリーンリーダーが各セルの位置を伝えられるよう、グリッドに aria-rowcount、各行に aria-rowindex が必要です。どちらも見出しが太字だけのデータ表と同じトピックで扱います。受信トレイのスレッドのように、行を開くと子の行が現れるものは、ツリーグリッドという別のパターンです。リッチテキストエディターを入れたセルには、そのエディター自身のキー操作が必要です。
AI コーディングアシスタントで修正しますか? このガイドを Markdown で取得、または全ガイドをコーディングエージェントで使う。
修正の確認
マウスを使わない4つの確認
確認方法: キーボード、スクリーンリーダー、強制配色、ズーム
OS、ブラウザーと支援技術のバージョン、ビルド、実施日、各手順の実際の結果を記録してください。表、その部品、ライブラリーを変更した際は、再度確認します。
制限
このサンプルは、どのアプリケーションでも同じ結果になることを保証するものではありません。グリッドのスクリプトは最小限の例で、入力で始まる編集、Page UpとPage Down、選択、コピーと貼り付け、必要なときの行の読み込みがありません。列での並べ替えは別の問題で、並べ替えた列とその向きを伝えるトピックで扱います。同じTabの移動先1つのパターンをサイドバーで使う例は、キーボードで開けず、階層も伝わらないツリービューにあります。データグリッドの部品は、ライブラリーによって作りがさまざまです。使う前に、すべてのセルに届き、すべてのキーを扱っているかを確かめます。対象とする支援技術で、一連の操作全体をテストしてください。
よくある問題を再現した学習用のサンプルです。実在のクライアントの診断結果ではありません。コードは出発点となる実装例のため、実際の製品でも確認してください。ここでは支援技術でのテスト結果は報告していません。
コードを更新しても、使いやすさを保つために
ガイドの内容を、コーディングエージェントやCIで使える開発ルールにまとめます。更新後は、キーボードとスクリーンリーダーで再テストし、問題が再発していないか確認します。