APG Patterns
ๆ—ฅๆœฌ่ชž
ๆ—ฅๆœฌ่ชž
๐Ÿ“Š

Grid and Table Properties

To fully convey the structure and relationships in a grid or table, authors need to ensure ARIA row and column properties are correctly set.

This practice is documented in detail atGrid and Table Properties - WAI-ARIA APG. Below we provide additional context specific to our pattern implementations.

Overview

Grids and tables are used to present structured data. For screen reader users to navigate and understand this data, proper ARIA properties must convey the structureโ€”row and column counts, headers, and cell relationships. This practice explains how to set these properties correctly.

When to Use Table vs Grid

Pattern Use When Keyboard Behavior
table Static, read-only data Standard tab navigation
grid Interactive cells (editable, actionable) Arrow key navigation between cells
treegrid Hierarchical data with expandable rows Arrow keys + expand/collapse

Essential ARIA Properties

Row and Column Counts

When using virtual scrolling or showing partial data:

<div role="grid" aria-rowcount="1000" aria-colcount="5">
<div role="row" aria-rowindex="10">
  <div role="gridcell" aria-colindex="1">...</div>
</div>
</div>
Only rows 10โ€“20 are rendered in the DOM. aria-rowcount and aria-rowindex communicate the full data set size and position to screen readers.
Property Purpose
aria-rowcount Total number of rows (including hidden)
aria-colcount Total number of columns (including hidden)
aria-rowindex Current rowโ€™s position (1-based)
aria-colindex Current cellโ€™s column position (1-based)

Spanning Cells

For cells that span multiple rows or columns:

<div role="gridcell" aria-colspan="2">Spans 2 columns</div>
<div role="gridcell" aria-rowspan="3">Spans 3 rows</div>

Headers

Use columnheader and rowheader roles:

<div role="grid">
  <div role="row">
    <div role="columnheader">Name</div>
    <div role="columnheader">Email</div>
  </div>
  <div role="row">
    <div role="rowheader">John Doe</div>
    <div role="gridcell">john@example.com</div>
  </div>
</div>

Native HTML Table Enhancement

When using native <table> elements, most properties are implicit:

<table>
<thead>
  <tr>
    <th scope="col">Product</th>
    <th scope="col">Price</th>
  </tr>
</thead>
<tbody>
  <tr>
    <th scope="row">Widget</th>
    <td>$10.00</td>
  </tr>
</tbody>
</table>
th with scope="col" implicitly receives the columnheader role, th with scope="row" receives rowheader, and td receives cell.

Add ARIA only when needed (virtual scrolling, dynamic updates):

<table aria-rowcount="500">
  <tbody>
    <tr aria-rowindex="50">
      ...
    </tr>
  </tbody>
</table>

Implementation Tips

Sort State

Indicate sortable columns and current sort:

<div role="columnheader" aria-sort="ascending">Name โ†‘</div>
<div role="columnheader" aria-sort="none">Email</div>
Value Meaning
ascending Sorted A-Z, low to high
descending Sorted Z-A, high to low
none Sortable but not currently sorted
other Sorted by other algorithm

Read-only and Disabled States

<div role="gridcell" aria-readonly="true">Fixed value</div>
Read-only cell โ€” value cannot be edited.
<div role="gridcell" aria-disabled="true">Unavailable</div>
Disabled cell โ€” interaction is not available.

Selection State

For grids with selectable rows or cells:

<div role="grid" aria-multiselectable="true">
  <div role="row" aria-selected="true">Selected row</div>
  <div role="row" aria-selected="false">Not selected</div>
</div>

Common Pitfalls

Missing Row/Column Indices

When using virtual scrolling, always provide indices so screen readers know the cellโ€™s position:

<div role="row">...</div>
Bad: No position information โ€” screen readers cannot determine where this row is in the data set.
<div role="row" aria-rowindex="50">...</div>
Good: Clear position โ€” screen readers can announce "row 50".

Incorrect Scope in Native Tables

<th>Name</th>
Bad: Missing scope โ€” screen readers may not correctly associate the header with its column or row.
<th scope="col">Name</th>
<th scope="row">John</th>
Good: Explicit scope clarifies the header's direction (column or row).

Using Grid for Non-interactive Tables

If users donโ€™t need to interact with individual cells, use table role instead of grid to avoid unnecessary complexity in keyboard navigation.

Resources