# キーボードで選べず、読み上げもされない座席表

Source: https://easeweb.dev/learn/seat-maps
Topics: 複雑なウィジェット > 座席表で、キーボードで座席を選べ、列と番号が伝わる (WCAG 1.4.1, 2.1.1, 4.1.2)
The fix: 各座席を、座席の形に描いたHTML標準のチェックボックスにし、列、番号、料金で名前を付けて、予約済みの座席は無効にします。大きな会場では、座席をグリッドに並べてTabの移動先を1つにし、矢印キーで移動できるようにします。各座席は aria-pressed 付きのボタンにし、予約済みには aria-disabled を付けます。どちらの方法でも、色に加えて、選択中の座席にはチェック、予約済みの座席には×を描きます。
Test with: keyboard, screen reader, colour vision, forced colors
References: https://www.w3.org/WAI/WCAG22/Understanding/use-of-color.html https://www.w3.org/WAI/WCAG22/Understanding/keyboard.html https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html https://www.w3.org/WAI/ARIA/apg/patterns/grid/ https://www.w3.org/WAI/ARIA/apg/patterns/grid/examples/layout-grids/ https://www.w3.org/WAI/ARIA/apg/patterns/button/ https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input/checkbox

よくある問題を再現した学習用のサンプルです。実在のクライアントの診断結果ではありません。コードは出発点となる実装例のため、実際の製品でも確認してください。ここでは支援技術でのテスト結果は報告していません。

## 場面

ある映画館の予約ページでは、1回の上映の座席を座席表で示しています。「スクリーン」と書かれた帯の下に、AからDの4列、各列6席が並びます。空席は緑、予約済みは赤で、空席をクリックすると選択中を示す青に変わります。座席表の上には3つの色を説明する凡例があり、A〜B列は$10、C〜D列は$14と書かれています。マウスなら、C列で隣り合う2席を選ぶのは2回のクリックで済みます。

マウスを使わずにTabキーを押すと、フォーカスはページの先頭からすべての座席を飛ばし、「次へ」へ進みます。座席は一つも選べません。座席が空のdivなので、スクリーンリーダーでページを読んでも、「スクリーン」に続いて列の文字「A」「B」「C」「D」が読まれるだけで、その間には何も聞こえません。赤と緑が同じ色に見える人には、どの座席も同じように見えます。C列の空席の1、2、5、6番は、予約済みの3、4番と同じ色合いです。

そこで、チームは各座席をボタンにします。Tabは各座席に止まるようになりますが、スクリーンリーダーには24席のどれも「ボタン」としか聞こえず、どの列の何番か、料金はいくらかは伝わりません。予約済みの座席もほかと同じボタンで、押しても何も起きず、何も読まれません。座席を選んでも色が変わるだけで、スクリーンリーダーは前と同じように「ボタン」と読みます。

## 確認できる問題

達成基準2.1.1「キーボード」は、マウスでできることはすべてキーボードでもできることを求めています。この座席表で座席を選ぶ方法は、divのクリックだけです。divはフォーカスを受けず、キーにも反応しないため、ポインターなしでは座席を選べません。

達成基準4.1.2「名前（name）・役割（role）及び値（value）」は、どのコントロールにも名前と役割があり、状態が支援技術に伝わることを求めています。座席もコントロールです。選択でき、空席・予約済み・選択中のいずれかの状態を持ちます。divには役割も名前もないため、スクリーンリーダーは何もないかのように通り過ぎます。ボタンには役割があっても、テキストも名前もありません。どちらの場合も、状態を表すのは `taken` と `chosen` というクラスと、その色だけです。ブラウザーが支援技術に伝える情報は要素と属性から作られ、クラス名や背景色からは作られないため、状態は伝わりません。

達成基準1.4.1「色の使用」は、色だけで情報を示さないことを求めています。どの座席も、空席か、予約済みか、選択中かを塗りの色だけで示しており、印も形も言葉もありません。座席表の上の凡例には言葉がありますが、凡例と座席を結び付けるのは色だけです。緑、赤、青の明るさの差は1.46:1以内、赤と青では1.11:1しかないため、色味を除くとほとんど同じに見えます。また、色覚特性の中でも多い、赤と緑を見分けにくいタイプの人には、緑と赤がほぼ同じ色に見えます。

## 利用者への影響

映画館、劇場、コンサートホール、スタジアム、航空会社、鉄道は、チケットを買う途中にこのような座席表を置いています。座席表でしか選べない場合、選べない座席は買えないチケットと同じです。座席表の横に「おまかせで選ぶ」ボタンを置くサイトもありますが、選ぶのはサイトの側です。通路側の席も、一緒に来た人の隣も、いつも座る列も選べません。

## 自動チェックとAIの限界

空のdivには役割がないため、1つ目の座席表ではaxeが確認するものはありません。axeのコントラストのルールが見るのはテキストで、塗りの色が何を意味するかは見ません。2つ目の座席表の空のボタンは、テキストも名前もないため、axeが `button-name` として報告します。ただし、そのボタンが座席であることや、クラスでは予約済みや選択中と示しているのにマークアップでは何も伝えていないことは分かりません。

この診断ツールは、`seat` やそれに類するよくあるクラス名を手がかりに、div、span、SVGの図形で作られた座席を探します。Tabが座席にも座席表の中のほかの要素にも届かない座席表を報告し、Tabは届いても、クラスでは予約済みや選択中と示しているのに `disabled`、`aria-disabled`、`checked`、`aria-pressed`、`aria-checked`、`aria-selected` のどれも持たない座席も報告します。Tabが届く座席表は、人が確認する項目として渡し、各座席の名前が列と番号を伝えているか、色がなくても状態が分かるかを確かめてもらいます。idやクラス名で座席表と分かる `canvas` も、同じように確認に回します。それと分かるクラス名のない図形で作った座席表は見つからず、座席の名前が聞く人にとって分かりやすいかは、どの確認でも分かりません。

広く写されている座席予約のチュートリアルは、1つ目の作り方、つまり1席ごとに空のdivを置き、座席表全体に1つのクリック処理を付ける形で作られています。AIアシスタントに座席表をアクセシブルにするよう頼むと、divをボタンに変えただけで終わり、2つ目の座席表になることがあります。すべての座席に `aria-label="座席"` のような同じ名前を付けることもあります。

## 修正前：クリックに反応するdivの座席

```html
<h2>座席を選択</h2>
<p>A〜B列 $10・C〜D列 $14</p>
<ul class="legend">
  <li>
    <span class="key"></span>
    空席
  </li>
  <li>
    <span class="key chosen"></span>
    選択中
  </li>
  <li>
    <span class="key taken"></span>
    予約済み
  </li>
</ul>
<div class="seat-map">
  <div class="screen">スクリーン</div>
  …
  <div class="row">
    <span class="row-name">C</span>
    <div class="seat"></div>
    <div class="seat"></div>
    <div class="seat taken"></div>
    <div class="seat taken"></div>
    <div class="seat"></div>
    <div class="seat"></div>
  </div>
  …
</div>
<button type="button" class="continue">次へ</button>
```

```css
.seat-map .row {
  display: flex;
  gap: 8px;
}
/* 座席と、凡例の見本 */
.seat,
.key {
  display: inline-block;
  inline-size: 28px;
  block-size: 24px;
  padding: 0;
  border: 2px solid #2f9e44;
  border-radius: 8px 8px 3px 3px;
  background: #2f9e44;
}
.seat {
  cursor: pointer;
}
.seat.chosen,
.key.chosen {
  border-color: #1971c2;
  background: #1971c2;
}
.seat.taken,
.key.taken {
  border-color: #e03131;
  background: #e03131;
}
```

```js
document.querySelector('.seat-map').addEventListener('click', (event) => {
  const seat = event.target.closest('.seat');
  if (seat && !seat.classList.contains('taken')) {
    seat.classList.toggle('chosen');
  }
});
```

- 座席は `click` にしか反応せず、フォーカスも受けないため、Tabは座席表を飛ばし、キーボードではどの座席も選べません。
- 座席は空のdivのため、スクリーンリーダーは列の文字を読むだけで、その間には何も読みません。
- 空席・予約済み・選択中の区別はクラスだけで、支援技術には伝わりません。
- 画面上でも、違いは塗りの色だけです。

## 修正前：同じ座席をボタンにしただけ

```html
<h2>座席を選択</h2>
<p>A〜B列 $10・C〜D列 $14</p>
<ul class="legend">
  <li>
    <span class="key"></span>
    空席
  </li>
  <li>
    <span class="key chosen"></span>
    選択中
  </li>
  <li>
    <span class="key taken"></span>
    予約済み
  </li>
</ul>
<div class="seat-map">
  <div class="screen">スクリーン</div>
  …
  <div class="row">
    <span class="row-name">C</span>
    <button type="button" class="seat"></button>
    <button type="button" class="seat"></button>
    <button type="button" class="seat taken"></button>
    <button type="button" class="seat taken"></button>
    <button type="button" class="seat"></button>
    <button type="button" class="seat"></button>
  </div>
  …
</div>
<button type="button" class="continue">次へ</button>
```

```css
.seat-map .row {
  display: flex;
  gap: 8px;
}
/* 座席と、凡例の見本 */
.seat,
.key {
  display: inline-block;
  inline-size: 28px;
  block-size: 24px;
  padding: 0;
  border: 2px solid #2f9e44;
  border-radius: 8px 8px 3px 3px;
  background: #2f9e44;
}
.seat {
  cursor: pointer;
}
.seat.chosen,
.key.chosen {
  border-color: #1971c2;
  background: #1971c2;
}
.seat.taken,
.key.taken {
  border-color: #e03131;
  background: #e03131;
}
```

```js
document.querySelector('.seat-map').addEventListener('click', (event) => {
  const seat = event.target.closest('.seat');
  if (seat && !seat.classList.contains('taken')) {
    seat.classList.toggle('chosen');
  }
});
```

CSSとスクリプトは上と同じで、変えたのは要素だけです。TabキーとEnterキーは使えるようになりましたが、次の問題が残ります。

- どの座席も「ボタン」としか読まれず、列も番号も料金も伝わりません。
- 予約済みの座席もほかと同じボタンで、押しても何も起きず、何も読まれません。
- 座席を選んでも変わるのはクラスと色だけのため、読み上げは選ぶ前と同じです。
- 画面上の違いは、今も塗りの色だけです。

## 修正後：座席ごとのチェックボックス

```html
<h2>座席を選択</h2>
<p>A〜B列 $10・C〜D列 $14</p>
<ul class="legend">
  <li>
    <span class="key"></span>
    空席
  </li>
  <li>
    <span class="key chosen"></span>
    選択中
  </li>
  <li>
    <span class="key taken"></span>
    予約済み
  </li>
</ul>
<div class="seat-map">
  <div class="screen">スクリーン</div>
  …
  <div class="row">
    <span class="row-name">C</span>
    <label class="seat">
      <input type="checkbox" name="seats" value="C1">
      <span class="visually-hidden">C列1番、$14</span>
    </label>
    <label class="seat">
      <input type="checkbox" name="seats" value="C2">
      <span class="visually-hidden">C列2番、$14</span>
    </label>
    <label class="seat">
      <input type="checkbox" name="seats" value="C3" disabled>
      <span class="visually-hidden">C列3番、$14</span>
    </label>
    <label class="seat">
      <input type="checkbox" name="seats" value="C4" disabled>
      <span class="visually-hidden">C列4番、$14</span>
    </label>
    <label class="seat">
      <input type="checkbox" name="seats" value="C5">
      <span class="visually-hidden">C列5番、$14</span>
    </label>
    <label class="seat">
      <input type="checkbox" name="seats" value="C6">
      <span class="visually-hidden">C列6番、$14</span>
    </label>
  </div>
  …
</div>
<button type="button" class="continue">次へ</button>
```

```css
.seat-map .row {
  display: flex;
  gap: 8px;
}
/* チェックボックスに描いた座席と、凡例の見本 */
.seat input,
.key {
  display: inline-grid;
  place-content: center;
  inline-size: 28px;
  block-size: 24px;
  padding: 0;
  border: 2px solid #2f9e44;
  border-radius: 8px 8px 3px 3px;
  background: #2f9e44;
}
.seat input {
  appearance: none;
  margin: 0;
  cursor: pointer;
}
/* 選択中：チェックボックス自身の状態から、塗りとチェックを描く */
.seat input:checked,
.key.chosen {
  border-color: #1971c2;
  background: #1971c2;
}
.seat input:checked::before,
.key.chosen::before {
  content: "";
  inline-size: 6px;
  block-size: 11px;
  border: solid #fff;
  border-width: 0 3px 3px 0;
  rotate: 45deg;
}
/* 予約済み：塗りと、コントラスト テーマでも残る2本の枠線の× */
.seat input:disabled,
.key.taken {
  border-color: #e03131;
  background: #e03131;
  cursor: default;
}
.seat input:disabled::before,
.seat input:disabled::after,
.key.taken::before,
.key.taken::after {
  content: "";
  grid-area: 1 / 1;
  block-size: 14px;
  border-left: 3px solid #fff;
  rotate: 45deg;
}
.seat input:disabled::after,
.key.taken::after {
  rotate: -45deg;
}
.seat input:focus-visible {
  outline: 3px solid #1a1a1a;
  outline-offset: 2px;
}
```

1. 各座席をlabelの中のチェックボックスにし、列、番号、料金を伝える隠したテキストを付けます。Tabが止まり、Spaceキーで選べ、スクリーンリーダーは「C列5番、$14、チェックボックス、チェックなし」と読み上げます。
2. 予約済みの座席のチェックボックスは無効にします。Tabキーでは飛ばされますが、スクリーンリーダーで座席表を読み進めると「使用不可」や「淡色表示」と読み上げられます。
3. `appearance: none` でチェックボックスそのものに座席を描き、色は `:checked` と `:disabled` から付けます。見た目と読み上げが食い違うことはありません。
4. 座席にも凡例にも、選択中にはチェック、予約済みには×を描きます。枠線で描くため、Windowsのコントラスト テーマでも消えません。
5. スクリプトは削除します。状態はチェックボックス自身が持ち、フォームの中に置けば、選んだ座席の値はコードなしで送信されます。

これで二つの修正前の例はどちらも直り、達成基準1.4.1、2.1.1、4.1.2を満たします。空席はすべてTabの移動先になり、列ごとに読む順にたどれます。

## 修正後：矢印キーで動く座席のグリッド

```html
<h2>座席を選択</h2>
<p>A〜B列 $10・C〜D列 $14</p>
<ul class="legend">
  <li>
    <span class="key"></span>
    空席
  </li>
  <li>
    <span class="key chosen"></span>
    選択中
  </li>
  <li>
    <span class="key taken"></span>
    予約済み
  </li>
</ul>
<div class="seat-map">
  <div class="screen">スクリーン</div>
  <table role="grid" aria-label="座席">
    …
    <tr>
      <th scope="row">C</th>
      <td>
        <button type="button" class="seat" aria-pressed="false">
          <span class="visually-hidden">C列1番、$14</span>
        </button>
      </td>
      <td>
        <button type="button" class="seat" aria-pressed="false">
          <span class="visually-hidden">C列2番、$14</span>
        </button>
      </td>
      <td>
        <button type="button" class="seat" aria-pressed="false" aria-disabled="true">
          <span class="visually-hidden">C列3番、$14</span>
        </button>
      </td>
      <td>
        <button type="button" class="seat" aria-pressed="false" aria-disabled="true">
          <span class="visually-hidden">C列4番、$14</span>
        </button>
      </td>
      <td>
        <button type="button" class="seat" aria-pressed="false">
          <span class="visually-hidden">C列5番、$14</span>
        </button>
      </td>
      <td>
        <button type="button" class="seat" aria-pressed="false">
          <span class="visually-hidden">C列6番、$14</span>
        </button>
      </td>
    </tr>
    …
  </table>
</div>
<button type="button" class="continue">次へ</button>
```

```css
.seat-map table {
  border-spacing: 8px 4px;
}
.seat-map th {
  font-weight: normal;
}
/* 座席と、凡例の見本 */
.seat,
.key {
  display: inline-grid;
  place-content: center;
  inline-size: 28px;
  block-size: 24px;
  padding: 0;
  border: 2px solid #2f9e44;
  border-radius: 8px 8px 3px 3px;
  background: #2f9e44;
}
.seat {
  cursor: pointer;
}
/* 選択中：aria-pressed から塗りとチェックを描く */
.seat[aria-pressed="true"],
.key.chosen {
  border-color: #1971c2;
  background: #1971c2;
}
.seat[aria-pressed="true"]::before,
.key.chosen::before {
  content: "";
  inline-size: 6px;
  block-size: 11px;
  border: solid #fff;
  border-width: 0 3px 3px 0;
  rotate: 45deg;
}
/* 予約済み：aria-disabled から塗りと2本の枠線の×を描く */
.seat[aria-disabled="true"],
.key.taken {
  border-color: #e03131;
  background: #e03131;
  cursor: default;
}
.seat[aria-disabled="true"]::before,
.seat[aria-disabled="true"]::after,
.key.taken::before,
.key.taken::after {
  content: "";
  grid-area: 1 / 1;
  block-size: 14px;
  border-left: 3px solid #fff;
  rotate: 45deg;
}
.seat[aria-disabled="true"]::after,
.key.taken::after {
  rotate: -45deg;
}
.seat:focus-visible {
  outline: 3px solid #1a1a1a;
  outline-offset: 2px;
}
```

```js
const grid = document.querySelector('.seat-map [role="grid"]');
const rows = [...grid.rows].map((row) => [...row.querySelectorAll('.seat')]);
const seats = rows.flat();
// Tabの移動先は1つ：最後に移動した座席で、最初は先頭の座席。
function moveTo(seat, focus = true) {
  for (const other of seats) other.tabIndex = other === seat ? 0 : -1;
  if (focus) seat.focus();
}
moveTo(seats[0], false);
grid.addEventListener('click', (event) => {
  const seat = event.target.closest('.seat');
  if (!seat) return;
  moveTo(seat);
  // 予約済みの座席もグリッドに残すので矢印キーで通れるが、選ぶことはできない。
  if (seat.getAttribute('aria-disabled') === 'true') return;
  const chosen = seat.getAttribute('aria-pressed') === 'true';
  seat.setAttribute('aria-pressed', String(!chosen));
});
grid.addEventListener('keydown', (event) => {
  const seat = event.target.closest('.seat');
  if (!seat) return;
  const r = rows.findIndex((row) => row.includes(seat));
  const c = rows[r].indexOf(seat);
  const next = {
    ArrowRight: rows[r][c + 1],
    ArrowLeft: rows[r][c - 1],
    ArrowDown: rows[r + 1]?.[c],
    ArrowUp: rows[r - 1]?.[c],
    Home: rows[r][0],
    End: rows[r].at(-1)
  };
  if (!(event.key in next)) return;
  event.preventDefault();
  if (next[event.key]) moveTo(next[event.key]);
});
```

1. 列を `role="grid"` と名前を付けたtableに並べ、各列の文字を行見出しにします。
2. 各座席をボタンにし、列、番号、料金を伝える隠したテキストと、選択中かどうかを示す `aria-pressed` を付けます。EnterキーとSpaceキーで押せ、スクリーンリーダーは「C列5番、$14、トグルボタン、押されていない」と読み上げます。
3. 予約済みの座席には `aria-disabled="true"` を付け、スクリプトで選べないようにします。座席はグリッドに残るので、実際の場内と同じ並びのまま、矢印キーで予約済みの座席も通って移動でき、スクリーンリーダーは「使用不可」や「淡色表示」と伝えます。
4. グリッドのTabの移動先を1つにします。`moveTo()` は、最後に移動した座席に `tabindex="0"`、ほかの座席に `-1` を付けるため、Tabを1回押すと座席表に入り、もう1回で出ます。
5. ARIA Authoring Practicesのグリッドのとおりに、矢印キーで移動できるようにします。左右の矢印キーで列の中を、上下の矢印キーで隣の列の同じ番号の座席へ、HomeとEndで列の両端へ移動します。
6. 色、チェック、×は、クラスではなく `aria-pressed` と `aria-disabled` から描きます。

これで二つの修正前の例はどちらも直り、達成基準1.4.1、2.1.1、4.1.2を満たします。ただし、それはスクリプトが正しく動く場合に限ります。どのキーも動き、Tabの移動先がフォーカスに付いていく必要があります。グリッドの扱いはスクリーンリーダーによって異なり、別の列へ移ったときに行見出しが読まれるとは限りません。各座席の名前に列を入れるのはそのためです。対応するスクリーンリーダーそれぞれで確認してください。

## どの方法を選ぶか

どちらも基準を満たします。この映画館のスクリーンのような小さな会場には、チェックボックスを使ってください。スクリプトがなく、壊れるところもありません。フォームの中に置けば、JavaScriptを読み込む前でも選んだ座席が送られます。利用者は、ほかのフォームと同じように、Tabで列ごとに空席を順に移動します。劇場、航空機、スタジアムの一区画のような大きな会場には、グリッドを使ってください。何百もの座席をTabで移動していては何分もかかり、利用者は後ろの列へそのまま移れることを期待します。その代わり、すべてのキーを扱うスクリプトが必要で、グリッドへの対応はスクリーンリーダーによって違うため、それぞれでの確認も必要です。

## 実装の判断

座席の名前は、フォーカスが移るたびにそれだけで読まれます。「5番」や「5」だけでは、フォーカスが1つ下の列へ移ったときに足りないため、どの名前にも列を入れます。選ぶときに大事な情報は、「通路側」「見えにくい席」「車いす用スペース」「足元が広い席」のように番号の後に加え、料金は座席によって違う場合にだけ入れます。車いす用スペースのように、座席ではない場所も同じで、その場所が何かを名前にします。

2つ目の修正前のように座席がすでにボタンなら、いちばん小さな変更はボタンのまま直すことです。各座席に隠したテキストと `aria-pressed` を付け、予約済みの座席に `disabled` を付けて、見た目をそれらの属性から描きます。チェックボックスの方法をボタンに置き換えたもので、空席はどれもTabの移動先になり、スクリーンリーダーは「チェックボックス」ではなく「トグルボタン」と読みます。

大きな会場では、まず区画の地図を示し、選んだ区画の座席を次に示すことがよくあります。各区画にも座席と同じ扱いが必要です。キーボードで届くコントロールにし、区画名と料金帯で名前を付け、「売り切れ」は灰色だけでなく言葉でも伝えます。拡大やドラッグで動かせる座席表には、どちらの操作にもボタンが必要です。[ボタンで代われないピンチ・ドラッグ・傾きのジェスチャー](/ja/learn/gestures)を参照してください。`canvas` に描いた座席表は、スクリーンリーダーに何も伝えません。その上に座席を要素として描くか、横に空席の一覧を列ごとに置きます。

座席そのものにも、周りとの3:1のコントラストが必要です。[コントラストが足りない入力欄・アイコン・フォーカス枠](/ja/learn/non-text-contrast)を参照してください。この例では、緑は白に対して3.45:1で、赤と青はそれより高くなっています。印は赤と青の上の白で、4.51:1と5.02:1です。チェックと×は枠線で描いているため、塗りの色を取り除くWindowsのコントラスト テーマでも見えます。背景のグラデーションで描いた印は、そこで消えてしまいます。注文のステータスのように、ほかの場所で色だけが状態を示している場合は、[色だけで区別されるステータス表示](/ja/learn/status-colours)を参照してください。

多くのサイトは、「次へ」の横に選んだ座席と合計を「C列5番・6番、$28」のように示します。この行は、支払いの前に誰もが選んだ内容を確かめるのに役立ちます。ただし、読み上げで知らせない限り、スクリーンリーダーにはそこまで読み進めたときにしか聞こえません。[バッジしか変わらない「カートに入れる」ボタン](/ja/learn/action-confirmations)を参照してください。

## 修正の確認

1. マウスを使わずに、Tabで座席表に入り、C列で隣り合う2つの空席を選んで「次へ」へ進む。グリッドでは、矢印キーで列の間を移動し、Tabを1回押すと座席表を出ることを確かめる。
2. スクリーンリーダーで座席を順に移動する。どの座席も、列、番号、料金が読み上げられる。
3. 座席を選んで聞く。チェックされた、または押されたと読み上げられる。予約済みの座席へ移動すると、使用不可と読み上げられ、押しても何も変わらない。
4. 座席表をグレースケールと、赤と緑の色覚シミュレーションで見る。チェックと×で、空席・予約済み・選択中を見分けられる。
5. Windowsのコントラスト テーマをオンにする。すべての座席の輪郭、チェック、×、フォーカスの枠が見える。

OS、ブラウザーと支援技術のバージョン、ビルド、実施日、各手順の実際の結果を記録してください。座席表、その部品、ライブラリーを変更した際は、再度確認します。

## 制限

このサンプルは、どのアプリケーションでも同じ結果になることを保証するものではありません。グリッドのスクリプトは最小限の例で、Page UpとPage Downや、列の文字で列へ移動する方法はありません。大きな会場では、それらが必要になることがあります。NVDAでは、グリッドのセルの中のリンクへ矢印キーで移動するとフォーカスモードが解除され、矢印キーが読み上げのカーソルを動かすようになると報告されています。このボタンでも同じことが起きると、NVDAの利用者はInsert + Spaceで矢印キーを取り戻す必要があるため、特にNVDAでグリッドを確かめてください。映画館の一覧のように、会場を街の地図に示すものは、[場所が地図でしか分からない店舗マップ](/ja/learn/interactive-maps)で扱っています。セルを選ぶのではなく編集するグリッドは[キーボードでセルを移動・編集できないデータグリッド](/ja/learn/editable-grids)で、フォームの中の手作りのチェックボックスは[キーボードでもスクリーンリーダーでも使えないカスタムチェックボックス](/ja/learn/custom-checkboxes)で扱っています。上映を選んでから支払うまでの一連の操作全体を、対象とする支援技術でテストしてください。

## スクリーンリーダーでの動き（EaseWebが実機で確認）

`aria-pressed`: ボタンを、オンとオフを切り替えるトグルにします。

| スクリーンリーダー | ブラウザーとOS | 押す | Tabで移動 |
| --- | --- | --- | --- |
| NVDA | Chrome, Windows 11 | 未確認 | 未確認 |
| JAWS | Chrome, Windows 11 | 未確認 | 未確認 |
| VoiceOver | Safari, iOS | 未確認 | 未確認 |
| TalkBack | Chrome, Android | 未確認 | 未確認 |

`role="grid", a button in each cell`: 座席表の座席のように、各セルにボタンがあり、Tabの移動先が1つのグリッドです。ページのスクリプトが矢印キーで移動させます。ARIAではグリッドはウィジェットで、その中の矢印キーはスクリーンリーダーではなくページが扱うものとされています。

| スクリーンリーダー | ブラウザーとOS | セル間の矢印キー |
| --- | --- | --- |
| NVDA | Chrome, Windows 11 | 未確認 |
| JAWS | Chrome, Windows 11 | 未確認 |
| VoiceOver | Safari, iOS | 未確認 |
| TalkBack | Chrome, Android | 未確認 |

https://easeweb.dev/ja/learn/screen-reader-support

---

このガイドをコーディングエージェントで使う: MCP https://easeweb.dev/mcp（設定は https://easeweb.dev/ja/services/developers）
