Table

Description

A table is a static tabular structure that organizes data into rows and columns, allowing users to look up and compare values by their position relative to row and column headers.

When to use

Choose a table when the content has a genuine two-dimensional relationship and users need to look up or compare values by row and column. Good fits include:

  • Comparison data such as feature matrices, pricing tables or specification comparisons across items.
  • Schedules and timelines with rows or columns representing time periods and cells showing events or values.
  • Structured datasets for any data where each cell is meaningfully associated with both a row header and a column header.

Do not choose a table for:

  • Page layout: Layout tables were historically used to arrange visual content in a grid, but CSS now provides far superior layout tools. Using a table for layout adds structural noise to the accessibility tree and forces screen reader users to navigate through meaningless row and cell announcements. Similarly, applying role="presentation" or role="none" to strip the semantics from a data table is equally problematic—it removes all structural meaning from a table that users need to navigate.
  • Interactive tables: If the interaction model requires users to focus or select individual cells and navigate with arrow keys, use a Data Grid instead of a static table.

Examples

Native HTML Table

When a table is constructed with the following semantic HTML elements, assistive technologies get the information needed for their users without the need, in most cases, for additional ARIA attributes.

ElementPurpose
<table>Root container for the table
<caption>Provides the table’s accessible name; rendered as visible text
<thead>Groups the header rows
<tbody>Groups the body rows
<tfoot>Groups the footer rows
<tr>A table row, containing one or more cells
<th>A header cell
<td>A data cell
A table using semantic HTML elements
1<table>
2 <caption>
3 Q1 sales by region
4 </caption>
5 <thead>
6 <tr>
7 <th scope="col">Region</th>
8 <th scope="col">January</th>
9 <th scope="col">February</th>
10 <th scope="col">March</th>
11 </tr>
12 </thead>
13 <tbody>
14 <tr>
15 <th scope="row">North</th>
16 <td>$12,400</td>
17 <td>$9,800</td>
18 <td>$14,200</td>
19 </tr>
20 <tr>
21 <th scope="row">South</th>
22 <td>$8,500</td>
23 <td>$7,100</td>
24 <td>$9,300</td>
25 </tr>
26 </tbody>
27</table>

HTML attributes are often needed to convey internal table relationship information to screen readers.

scope

Set on <th> to declare whether it heads a col, row, colgroup, or rowgroup. Required for all <th> elements in data tables.

colspan, rowspan

Cause a cell to span multiple columns or rows. For ARIA tables, use aria-colspan or aria-rowspan instead.

aria-sort

Set on a sortable column or row header to indicate the current sort direction: ascending, descending, other, or none.

Applying display: flex, display: grid, or display: block to table elements via CSS removes their native table semantics in most browsers. If responsive layout overrides are necessary, replace the native <table> with an ARIA table pattern rather than restyling the native element.

Custom ARIA table

Prefer native <table> element whenever possible. It communicates tabular structure to assistive technologies without any additional ARIA attributes. When a native <table> cannot be used, replicate its structure using the following ARIA roles:

Semantic HTML elementARIA role
<table>role="table"
<caption>role="caption"
<thead>, <tbody>, <tfoot>role="rowgroup"
<tr>role="row"
<th scope="col">role="columnheader"
<th scope="row">role="rowheader"
<td>role="cell"

A native <table> receives its accessible name from its <caption> element. For an ARIA table, use aria-label or aria-labelledby on the role="table" element referencing a role="caption" element.

A table using ARIA roles and attributes
1<div role="table" aria-labelledby="sales-heading">
2 <span id="sales-heading">Q1 sales by region</span>
3 <div role="rowgroup">
4 <div role="row">
5 <span role="columnheader">Region</span>
6 <span role="columnheader">January</span>
7 </div>
8 </div>
9 <div role="rowgroup">
10 <div role="row">
11 <span role="rowheader">North</span>
12 <span role="cell">$12,400</span>
13 </div>
14 </div>
15</div>

Virtualized tables

When a table is paginated or uses virtual scrolling — not all rows or columns are present in the DOM simultaneously — use aria-rowcount and aria-colcount on the table element to declare the full dimensions, aria-rowindex on each row, and aria-colindex on each cell or on each row as a column start position to declare its position within the complete table.

The following example uses ARIA roles to construct a table. Use th aria-rowcount, aria-colcount, aria-rowindex and aria-colindex attributes similarly when constructing a table with semantic HTML elements.

Virtualized table example
1<div role="table" aria-rowcount="500" aria-colcount="6" aria-label="Products">
2 <!-- Only rows 1–2 shown for brevity; rows 3–500 follow the same pattern -->
3 <div role="row" aria-rowindex="1">
4 <span role="cell" aria-colindex="1">Widget A</span>
5 <span role="cell" aria-colindex="2">$4.99</span>
6 </div>
7 <div role="row" aria-rowindex="2">
8 <span role="cell" aria-colindex="1">Widget B</span>
9 <span role="cell" aria-colindex="2">$7.49</span>
10 </div>
11</div>

Structure and semantics

All users must be able to perceive the structure of a table and understand the relationship between its headers and data cells. The table must have a correct ARIA role, a meaningful accessible name, valid row and header structure, and accurate cell index values when virtualization is used.

Table structure and semantics are checked with the following tests.

Role

A table is composed of elements with a number of roles thar are assigned either by using the proper semantic HTML element or by using a generic element with an assigned ARIA role as shown in section Custom ARIA Table.

Element has incorrect role
StandardCriteria
WCAG4.1.2 Name, Role, Value
EN 301 5499.4.1.2 Name, Role, Value
Section 5081194.21(d)

Impact: Critical

Every element within a table must carry the correct ARIA role, as specified in the custom ARIA table section. For example, a cell that presents column-heading information must be a <th scope="col"> or have role="columnheader", not role="cell". An incorrect role prevents screen readers from building an accurate representation of the table’s structure, making it impossible for users to understand the relationship between headers and data.

Table has Rows

No group members with required role
StandardCriteria
WCAG1.3.1 Info and Relationships
EN 301 5499.1.3.1 Info and Relationships
Section 508N/A

Impact: Serious

A table must contain at least one row, and similarly, each row must have at least one cell. A <table> with no <tr> children, an ARIA table lacking role="row" elements, or rows without any data or header cells (such as <td>, <th>, or their ARIA equivalents), fails to convey tabular structure to assistive technologies. Make sure that every table includes at least one body row, and every row contains at least one cell before rendering.

Accessibility Label

Missing contextual labeling
StandardCriteria
WCAG1.3.1 Info and Relationships
EN 301 5499.1.3.1 Info and Relationships
Section 508N/A

Impact: Moderate

For a table to be understandable, both the table itself and its header cells must have accessible names.

Table accessible name

  • For native <table> elements, the preferred method is a <caption> element. If a visible caption is not desired, use aria-label or aria-labelledby instead. Alternatively, keep the <caption> element and hide it visually with a .visually-hidden CSS utility class (absolute positioning with clip-path), so sighted users do not see it but assistive technologies still announce it.
  • For custom ARIA tables (role="table"), use aria-label or aria-labelledby referencing a role="caption" element.

Cell accessible names

The <th> and <td> elements derive their accessible names from their text content. If the cell contains only icons or images, provide a name with aria-label or by including visually hidden text within the cell so that screen reader users can access the cell information. Ensure that every header cell contains meaningful, non-empty text.

Table has empty caption
StandardCriteria
WCAGN/A
EN 301 549N/A
Section 508N/A

Impact: Moderate

A <caption> element that is present but contains no text provides no accessible name for the table. Either populate the caption with a meaningful description of the table’s content, or remove the element and provide an accessible name using aria-label or aria-labelledby on the <table> element.

Table Structure

Table has multiple headers
StandardCriteria
WCAGN/A
EN 301 549N/A
Section 508N/A

Impact: Minor

A table should have at most one <thead> section (or one role="rowgroup" functioning as a header group). Multiple header sections create an ambiguous reading order and may cause screen readers to announce header rows more than once or out of sequence. Browsers typically treat a surplus <thead> as a <tbody>, so the real-world impact is lower than for duplicate <tfoot> elements — which is why multiple footers carry a higher severity rating than multiple headers.

Table has multiple footers
StandardCriteria
WCAG1.3.2 Meaningful Sequence
EN 301 5499.1.3.2 Meaningful Sequence
Section 508N/A

Impact: Moderate

A table should have at most one <tfoot> section. Multiple footer sections disrupt the logical reading order and may cause screen readers to announce footer content in an unexpected sequence, making it difficult for users to distinguish summary or total rows from body data.

Unambiguous headers

Missing table headers
StandardCriteria
WCAG1.3.1 Info and Relationships
EN 301 5499.1.3.1 Info and Relationships
Section 508N/A

Impact: Moderate

The table contains no header cells at all. Data tables should have at least one header row or column — using <th> elements for native tables, or role="columnheader" / role="rowheader" for ARIA tables — so that screen reader users can understand what each column or row represents. When this issue is detected, further header analysis is skipped. If the table is used purely for layout (not recommended), the absence of headers may be intentional, but layout tables should be replaced with CSS-based layout instead.

Table headers missing scope attribute
StandardCriteria
WCAG1.3.1 Info and Relationships
EN 301 5499.1.3.1 Info and Relationships
Section 508N/A

Impact: Moderate

Every <th> element in a data table must carry a scope attribute (col, row, colgroup, or rowgroup) that declares which cells it headers. Without scope, browsers use heuristics to infer the association, producing inconsistent results across assistive technologies.

Conflicting scope spanning values
StandardCriteria
WCAG1.3.1 Info and Relationships
EN 301 5499.1.3.1 Info and Relationships
Section 508N/A

Impact: Serious

When a <th> element has a colspan attribute, its scope must be set to "colgroup". When it has a rowspan attribute, its scope must be set to "rowgroup". Using scope="col" or scope="row" on a spanning header conflicts with the actual coverage of that cell and may produce unreliable associations across assistive technologies.

Complex headers in ARIA table
StandardCriteria
WCAG1.3.1 Info and Relationships
EN 301 5499.1.3.1 Info and Relationships
Section 508N/A

Impact: Serious

When an ARIA table contains header cells that span multiple columns or rows, some screen readers will not correctly link those headers to their associated data cells — even when header groups (role="rowgroup") are used. This is a known limitation of the ARIA table model. If your table requires spanning headers, use a native <table> element so that browsers can apply their built-in cell-to-header association algorithms.

Disabled

Element type does not support disabled state
StandardCriteria
WCAG4.1.2 Name, Role, Value
EN 301 5499.4.1.2 Name, Role, Value
Section 5081194.21(d)

Impact: Serious

Tables are static data structures and do not have a disabled state. Therefore the disabled attribute is not valid on <table>, <thead>, <tbody>, <tfoot>, <tr>, or <td> elements. Similarly, aria-disabled carries no defined meaning for non-interactive roles such as table, rowgroup, row, or cell. Applying either to these elements communicates a state that the role does not support, which assistive technologies will handle inconsistently.

Two exceptions apply: Header cells that support user interactions such as filtering or sorting may use aria-disabled, and interactive tables that implement the data grid pattern (role="grid") support aria-disabled on the grid itself and its cells.

Cell index

When some rows or columns of a table are not always present in the DOM, it may be necessary to index rows or columns so that browsers may properly index them in context of the overall table and convey that information to assistive technologies. The tests in this section check for problems with that indexing.

Row index with no row count context (aria-rowindex)
StandardCriteria
WCAGN/A
EN 301 549N/A
Section 508N/A

Impact: Minor

aria-rowindex is only meaningful when aria-rowcount is also set on the table element. Without aria-rowcount, assistive technologies cannot determine the full size of the table, making the index value meaningless to users.

Column index with no column count context (aria-colindex)
StandardCriteria
WCAGN/A
EN 301 549N/A
Section 508N/A

Impact: Minor

aria-colindex is only meaningful when aria-colcount is also present on the table element. Set aria-colcount to the total number of columns in the complete table, including any columns not currently rendered.

Incorrect aria-rowcount value
StandardCriteria
WCAG1.3.1 Info and Relationships
EN 301 5499.1.3.1 Info and Relationships
Section 508N/A

Impact: Moderate

The value of aria-rowcount must not be less than the number of rows currently present in the DOM. Because aria-rowcount declares the total row count of the complete (possibly virtualized) table, a value smaller than the number of already-rendered rows is a clear contradiction that causes screen readers to misreport the table’s dimensions.

Incorrect aria-colcount value
StandardCriteria
WCAG1.3.1 Info and Relationships
EN 301 5499.1.3.1 Info and Relationships
Section 508N/A

Impact: Moderate

The value of aria-colcount must not be less than the number of cells currently present in the DOM. Because aria-colcount declares the total column count of the complete table, a value smaller than the number of already-rendered cells is a clear contradiction that causes screen readers to misreport the table’s width.

Negative row or column index
StandardCriteria
WCAGN/A
EN 301 549N/A
Section 508N/A

Impact: Minor

aria-rowindex and aria-colindex must be positive integers greater than or equal to 1. Negative values and zero are invalid and will be ignored or misinterpreted by assistive technologies.

Index out of range
StandardCriteria
WCAGN/A
EN 301 549N/A
Section 508N/A

Impact: Minor

aria-rowindex must not exceed the value of aria-rowcount, and aria-colindex must not exceed aria-colcount. Out-of-range values indicate a mismatch between the rendered cells and the declared table dimensions, which screen readers will surface as confusing position announcements.

Inconsistent index values
StandardCriteria
WCAGN/A
EN 301 549N/A
Section 508N/A

Impact: Minor

aria-rowindex and aria-colindex values must be strictly ascending with no duplicates across the rendered rows or cells. Gaps from the table start are valid and expected in virtualized tables — for example, a table rendering rows 50–75 of 500 should use index values 50–75, not 1–26. Inconsistent values make it impossible for assistive technologies to correctly determine a cell’s position within the complete table.

Mixed aria-colindex implementation
StandardCriteria
WCAG1.3.1 Info and Relationships
EN 301 5499.1.3.1 Info and Relationships
Section 508N/A

Impact: Minor

aria-colindex must be applied at a consistent level: either on all row elements (<tr> / role="row") or on all individual cells, but not on both rows and cells simultaneously. Mixing the two levels produces conflicting column-position information that assistive technologies cannot resolve reliably.

Missing aria-colindex
StandardCriteria
WCAG1.3.1 Info and Relationships
EN 301 5499.1.3.1 Info and Relationships
Section 508N/A

Impact: Serious

When aria-colcount is defined on the table, every row or every cell must also have aria-colindex set — the same level used consistently throughout the table. Without it, screen readers cannot determine which column each row or cell occupies within the complete table.

Relationships

A table’s accessibility depends not only on its internal structure but also on how it relates to surrounding elements. Keeping the table’s DOM hierarchy intact and well-formed is the most reliable foundation for correct assistive technology behavior.

Internal and external relationships that affect accessibility are checked in the following tests.

Nested tables

Unsupported table structure
StandardCriteria
WCAGN/A
EN 301 549N/A
Section 508N/A

Impact: Minor

Nesting tables within table cells is not recommended. Although most assistive technologies can parse nested tables, this structure results in inner tables being treated as separate tables by screen readers, which can make navigation confusing and increase cognitive load for users navigating with keyboard shortcuts or landmarks.

Nested tables are not automatically included in accessibility evaluations by Evinced’s component analysis tools.

Aria Owns

Set on an element with role="table" to identify elements with role row or rowgroup when they and their child columnheader, rowheader, or cell elements cannot be included in the table’s DOM hierarchy.

Invalid aria-owns values

Best practice.

Impact: Varies

Elements referenced by the aria-owns attribute on a table container (<table> or role="table") have roles that do not conform to the expected child roles for a table. A table container should only own elements with role="row" or role="rowgroup". When referenced elements carry incompatible roles, screen readers may misinterpret the table's structure, which can confuse users navigating the data.

Avoid aria-owns

Best practice.

Impact: Varies

The aria-owns attribute is poorly supported for table structures. Avoid using aria-owns if possible.

Assistive technology support is poor and it can produce an inconsistent reading order. If rows or cells are currently rendered outside the table container, restructure the component so they are direct descendants of the table element, or switch to a virtualization library that supports in-tree row rendering rather than “portaling” rows into a separate DOM node outside of the component hierarchy.

If you must use it:

  • Preserve DOM order: DOM children of the container should come before explicitly owned elements in the DOM order unless you explicitly list the DOM children in aria-owns in the desired reading order.
  • Match expected roles: All elements specified in aria-owns must have roles that are allowed to be children of the container.
  • Avoid duplicate or inconsistent references: An element’s id must not appear in more than one element’s aria-owns attribute at any time.

Operation

Tables are typically read-only and do not require interaction. In tables that support column sorting or row selection, keyboard interactions must be implemented with care to ensure all users can operate them effectively.

Accessible table operability is checked with the following tests.

Aria Sort

aria-sort invalid value
StandardCriteria
WCAG4.1.2 Name, Role, Value
EN 301 5499.4.1.2 Name, Role, Value
Section 5081194.21(d)

Impact: Minor

The aria-sort attribute must be set to one of the four valid values: ascending, descending, none, or other. An unrecognized value is ignored by assistive technologies, leaving users without sort-direction feedback for that column.

aria-sort on a table cell
StandardCriteria
WCAGN/A
EN 301 549N/A
Section 508N/A

Impact: Minor

The aria-sort attribute is only meaningful on header cells — <th> elements for native tables, or elements with role="columnheader" or role="rowheader" for ARIA tables. Placing it on a data cell or other element has no effect and may produce unexpected behavior in assistive technologies.

Sort not accessible to keyboard
StandardCriteria
WCAG2.1.1 Keyboard
EN 301 5499.2.1.1 Keyboard
Section 5081194.21(a)

Impact: Serious

A column header that triggers sorting has been detected, but it cannot be reached or activated by keyboard. All interactive sort controls must be reachable via the Tab key and can be activated with Enter or Space, so that keyboard-only users have the same sorting capability as pointer users.

Ambiguous multi-sort levels
StandardCriteria
WCAG1.3.1 Info and Relationships
EN 301 5499.1.3.1 Info and Relationships
Section 508N/A

Impact: Minor

When a table supports multi-column sorting, only the column with the primary sort priority should have aria-sort set to ascending, descending, or other. All other participating columns must have aria-sort="none" or have the attribute removed. Setting an active sort direction on more than one column simultaneously makes the sort order ambiguous for screen reader users, who cannot determine which column takes precedence.