In addition to the data grid-specific tests documented below, Evinced also runs the full Table component accessibility analysis on data grid components. Refer to the Table knowledge base entry for details on all table-level tests — including accessible name, header structure, cell relationships, and column/row span correctness — that also apply to your data grid.
Data Grid
Description
A data grid is a composite widget that presents tabular data in a two-dimensional structure of rows and cells. Users may use a keyboard to navigate the grid with Arrow, Home, and End keys, then interact with buttons, checkboxes, links, text fields or other interactive elements contained within grid cells.
Though often structured as a table, the grid role does not imply a specific visual presentation—it describes relationships among elements.
Though a data grid may contain multiple interactive elements, in occupies a single tab stop in the page’s focus sequence. Internal focus should be managed during keyboard navigation as described in the ARIA guidelines Managing Focus.
When to use
Use a data grid when:
- Users need to navigate through tabular data cell-by-cell using arrow keys. Examples include spreadsheet-like interfaces, scheduling grids, or data management tables.
- Individual cells contain interactive controls such as buttons, checkboxes, links, or editable inputs. With a single entry for the grid in the page tabbing sequence, keyboard users can more easily navigate the page without needing to tab through every interactive element in the data grid.
- Selection of rows or cells or column sort controls are necessary. Such elements will need to be operated using a keyboard.
Do not use a data grid when:
- The data is read-only and users only need to scan or look up values. Refer to the Table entry for accessibility guidelines for tables. -
- The "grid" is purely for layout purposes. CSS grid and flexbox are the
appropriate tools; Layout grids should never carry
role="grid". - The data does not contain a logical row and column association. Do not use a data grid for a linear array of data simply because it wraps into multiple rows when it is displayed.
Examples
Constructing an accessible data grid widget requires more than adding interactive elements to a table structure. At a minimum, these two things are necessary:
- Setting
role="grid"on the containing element and, depending on the structure, additional ARIA attributes on the widget’s elements. - Managing focus within the data grid widget using either the roving tabindex technique or the active descendant technique as described in Developing a Keyboard Interface.
Creating a data grid widget using the HTML table element
By using native table markup and adding ARIA grid roles and JavaScript required for grid semantics and interaction, the widget takes advantage of the browser’s built-in cell-spanning, header-association, and accessible-name calculation.
In this example, an accessible data grid is set up for a Tic Tac Toe game (also known as “Noughts and Crosses”) using standard HTML table elements. The example illustrates HTML structure for a data grid but does not include the JavaScript needed to handle keyboard interaction for a data grid or everything that would be needed for a workable game.
The example uses the roving tabindex technique for managing focus within the
grid. In the initial state, tabindex="0" is set on the first interactive
element so that the grid will be in the tabbing sequence. All other interactive
elements in the grid have tabindex="-1" set.
Setting role="grid" on the <table> element sets the implicit role of its
children, so often it is only necessary to set role="grid" on the table
element itself.
| Element | Default Role | Role with table role="grid" |
|---|---|---|
<table> | table | grid |
<caption> | caption | caption |
<thead> | rowgroup | rowgroup |
<tbody> | rowgroup | rowgroup |
<tfoot> | rowgroup | rowgroup |
<tr> | row | row |
<th scope="col"> | columnheader | columnheader |
<th scope="row"> | rowheader | rowheader |
<th> | cell | gridcell |
<td> | cell | gridcell |
Example Grid HTML structure using table elements
12<table role='grid'>3 <caption>Play Tic-Tac-Toe</caption>4 <thead>5 <tr>6 <th></th>7 <th scope='col'>Column 1</th>8 <th scope='col'>Column 2</th>9 <th scope='col'>Column 3</th>10 </tr>11 </thead>12 <tbody>13 <tr>14 <th scope='row'>Row 1</th>15 <td>16 <input name='11' tabindex="0">17 </td>18 <td>19 <input name='12' tabindex="-1">20 </td>21 <td>22 <input name='13' tabindex="-1">23 </td>24 </tr>25 <tr>26 <th scope='row'>Row 2</th>27 <td>28 <input name='21' tabindex="-1">29 </td>30 <td>31 <input name='22' tabindex="-1">32 </td>33 <td>34 <input name='23' tabindex="-1">35 </td>36 </tr>3738 <tr>39 <th scope='row'>Row 3</th>40 <td>41 <input name='31' tabindex="-1">42 </td>43 <td>44 <input name='32' tabindex="-1">45 </td>46 <td>47 <input name='33' tabindex="-1">48 </td>49 </tr>50 </tbody>51</table>52
Creating a data grid widget with ARIA
The ARIA grid role does not imply a physical presentation. When inherently
two-dimensional data needs to be arranged in something other than a tabular
display, an ARIA-based structure may be appropriate.
Example Grid HTML structure using ARIA
The following example illustrates the use of ARIA with the generic elements used rather than table elements to construct the Tic Tac Toe game.
1<h2 id="gameLabel">Play Tic-Tac-Toe</h2>23<div role='grid' aria-labelledby="gameLabel">4 <div role="rowgroup">5 <div role="row">6 <div></div>7 <div role="columnheader">Column 1</div>8 <div role="columnheader">Column 2</div>9 <div role="columnheader">Column 3</div>10 </div>11 </div>12 <tbody>13 <div role="row">14 <div role="rowheader">Row 1</div>15 <div role="gridcell">16 <input name='11' tabindex="0">17 </div>18 <div role="gridcell">19 <input name='12' tabindex="-1">20 </div>21 <div role="gridcell">22 <input name='13' tabindex="-1">23 </div>24 </div>25 <div role="row">26 <div role="rowheader">Row 2</div>27 <div role="gridcell">28 <input name='21' tabindex="-1">29 </div>30 <div role="gridcell">31 <input name='22' tabindex="-1">32 </div>33 <div role="gridcell">34 <input name='23' tabindex="-1">35 </div>36 </div>3738 <div role="row">39 <div role="rowheader">Row 3</div>40 <div role="gridcell">41 <input name='31' tabindex="-1">42 </div>43 <div role="gridcell">44 <input name='32' tabindex="-1">45 </div>46 <div role="gridcell">47 <input name='33' tabindex="-1">48 </div>49 </div>50 </tbody>51</div>52
Relevant ARIA attributes
role="grid"on the container element identifies it as an interactive grid widget. The grid must have an accessible name, typically viaaria-labeloraria-labelledby.role="row"on each row element. Rows are owned by thegridcontainer, or by an optionalrole="rowgroup"element (equivalent to<thead>,<tbody>, or<tfoot>).role="gridcell"on each data cell. For header cells, userole="columnheader"orrole="rowheader"instead.aria-rowcount/aria-colcounton the grid element when the DOM contains only a subset of rows or columns (virtual scrolling). These communicate the true total size of the grid to assistive technologies.aria-rowindex/aria-colindexon each row or cell to communicate its position within the full dataset when virtual scrolling is in use.aria-multiselectableon the grid element to indicate that multiple rows or cells can be selected simultaneously.aria-selectedon a row or cell to indicate its selection state. Whenaria-multiselectableis set, all selectable rows or cells must have an explicitaria-selectedvalue.aria-readonlyon the grid, a row, or an individual cell to indicate that the content cannot be edited. See the Avoid aria-readonly attribute test entry for important caveats.aria-sorton column or row header cells to communicate the current sort direction (ascending,descending,other, ornone). See Grid cell aria-sort accessibility tests.aria-expandedon a row that can be expanded to reveal detail rows.Focus management: Use a roving
tabindex(settabindex="0"on the currently active cell,tabindex="-1"on all others, and update on arrow-key navigation) oraria-activedescendanton the grid container (keeping focus on the grid while updating the attribute to point to the focused cell). Either approach ensures the grid has exactly one tab stop in the page's tab sequence.
Table component accessibility tests
Structure and semantics
The grid container and its cells must carry the correct ARIA roles and attributes so that assistive technologies can identify the widget, communicate its structure, and expose the state of individual cells.
Role
Element has incorrect role
| Standard | Criteria |
|---|---|
| WCAG | 4.1.2 Name, Role, Value |
| EN 301 549 | 9.4.1.2 Name, Role, Value |
| Section 508 | 1194.21(d) |
Impact: Critical
The root element of a data grid widget must have role="grid". When an
incorrect role is applied — for example, role="table", role="list", or no
role at all — assistive technologies identify the widget as a different type of
component. Screen reader users are presented with the wrong interaction model:
they will not be told to use arrow keys to navigate, and key commands for
grid-specific behavior (such as navigating to the first or last cell in a row)
will not be announced.
Ensure:
- The outermost container element has
role="grid". - Each row element has
role="row". - Each data cell has
role="gridcell",role="columnheader", orrole="rowheader"as appropriate.
Incorrect root element
The Evinced Unit Tester may report this issue when it detects role="grid" on a
descendant element rather than on the element passed to the test locator. Verify
that the locator used in the test targets the outermost container of the grid
widget, and that role="grid" is applied directly to that element rather than
to a child or wrapper inside it.
Grid Has Cells
No group members with required role
| Standard | Criteria |
|---|---|
| WCAG | 1.3.1 Info and Relationships |
| EN 301 549 | 9.1.3.1 Info and Relationships |
| Section 508 | N/A |
Impact: Serious
A grid container must own child elements with the required cell roles.
Specifically, the grid must contain role="row" elements, and each row must
contain at least one element with role="gridcell", role="columnheader", or
role="rowheader". If the grid container has no descendants carrying these
required roles, assistive technologies cannot convey the grid's structure to
users: screen readers will not announce rows, column counts, or individual cell
content in a meaningful way.
Ensure that:
- Every row in the grid is marked with
role="row". - Every data cell is marked with
role="gridcell". - Header cells use
role="columnheader"orrole="rowheader"as appropriate.
Accessible name
Like table components, data grid elements must have accessible names — refer to the Table accessible name tests for full guidance on naming the grid container and its structural elements.
Aria Readonly
Avoid aria-readonly attribute
| Standard | Criteria |
|---|---|
| WCAG | N/A |
| EN 301 549 | N/A |
| Section 508 | N/A |
Impact: Best Practice
The aria-readonly attribute is being used on one or more cells or rows in the
data grid. While the ARIA specification explicitly supports this —
aria-readonly set on the grid element propagates automatically to all owned
gridcell elements, and individual cells may override the propagated value —
support for announcing the read-only state is inconsistent across browsers and
assistive technologies. Some screen readers do not announce aria-readonly at
all, leaving users unaware that certain cells cannot be edited.
A few important points from the spec to keep in mind:
aria-readonlycommunicates only whether cell content is editable. It has no effect on a user's ability to navigate or otherwise manipulate the grid itself — cells witharia-readonly="true"must remain focusable and reachable via arrow key navigation.- If a grid presents content that is inherently non-editable (for example, a
date picker showing a grid of link elements), you do not need to set
aria-readonlyat all. The absence ofaria-readonlydoes not imply that content is editable.
Where possible, prefer the native HTML readonly attribute on form controls
within cells, as it has broader and more consistent screen reader support. If
you use aria-readonly, test with multiple screen reader and browser
combinations to verify that the read-only state is announced correctly.
Disabled
Element type does not support disabled state
| Standard | Criteria |
|---|---|
| WCAG | 4.1.2 Name, Role, Value |
| EN 301 549 | 9.4.1.2 Name, Role, Value |
| Section 508 | 1194.21(d) |
Impact: Serious
An element with a role of grid, row, or gridcell has the native HTML
disabled attribute applied to it. While the ARIA specification does allow
these roles to carry a disabled state, the correct way to communicate it is
through aria-disabled="true" — not the native disabled attribute. The native
disabled attribute is a feature of HTML form controls; applying it to generic
or ARIA-role elements has no semantic effect and will not be announced by screen
readers.
Replace the disabled attribute with aria-disabled="true" on the affected
element. Note that unlike native disabled, aria-disabled does not suppress
keyboard interaction automatically — you must also add event handlers that
prevent activation when the element is in a disabled state.
Conflicting disabled values
| Standard | Criteria |
|---|---|
| WCAG | 4.1.2 Name, Role, Value |
| EN 301 549 | 9.4.1.2 Name, Role, Value |
| Section 508 | 1194.21(d) |
Impact: Serious
An element within the grid has both disabled and aria-disabled attributes
set to conflicting values — for example, disabled present (which implies
disabled) alongside aria-disabled="false" (which explicitly signals enabled).
This inconsistency causes assistive technologies to report the element's state
incorrectly, leading to a discrepancy between what sighted users see and what
screen reader users hear.
Resolve the conflict by using a single attribute:
- For native HTML form controls inside grid cells, use only the
disabledattribute. - For custom elements, use only
aria-disabled.
Aria-sort
aria-sort invalid value
| Standard | Criteria |
|---|---|
| WCAG | 4.1.2 Name, Role, Value |
| EN 301 549 | 9.4.1.2 Name, Role, Value |
| Section 508 | 1194.21(d) |
Impact: Minor
A table or grid header cell has an aria-sort attribute with an invalid value.
The only valid values are ascending, descending, other, and none. An
invalid value (such as a misspelling or a custom string) is ignored by assistive
technologies, which then cannot communicate the column's sort state to screen
reader users.
Correct the aria-sort value to one of the four valid options. Update the value
dynamically as users interact with the sort control — toggle between ascending
and descending, and reset to none (or omit the attribute) when the column is
unsorted.
aria-sort on a table cell
| Standard | Criteria |
|---|---|
| WCAG | N/A |
| EN 301 549 | N/A |
| Section 508 | N/A |
Impact: Best Practice
The aria-sort attribute is present on a standard data cell (<td> or
role="gridcell") rather than on a header cell (<th>, role="columnheader",
or role="rowheader"). Assistive technologies expect sort indicators on header
cells; placing aria-sort on a data cell may not be announced correctly and is
inconsistent with the ARIA specification.
Move the aria-sort attribute to the appropriate column or row header cell that
controls the sort order. Ensure the header cell is also keyboard operable if it
triggers a sort action.
Table sort not accessible to keyboard
| Standard | Criteria |
|---|---|
| WCAG | 2.1.1 Keyboard |
| EN 301 549 | 9.2.1.1 Keyboard |
| Section 508 | N/A |
Impact: Needs Review
A header cell with aria-sort is present, indicating that the column supports
sorting, but the cell cannot be reached or activated by keyboard. Keyboard-only
users are unable to change the sort order, which means a core function of the
data grid is inaccessible to them.
Take the following actions:
- Make sortable header cells focusable by adding
tabindex="-1"(ortabindex="0"if they are the initially active cell) so that keyboard users can reach them via arrow navigation within the grid. - Add a
keydownevent listener to handleEnterorSpaceto trigger the sort, consistent with button behavior.
Relationships
Aria-owns
Invalid aria-owns values
| Standard | Criteria |
|---|---|
| WCAG | N/A |
| EN 301 549 | N/A |
| Section 508 | N/A |
Impact: Needs Review
Elements referenced by the aria-owns attribute on the grid container have
roles that do not conform to the expected child roles for a grid. A grid
should only own elements with role="row" or role="rowgroup". Referencing
elements with incompatible roles causes assistive technologies to misrepresent
the grid's structure, potentially confusing users about which cells belong to
the grid.
Avoid using aria-owns if possible. If you must use it:
- Preserve DOM order: DOM children of the grid should come first, followed
by the elements referenced in
aria-owns, unless you explicitly list the DOM children inaria-ownsin the desired reading order. - Match expected roles: All elements specified in
aria-ownsmust have roles that are valid owned children ofgrid—role="row"orrole="rowgroup". - Avoid circular references: An element's
idmust not appear in more than onearia-ownsattribute at any time.
Avoid aria-owns
| Standard | Criteria |
|---|---|
| WCAG | N/A |
| EN 301 549 | N/A |
| Section 508 | N/A |
Impact: Needs Review
The aria-owns attribute is being used on the grid container or on one of its
gridcell elements to override the default DOM parent–child relationship.
Support for aria-owns is inconsistent across assistive technologies, and its
use can produce an unpredictable or incorrect reading order for screen reader
users.
Avoid aria-owns at every level of the grid. Instead:
- Ensure row and cell elements are direct DOM children of the grid (or nested
inside a
rowgroupchild of the grid). - Place any content that belongs to a cell as a direct DOM child of that
gridcellelement.
If content appears out of order in the DOM for layout reasons, restructure the
markup or CSS to achieve the required visual presentation without modifying the
accessibility tree via aria-owns.
Operation
Pointer operation
A user with a mouse, touch screen, or other pointing device should be able to:
Move focus to the clicked cell, making it the active cell. Subsequent arrow-key navigation starts from that cell.
Activate the control (checkbox, button, link, etc.) directly without a separate step to enter the cell.
Change the column's sort order. The header's aria-sort value should update to
reflect the new sort direction.
Keyboard operation
Data grids implement a two-mode keyboard interaction model. In navigation mode , arrow keys move focus between cells. In interaction (or edit) mode, focus is inside a cell's content so the user can interact with the element within it.
Moves focus into the grid, landing on the previously focused cell or, if none, the first cell. Subsequent Tab presses move focus out of the grid to the next element in the page tab sequence.
Moves focus into the grid from the other direction (if the grid was the last-visited focusable element), or moves focus to the previous page element when pressed from within the grid.
Moves focus one cell to the right. Focus does not wrap at the end of a row.
Moves focus one cell to the left. Focus does not wrap at the start of a row.
Moves focus one cell down. Focus does not wrap from the last row to the first.
Moves focus one cell up. Focus does not wrap from the first row to the last.
Moves focus to the first cell in the current row.
Moves focus to the last cell in the current row.
Moves focus to the first cell in the grid (first row, first column).
Moves focus to the last cell in the grid (last row, last column).
Optionally moves focus down by the number of rows visible in the viewport.
Optionally moves focus up by the number of rows visible in the viewport.
When focus is on a cell, switches to interaction mode: moves focus to the editable element or first interactive element within the cell.
When in interaction mode, exits the cell content and returns focus to the cell itself (navigation mode). Focus must not leave the grid entirely.
Activates the focused interactive element (for example, checks a checkbox, follows a link, or triggers a button) within the cell.
Navigate within a composite element inside the cell (for example, within a select or radio group). Arrow keys operate the inner widget rather than moving between grid cells while interaction mode is active.
Move focus between multiple interactive elements within the same cell while in interaction mode, if the cell contains more than one control.
Focus sequence
Data grid not focusable
| Standard | Criteria |
|---|---|
| WCAG | 2.1.1 Keyboard |
| EN 301 549 | 9.2.1.1 Keyboard |
| Section 508 | N/A |
Impact: Critical
Neither the grid container nor any of its descendant cells are included in the page's tab sequence. Keyboard users have no way to move focus into the grid at all: they cannot navigate its cells, read cell content, or operate any interactive controls within it.
Take one of the following actions:
- Set
tabindex="0"on the first cell (or the most recently active cell) so that the grid is reachable viaTab. - Set
tabindex="0"on the grid container element if you are using thearia-activedescendantfocus management pattern.
Only one element within the grid should have tabindex="0" at any time; all
other cells should have tabindex="-1".
Composite unpredictable focus
| Standard | Criteria |
|---|---|
| WCAG | 2.1.1 Keyboard |
| EN 301 549 | 9.2.1.1 Keyboard |
| Section 508 | N/A |
Impact: Serious
When focus enters the grid, it does not land on the expected cell. The ARIA authoring practices require that focus lands on the previously active cell, or on the first cell if no cell has been active before. When focus arrives unpredictably — for example, on a non-cell element, or on a cell that is not the logical starting point — keyboard and screen reader users lose their orientation within the grid.
Ensure that:
- When the grid gains focus for the first time, focus lands on the first cell.
- On subsequent visits, focus returns to the previously active cell.
- No cell other than the intended one has
tabindex="0"at the time focus enters the grid.
Redundant focusable children
| Standard | Criteria |
|---|---|
| WCAG | 1.3.1 Info and Relationships |
| EN 301 549 | 9.1.3.1 Info and Relationships |
| Section 508 | N/A |
Impact: Moderate
Multiple child elements of the grid are included in the page's tab sequence
simultaneously (i.e., more than one element has tabindex="0"). This disrupts
the expected single-tab-stop pattern for composite widgets. Users navigating
with Tab must stop at each extra focusable element within the grid rather than
passing through it in one step.
Audit all elements in the grid and ensure that at any given moment only one
element has tabindex="0". All other cells and interactive elements within
cells should have tabindex="-1".
Skipped shift-tab evaluation
| Standard | Criteria |
|---|---|
| WCAG | N/A |
| EN 301 549 | N/A |
| Section 508 | N/A |
Impact: Needs Review
The tester was unable to evaluate Shift+Tab behavior for this data grid. This
typically occurs when earlier focus sequence tests have failed, leaving the grid
in a state where reverse-tab navigation cannot be reliably tested.
Manually verify that:
- Pressing
Tabfrom within the grid moves focus to the next element in the page tab sequence, not to another cell within the grid. - Pressing
Shift+Tabmoves focus to the previous element in the page tab sequence. - Focus is never lost or sent to
<body>after exiting the grid in either direction.
Keyboard navigation
Skipped grid keyboard navigation tests
| Standard | Criteria |
|---|---|
| WCAG | 2.1.1 Keyboard |
| EN 301 549 | 9.2.1.1 Keyboard |
| Section 508 | N/A |
Impact: Serious
The grid's keyboard navigation does not conform to the ARIA spec, causing the tester to lose context and skip further evaluation. This issue is triggered when one or more of the following fundamental navigation behaviors are broken:
- Arrow keys (
ArrowLeft,ArrowRight,ArrowUp,ArrowDown) do not move focus between cells as expected. - Edge behavior — focus loops from the last cell in a row or column back to the first, rather than stopping at the boundary.
- Edge keys (
Home,End,Ctrl+Home,Ctrl+End) do not move focus to the first or last cell in the current row or in the entire grid.
Keyboard-only users will have significant difficulty navigating the data grid. Because this test is a prerequisite for cell-level evaluation, the tester will also skip all cell focus and interaction tests when this failure is present — meaning the full extent of keyboard accessibility issues in the grid may be underreported until this is fixed.
Address the failures above, then re-run the tests. Refer to Keyboard operation for the full expected interaction spec.
Focus left data grid unexpectedly
| Standard | Criteria |
|---|---|
| WCAG | 1.3.2 Meaningful Sequence |
| EN 301 549 | 9.1.3.2 Meaningful Sequence |
| Section 508 | N/A |
Impact: Serious
Focus unexpectedly leaves the grid boundary when navigating with arrow keys or Home/End/Ctrl+Home/Ctrl+End. The expected behavior is that all directional navigation keys keep focus within the grid cells. When focus escapes to an element outside the grid, keyboard users lose their position and the reading sequence is disrupted.
To fix this:
- Ensure
role="grid"is on the container androle="row"/role="gridcell"are on the appropriate descendants so the browser can compute the correct grid structure. - Add
keydownevent listeners on the grid container that callevent.preventDefault()for all navigation keys (arrow keys, Home, End, Ctrl+Home, Ctrl+End) and then programmatically move focus to the intended cell.
Grid arrow navigation failed
| Standard | Criteria |
|---|---|
| WCAG | N/A |
| EN 301 549 | N/A |
| Section 508 | N/A |
Impact: Best Practice
Users cannot navigate to neighboring cells using arrow keys. This is the most fundamental keyboard interaction in a data grid; without it, keyboard-only users have no way to move through the grid's data.
Implement keydown event listeners on the grid container for ArrowUp,
ArrowDown, ArrowLeft, and ArrowRight. Each handler should:
- Calculate the target cell based on the current cell's row and column indices.
- Update
tabindex(roving pattern) oraria-activedescendantto the target cell. - Set focus on the target cell.
- Call
event.preventDefault()to prevent the browser's default scroll behavior.
Failed next cell keyboard navigation
| Standard | Criteria |
|---|---|
| WCAG | N/A |
| EN 301 549 | N/A |
| Section 508 | N/A |
Impact: Best Practice
The grid does not support navigating to the next cell using ArrowRight or
ArrowDown. Users relying on keyboard navigation are unable to move forward
through the grid's data, making the component effectively unusable for
keyboard-only users.
Add keydown event listeners for ArrowRight and ArrowDown that move focus
to the next cell in the respective direction. Ensure the handler prevents
default browser scrolling and that focus lands directly on the target gridcell
element (or updates aria-activedescendant).
Failed previous cell keyboard navigation
| Standard | Criteria |
|---|---|
| WCAG | N/A |
| EN 301 549 | N/A |
| Section 508 | N/A |
Impact: Best Practice
The grid does not correctly handle ArrowLeft or ArrowUp to move focus to the
previous cell. Keyboard users can navigate forward into the grid but cannot
navigate backward, severely limiting their ability to review or interact with
data they have already passed.
Add keydown event listeners for ArrowLeft and ArrowUp. Check that
aria-activedescendant is updated correctly, or that tabindex is transferred
to the target cell, when these keys are pressed.
Grid arrow navigation should not loop
| Standard | Criteria |
|---|---|
| WCAG | N/A |
| EN 301 549 | N/A |
| Section 508 | N/A |
Impact: Best Practice
Pressing an arrow key at the edge of a row or column causes focus to loop to the
opposite edge — for example, pressing ArrowRight on the last cell in a row
moves focus to the first cell of the same row. Looping behavior is not part of
the ARIA grid pattern and is disorienting: users expecting to reach the edge of
the grid instead find themselves unexpectedly repositioned.
Update the navigation logic to treat grid edges as hard boundaries. When focus is on:
- The last cell in a row:
ArrowRightshould have no effect. - The first cell in a row:
ArrowLeftshould have no effect. - The last cell in a column:
ArrowDownshould have no effect. - The first cell in a column:
ArrowUpshould have no effect.
Grid row context lost on Home/End key press
| Standard | Criteria |
|---|---|
| WCAG | N/A |
| EN 301 549 | N/A |
| Section 508 | N/A |
Impact: Best Practice
Pressing Home or End moves focus to a different row rather than to the first
or last focusable cell in the current row. The ARIA authoring practices specify
that Home should move focus to the first cell in the current row and End to
the last cell in the current row. Jumping to a different row causes users to
lose their row context, making the grid harder to navigate.
Implement custom keydown handlers for Home and End that calculate the
first and last cells in the currently focused row and move focus there, without
changing the active row.
Grid does not support Home/End keys
| Standard | Criteria |
|---|---|
| WCAG | N/A |
| EN 301 549 | N/A |
| Section 508 | N/A |
Impact: Best Practice
The grid does not respond to Home, End, Ctrl+Home, or Ctrl+End key
presses. These keys are part of the standard grid keyboard interaction and allow
efficient navigation to row and grid boundaries without pressing arrow keys
repeatedly.
Implement the following behaviors:
Home→ first focusable cell in the current row.End→ last focusable cell in the current row.Ctrl+Home→ first cell in the grid.Ctrl+End→ last cell in the grid.
RTL navigation not supported
| Standard | Criteria |
|---|---|
| WCAG | N/A |
| EN 301 549 | N/A |
| Section 508 | N/A |
Impact: Needs Review
The grid's keyboard navigation appears to use a right-to-left (RTL) convention, which is not currently supported by the tester. Users may experience unexpected behavior when navigating with arrow keys if the directionality of the navigation does not match the document or component's text direction setting.
Ensure the grid's keyboard navigation follows the expected convention for the
document's text direction (dir attribute). If RTL support is intentional,
verify that ArrowLeft moves focus toward the reading end of the row and
ArrowRight moves toward the reading start, consistent with RTL user
expectations.
Keyboard accessible
Unreachable grid cells
| Standard | Criteria |
|---|---|
| WCAG | 1.3.1 Info and Relationships |
| EN 301 549 | 9.1.3.1 Info and Relationships |
| Section 508 | N/A |
Impact: Needs Review
Some cells in the grid are not reachable by keyboard arrow navigation. Keyboard
users cannot access or interact with the content in those cells. This may occur
when cells lack a tabindex attribute, when navigation logic contains a bug
that skips certain cells, or when cells are rendered in the DOM in a way that
the navigation handler does not account for.
Ensure that every cell in the grid:
- Has
tabindex="-1"(or participates in thearia-activedescendantpattern) so it can receive programmatic focus. - Is reachable from an adjacent cell via the arrow key corresponding to its direction.
- Is included in the navigation handler's cell-indexing logic (particularly important for grids with spanning cells or virtualized rows).
Focus inconsistently leaves data grid
| Standard | Criteria |
|---|---|
| WCAG | N/A |
| EN 301 549 | N/A |
| Section 508 | N/A |
Impact: Best Practice
A specific cell or set of cells causes focus to leave the grid when navigated to
with arrow keys, even though other cells in the grid keep focus contained. This
inconsistency indicates a bug in the navigation handler — for example, a cell
that is missing a tabindex="-1" attribute, or whose keydown listener
incorrectly falls through to the browser's default behavior.
Audit each cell that triggers the escape and verify that:
- It has
tabindex="-1"so it can receive programmatic focus. - The grid's
keydownhandler callsevent.preventDefault()for navigation keys when focus is on that cell.
Multiple Widgets
Cell contains a composite element
| Standard | Criteria |
|---|---|
| WCAG | N/A |
| EN 301 549 | N/A |
| Section 508 | N/A |
Impact: Best Practice
A composite UI element that requires arrow keys to navigate — such as a radio group, listbox, or another grid — has been placed inside a grid cell. Because the grid itself uses arrow keys for cell-to-cell navigation, nesting a composite widget that also captures arrow keys creates a conflict: keyboard users may become trapped within the inner composite and unable to continue navigating the outer grid.
Where possible, keep composite widgets outside of grid cells. If a composite must appear in a cell, implement a clear Enter/Escape mode-switching pattern so users can enter the inner widget, interact with it using arrow keys, and then exit back to grid navigation mode.
Cell contains multiple interactives
| Standard | Criteria |
|---|---|
| WCAG | N/A |
| EN 301 549 | N/A |
| Section 508 | N/A |
Impact: Best Practice
A single grid cell contains more than one interactive element. Keyboard and screen reader users may struggle to efficiently navigate and interact with the cell's content — particularly because the grid navigation model assumes a relatively simple relationship between a cell and its focusable content.
Where possible, simplify cells by reducing the number of interactive elements. If multiple interactions are necessary within a cell, consider separating them into distinct cells, or ensure clear mode-switching (Enter to enter the cell, Escape to return to grid navigation) so users can Tab between the multiple controls within the cell while in interaction mode.
Focus Within Cell
Redundant focus on grid cell
| Standard | Criteria |
|---|---|
| WCAG | N/A |
| EN 301 549 | N/A |
| Section 508 | N/A |
Impact: Best Practice
During arrow key navigation, focus lands on the gridcell element rather than
directly on the single interactive element within it — for example, landing on
the cell rather than on the checkbox or link it contains. When a cell contains
exactly one interactive element that does not require arrow keys to operate (
such as a checkbox, button, or link), the ARIA authoring practices recommend
placing focus directly on that element rather than on the cell itself, as users
expect to interact with the control immediately.
Update the navigation logic to set focus on the interactive element inside the
cell when that cell contains exactly one such element, rather than on the
gridcell container. Reserve focus on the cell itself for cells that contain no
interactives or multiple interactives.
Focus lands within grid cell with multiple elements
| Standard | Criteria |
|---|---|
| WCAG | N/A |
| EN 301 549 | N/A |
| Section 508 | N/A |
Impact: Best Practice
During arrow key navigation, focus lands on an element inside the grid cell
rather than on the cell itself, even though the cell contains multiple
interactive elements or no interactive elements at all. In these cases, focus
should remain on the gridcell element; the user enters the cell's content via
Enter (interaction mode) and uses Tab or arrow keys to navigate within it.
Update focus management so that:
- No interactive elements: focus stays on the
gridcellitself during arrow navigation. The cell is read-only from a keyboard perspective. - Exactly one interactive element that does not require arrow keys ( checkbox, button, link): focus may be placed directly on that element, bypassing the cell container.
- Multiple interactive elements: focus stays on the
gridcellitself during arrow navigation. The user pressesEnterorF2to enter interaction mode, then usesTab/Shift+Tabto move between the controls inside the cell, andEscapeto return focus to the cell and exit interaction mode.
Interactions within cell
Grid cell not focused after action
| Standard | Criteria |
|---|---|
| WCAG | 2.1.1 Keyboard |
| EN 301 549 | 9.2.1.1 Keyboard |
| Section 508 | N/A |
Impact: Critical
When a user presses a key (Typically Enter or F2) to interact with a grid
cell's content, focus leaves the cell entirely rather than remaining within it.
Users lose their position in the grid and may not be able to resume navigation
from where they left off.
Ensure that focus management within cells is handled explicitly:
- If a cell contains editable content or a composite element, pressing
Entershould enter interaction mode and move focus to the content within the cell. - Handle all relevant key events with explicit
focus()calls andevent.preventDefault()where necessary to prevent the browser from moving focus outside the grid.
Focus does not enter grid cell
| Standard | Criteria |
|---|---|
| WCAG | 2.1.1 Keyboard |
| EN 301 549 | 9.2.1.1 Keyboard |
| Section 508 | N/A |
Impact: Critical
Pressing a key that should switch the cell to interaction mode — typically
Enter or F2 — does not move focus to the interactive element within the
cell. Focus stays on the gridcell element, and users are unable to interact
with the cell's content (for example, type in an editable field or operate a
dropdown).
Add a keydown listener on the gridcell element (or delegate from the grid
container) that responds to Enter and F2 by explicitly calling .focus() on
the appropriate interactive element within the cell.
Grid cell lost focus on Escape
| Standard | Criteria |
|---|---|
| WCAG | 1.3.2 Meaningful Sequence |
| EN 301 549 | 9.1.3.2 Meaningful Sequence |
| Section 508 | N/A |
Impact: Moderate
Pressing Escape while in interaction mode inside a cell moves focus out of the
grid entirely rather than returning it to the gridcell element. The expected
behavior is that Escape exits interaction mode and returns focus to the cell
itself, leaving the user in navigation mode at their previous position.
To fix this:
- Add a
keydownlistener on each interactive element within the cell that listens forEscape. - On
Escape, call.focus()on the parentgridcellelement to re-enter navigation mode. - Ensure the
gridcellelement itself hastabindex="-1"so it can receive programmatic focus.
Cannot restore grid navigation
| Standard | Criteria |
|---|---|
| WCAG | N/A |
| EN 301 549 | N/A |
| Section 508 | N/A |
Impact: Best Practice
Pressing Escape to exit a cell's interaction mode does not correctly return
focus to the gridcell element. This is a less severe variant of the issue
above: focus may land somewhere within the grid but not precisely on the
expected cell, making it difficult for users to orient themselves after exiting
interaction mode.
Verify that:
- All interactive elements within every grid cell have
keydownlisteners forEscape. - Each listener explicitly calls
gridcellElement.focus()on the containinggridcell. - The
gridcellelement hastabindex="-1"to ensure it is programmatically focusable.
Event Listener Type
Deprecated keypress listener
| Standard | Criteria |
|---|---|
| WCAG | N/A |
| EN 301 549 | N/A |
| Section 508 | N/A |
Impact: Needs Review
The grid or one of its cells is using a keypress event listener for keyboard
interaction. The keypress event is deprecated: it does not fire for
non-printable keys in some browsers, behaves inconsistently with IME (non-Latin
input methods), and may be removed in future browser versions. This can cause
keyboard handlers to silently fail, particularly for Escape, Enter,
ArrowUp, ArrowDown, and other navigation keys that data grids depend on.
Replace all keypress listeners with keydown listeners. The keydown event
fires for every key press, including arrow keys, modifier keys, and
non-printable characters, and is fully supported across all current browsers and
assistive technology combinations.
keydown event handler example for a data grid
1gridContainer.addEventListener("keydown", (event) => {2 const currentCell = document.activeElement;3 switch (event.key) {4 case "ArrowRight":5 focusCell(currentCell, 0, +1);6 event.preventDefault();7 break;8 case "ArrowLeft":9 focusCell(currentCell, 0, -1);10 event.preventDefault();11 break;12 case "ArrowDown":13 focusCell(currentCell, +1, 0);14 event.preventDefault();15 break;16 case "ArrowUp":17 focusCell(currentCell, -1, 0);18 event.preventDefault();19 break;20 case "Home":21 focusFirstCellInRow(currentCell);22 event.preventDefault();23 break;24 case "End":25 focusLastCellInRow(currentCell);26 event.preventDefault();27 break;28 case "Enter":29 case "F2":30 enterInteractionMode(currentCell);31 break;32 case "Escape":33 exitInteractionMode(currentCell);34 break;35 }36});