This example uses a range of roles to define table structure.
role="table": to replicate the <table> elementrole="rowgroup": to replicate the <thead> and <tbody> elementsrole="row": to replicate the <tr> elementrole="columnheader": to replicate the <th> element at the top of columnsrole="rowheader": to replicate the <th> element at the start of rowsrole="cell": to replicate the <td> elementaria-labelledby<h3 id="aaa">Animals found in different habitats</h3>
<div role="table" aria-labelledby="aaa">
<div role="rowgroup">
<div role="row">
<span role="columnheader">Habitat</span>
<span role="columnheader">Mammal</span>
<span role="columnheader">Bird</span>
<span role="columnheader">Reptile</span>
</div>
</div>
<div role="rowgroup">
<div role="row">
<span role="rowheader">Forest</span>
<span role="cell">Deer</span>
<span role="cell">Owl</span>
<span role="cell">Lizard</span>
</div>
<div role="row">
<span role="rowheader">Ocean</span>
<span role="cell">Dolphin</span>
<span role="cell">Albatross</span>
<span role="cell">Turtle</span>
</div>
<div role="row">
<span role="rowheader">Desert</span>
<span role="cell">Camel</span>
<span role="cell">Falcon</span>
<span role="cell">Gecko</span>
</div>
<div role="row">
<span role="rowheader">Wetland</span>
<span role="cell">Otter</span>
<span role="cell">Heron</span>
<span role="cell">Crocodile</span>
</div>
</div>
</div>
role="grid")This example uses role="grid" instead of role="table", since the columns can be sorted, an interactive behaviour static tables don't have. Activate a column header to sort by that column. aria-sort is updated on the active columnheader to reflect the current sort direction.
Note: a fully spec-complete grid also supports arrow-key navigation between individual cells. That behaviour is not implemented in this simplified example, keyboard interaction here is limited to activating the sort buttons in the header row.
<h3 id="bbb">Species count...</h3>
<div id="sort-announcer" class="hidden" role="status"></div>
<div role="grid" aria-labelledby="bbb" id="sortable-grid">
<div role="rowgroup">
<div role="row">
<span
role="columnheader"
aria-sort="none"
data-column="habitat"
data-type="text"
>
<button aria-label="Habitat - activate to sort">
Habitat
<span aria-hidden="true"></span>
</button>
</span>
<span
role="columnheader"
aria-sort="none"
data-column="species"
data-type="number"
>
<button aria-label="Number of species - activate to sort">
Number of species
<span aria-hidden="true"></span>
</button>
</span>
<span
role="columnheader"
aria-sort="none"
data-column="temperature"
data-type="number"
>
<button aria-label="Average temp - activate to sort">
Average temp
<span aria-hidden="true"></span>
</button>
</span>
</div>
</div>
<div role="rowgroup" id="sortable-grid-body">
<div role="row">
<span role="rowheader">Forest</span>
<span role="gridcell">120</span>
<span role="gridcell">12</span>
</div>
<!-- remaining rows follow the same pattern -->
</div>
</div>
aria-label on the sort buttons. The buttons used to sort rows include aria-label to provide additional context for screen reader users, for example Habitat - activate to sort
. The visible text remains concise, while the accessible name explains that each button can be activated to sort the table.
role="grid" instead of role="table". A grid is used when a table contains interactive elements, such as sortable column headers. It tells assistive technologies that users can interact with the table, rather than simply read it.
aria-sort. Each sortable column header exposes its current sort state. Headers start with aria-sort="none". When a column is sorted, its value changes to "ascending" or "descending", while all other columns return to "none". This allows assistive technologies to identify which column is currently sorted.
role="gridcell" instead of role="cell". Once a table becomes a grid, its cells should use the corresponding gridcell role so the accessibility semantics remain consistent.
data-* attributes for JavaScript. The data-column and data-type attributes are used only by the JavaScript to determine which column to sort and how to sort it. They provide no accessibility information and are never exposed to assistive technologies.
A live region role="status". When the sort order changes, a live region announces the result, such as Sorted by Name, ascending
. This provides immediate feedback without relying solely on changes to aria-sort.
Reordering the DOM, not just the display. The table rows are physically reordered in the document, so the reading order for screen reader users matches the visual order seen by sighted users.
<table>A native <table> provides built-in structure, relationships and behaviour. This makes it the best choice for displaying static tabular data. However, once a table becomes highly interactive, such as supporting sortable columns, developers often need greater control over its behaviour and presentation.
An ARIA grid is built from generic elements, such as <div> and <span>, with ARIA roles providing the semantics. This makes it easier to add interactive features such as sorting, update states like aria-sort, and reorder rows using JavaScript without working against native table behaviour.
The trade-off is that none of the accessibility provided by a native table comes for free. Developers must correctly recreate the table structure, relationships and states using ARIA. While ARIA grids offer greater flexibility, they also require much more care to implement and test correctly.