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 at Grid 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

PatternUse WhenKeyboard Behavior
tableStatic, read-only dataStandard tab navigation
gridInteractive cells (editable, actionable)Arrow key navigation between cells
treegridHierarchical data with expandable rowsArrow 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.
PropertyPurpose
aria-rowcountTotal number of rows (including hidden)
aria-colcountTotal number of columns (including hidden)
aria-rowindexCurrent rowโ€™s position (1-based)
aria-colindexCurrent 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>
ValueMeaning
ascendingSorted A-Z, low to high
descendingSorted Z-A, high to low
noneSortable but not currently sorted
otherSorted 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