ARIA:網格角色

網格角色用於包含一個或多個行單元格的小部件。每個單元格的位置都很重要,可以使用鍵盤輸入來獲得焦點。

描述

grid 角色是一個複合小部件,包含一個或多個行,每行包含一個或多個單元格,其中網格中的一些或所有單元格都可以透過使用二維導航方法(如方向箭頭鍵)獲得焦點。

html
<table role="grid" aria-labelledby="id-select-your-seat">
  <caption id="id-select-your-seat">
    Select your seat
  </caption>
  <tbody role="presentation">
    <tr role="presentation">
      <td></td>
      <th>Row A</th>
      <th>Row B</th>
    </tr>
    <tr>
      <th scope="row">Aisle 1</th>
      <td tabindex="0">
        <button id="1a" tabindex="-1">1A</button>
      </td>
      <td tabindex="-1">
        <button id="1b" tabindex="-1">1B</button>
      </td>
      <!-- More Columns -->
    </tr>
    <tr>
      <th scope="row">Aisle 2</th>
      <td tabindex="-1">
        <button id="2a" tabindex="-1">2A</button>
      </td>
      <td tabindex="-1">
        <button id="2b" tabindex="-1">2B</button>
      </td>
      <!-- More Columns -->
    </tr>
  </tbody>
</table>

網格小部件包含一個或多個行,每行包含一個或多個包含主題相關互動式內容的單元格。雖然它不暗示特定的視覺呈現,但它暗示了元素之間的關係。用途分為兩類:呈現表格資訊(資料網格)和分組其他小部件(佈局網格)。儘管資料網格和佈局網格都使用相同的 ARIA 角色、狀態和屬性,但它們的內容和目的之間的差異會顯現出在鍵盤互動設計中需要考慮的重要因素。有關更多詳細資訊,請參見 ARIA 作者實踐指南

單元格元素具有 gridcell 角色,除非它們是行或列標題。然後,這些元素分別為 rowheadercolumnheader。單元格元素需要由具有 row 角色的元素擁有。行可以使用 rowgroups 分組。

如果網格用作互動式小部件,則需要實現 鍵盤互動

關聯的 ARIA 角色、狀態和屬性

角色

treegrid(子類)

如果網格的列可以展開或摺疊,則可以使用樹網格。

row

網格內的行。

rowgroup

包含一個或多個 row 的組。

狀態和屬性

aria-level

指示網格在其他結構中的層次結構級別。

aria-multiselectable

如果 aria-multiselectable 設定為 true,則可以選擇網格中的多個專案。預設值為 false

aria-readonly

如果使用者可以導航網格但不能更改網格的值或多個值,則應將 aria-readonly 設定為 true。預設值為 false

注意:對於許多用例,HTML <table> 元素就足夠了,因為它和各種表格元素已經包含許多 ARIA 角色。

鍵盤互動

當鍵盤使用者遇到網格時,他們使用 leftrighttopdown 鍵來導航行和列。要啟用互動式元件,他們將使用 returnspace 鍵。

操作
將焦點向右移動一個單元格。可選地(佈局網格),如果焦點位於行中最右側的單元格,焦點可能會移動到下一行的第一個單元格。如果焦點位於網格的最後一個單元格,焦點不會移動。
將焦點向左移動一個單元格。可選地(佈局網格),如果焦點位於行中最左側的單元格,焦點可能會移動到上一行的最後一個單元格。如果焦點位於網格的第一個單元格,焦點不會移動。
將焦點向下移動一個單元格。可選地(佈局網格),如果焦點位於列中最底部的單元格,焦點可能會移動到下一列的最頂部單元格。如果焦點位於網格的最後一個單元格,焦點不會移動。
將焦點向上移動一個單元格。可選地(佈局網格),如果焦點位於列的最頂部的單元格,焦點可能會移動到上一列的最底部的單元格。如果焦點位於網格的第一個單元格,焦點不會移動。
Page Down 將焦點向下移動作者確定的行數,通常滾動,以便當前可見的行集中最底部的行成為第一個可見行之一。如果焦點位於網格的最後一行,焦點不會移動。
Page Up 將焦點向上移動作者確定的行數,通常滾動,以便當前可見的行集中最頂部的行成為最後一個可見行之一。如果焦點位於網格的第一行,焦點不會移動。
Home 將焦點移動到包含焦點的行的第一個單元格。
End 將焦點移動到包含焦點的行的最後一個單元格。
ctrl + Home 將焦點移動到第一行的第一個單元格。
ctrl + End 將焦點移動到最後一行最後一個單元格。

如果可以選中單元格、行或列,則通常使用以下鍵組合

鍵組合 操作
ctrl + Space 選中包含焦點的列。
shift + Space 選中包含焦點的行。如果網格包含一列具有複選框以選中行的複選框,則即使焦點不在複選框上,也可以使用此鍵組合來選中該複選框。
ctrl + A 選擇所有單元格。
shift + 將選擇範圍擴充套件到右側一個單元格。
shift + 將選擇範圍擴充套件到左側一個單元格。
shift + 將選擇範圍擴充套件到下方一個單元格。
shift + 將選擇範圍擴充套件到上方一個單元格。

示例

日曆示例

HTML

html
<table role="grid" aria-labelledby="calendarheader">
  <caption id="calendarheader">
    September 2018
  </caption>
  <thead role="rowgroup">
    <tr role="row">
      <td></td>
      <th role="columnheader" aria-label="Sunday">S</th>
      <th role="columnheader" aria-label="Monday">M</th>
      <th role="columnheader" aria-label="Tuesday">T</th>
      <th role="columnheader" aria-label="Wednesday">W</th>
      <th role="columnheader" aria-label="Thursday">T</th>
      <th role="columnheader" aria-label="Friday">F</th>
      <th role="columnheader" aria-label="Saturday">S</th>
    </tr>
  </thead>
  <tbody role="rowgroup">
    <tr role="row">
      <th scope="row" role="rowheader">Week 1</th>
      <td>26</td>
      <td>27</td>
      <td>28</td>
      <td>29</td>
      <td>30</td>
      <td>31</td>
      <td role="gridcell" tabindex="-1">1</td>
    </tr>
    <tr role="row">
      <th scope="row" role="rowheader">Week 2</th>
      <td role="gridcell" tabindex="-1">2</td>
      <td role="gridcell" tabindex="-1">3</td>
      <td role="gridcell" tabindex="-1">4</td>
      <td role="gridcell" tabindex="-1">5</td>
      <td role="gridcell" tabindex="-1">6</td>
      <td role="gridcell" tabindex="-1">7</td>
      <td role="gridcell" tabindex="-1">8</td>
    </tr>
    <tr role="row">
      <th scope="row" role="rowheader">Week 3</th>
      <td role="gridcell" tabindex="-1">9</td>
      <td role="gridcell" tabindex="-1">10</td>
      <td role="gridcell" tabindex="-1">11</td>
      <td role="gridcell" tabindex="-1">12</td>
      <td role="gridcell" tabindex="-1">13</td>
      <td role="gridcell" tabindex="-1">14</td>
      <td role="gridcell" tabindex="-1">15</td>
    </tr>
    <tr role="row">
      <th scope="row" role="rowheader">Week 4</th>
      <td role="gridcell" tabindex="-1">16</td>
      <td role="gridcell" tabindex="-1">17</td>
      <td role="gridcell" tabindex="-1">18</td>
      <td role="gridcell" tabindex="-1">19</td>
      <td role="gridcell" tabindex="-1">20</td>
      <td role="gridcell" tabindex="-1">21</td>
      <td role="gridcell" tabindex="-1">22</td>
    </tr>
    <tr role="row">
      <th scope="row" role="rowheader">Week 5</th>
      <td role="gridcell" tabindex="-1">23</td>
      <td role="gridcell" tabindex="-1">24</td>
      <td role="gridcell" tabindex="-1">25</td>
      <td role="gridcell" tabindex="-1">26</td>
      <td role="gridcell" tabindex="-1">27</td>
      <td role="gridcell" tabindex="-1">28</td>
      <td role="gridcell" tabindex="-1">29</td>
    </tr>
    <tr role="row">
      <th scope="row" role="rowheader">Week 6</th>
      <td role="gridcell" tabindex="-1">30</td>
      <td>1</td>
      <td>2</td>
      <td>3</td>
      <td>4</td>
      <td>5</td>
      <td>6</td>
    </tr>
  </tbody>
</table>

CSS

css
table {
  margin: 0;
  border-collapse: collapse;
  font-variant-numeric: tabular-nums;
}

tbody th,
tbody td {
  padding: 5px;
}

tbody td {
  border: 1px solid #000;
  text-align: right;
  color: #767676;
}

tbody td[role="gridcell"] {
  color: #000;
}

tbody td[role="gridcell"]:hover,
tbody td[role="gridcell"]:focus {
  background-color: #f6f6f6;
  outline: 3px solid blue;
}

JavaScript

js
const selectables = document.querySelectorAll('table td[role="gridcell"]');

selectables[0].setAttribute("tabindex", 0);

const trs = document.querySelectorAll("table tbody tr");
let row = 0;
let col = 0;
let maxrow = trs.length - 1;
let maxcol = 0;

trs.forEach((gridrow) => {
  gridrow.querySelectorAll("td").forEach((el) => {
    el.dataset.row = row;
    el.dataset.col = col;
    col++;
  });
  if (col > maxcol) {
    maxcol = col - 1;
  }
  col = 0;
  row++;
});

function moveto(newrow, newcol) {
  const tgt = document.querySelector(
    `[data-row="${newrow}"][data-col="${newcol}"]`,
  );
  if (tgt?.getAttribute("role") === "gridcell") {
    document.querySelectorAll("[role=gridcell]").forEach((el) => {
      el.setAttribute("tabindex", "-1");
    });
    tgt.setAttribute("tabindex", "0");
    tgt.focus();
    return true;
  } else {
    return false;
  }
}

document.querySelector("table").addEventListener("keydown", (event) => {
  const col = parseInt(event.target.dataset.col, 10);
  const row = parseInt(event.target.dataset.row, 10);
  switch (event.key) {
    case "ArrowRight": {
      const newrow = col === 6 ? row + 1 : row;
      const newcol = col === 6 ? 0 : col + 1;
      moveto(newrow, newcol);
      break;
    }
    case "ArrowLeft": {
      const newrow = col === 0 ? row - 1 : row;
      const newcol = col === 0 ? 6 : col - 1;
      moveto(newrow, newcol);
      break;
    }
    case "ArrowDown":
      moveto(row + 1, col);
      break;
    case "ArrowUp":
      moveto(row - 1, col);
      break;
    case "Home": {
      if (event.ctrlKey) {
        let i = 0;
        let result;
        do {
          let j = 0;
          do {
            result = moveto(i, j);
            j++;
          } while (!result);
          i++;
        } while (!result);
      } else {
        moveto(row, 0);
      }
      break;
    }
    case "End": {
      if (event.ctrlKey) {
        let i = maxrow;
        let result;
        do {
          let j = maxcol;
          do {
            result = moveto(i, j);
            j--;
          } while (!result);
          i--;
        } while (!result);
      } else {
        moveto(
          row,
          document.querySelector(
            `[data-row="${event.target.dataset.row}"]:last-of-type`,
          ).dataset.col,
        );
      }
      break;
    }
    case "PageUp": {
      let i = 0;
      let result;
      do {
        result = moveto(i, col);
        i++;
      } while (!result);
      break;
    }
    case "PageDown": {
      let i = maxrow;
      let result;
      do {
        result = moveto(i, col);
        i--;
      } while (!result);
      break;
    }
    case "Enter": {
      console.log(event.target.textContent);
      break;
    }
  }
  event.preventDefault();
});

更多示例

無障礙問題

即使鍵盤使用已正確實現,一些使用者可能不知道他們必須使用箭頭鍵。確保可以透過使用網格角色來最佳地實現所需的功能和互動。

規範

規範
無障礙富網際網路應用 (WAI-ARIA)
# 網格

另請參閱