Add WAI-ARIA roles to custom header components in data tables
What
This practice is triggered when creating custom header components or when not using the semantically correct th elements. Violations occur when header cells are rendered as non-semantic elements such as divs without the proper ARIA role.
Why
Adding WAI-ARIA roles like "columnheader" or "rowheader" improves screen reader support by explicitly declaring the cell’s purpose, which is critical when semantic elements cannot be used.
Fix
Either use semantically correct th elements or, if a custom component renders a non-semantic element, include the correct role attribute in the element declaration.
Examples
Example 1:
Positive
This example uses a custom TableHeader component that appropriately adds the aria role for header cells, ensuring accessibility.
import React from "react";
const TableHeader: React.FC<{ children: React.ReactNode; isRowHeader?: boolean }> = ({ children, isRowHeader = false }) => {
return (
<div role={isRowHeader ? "rowheader" : "columnheader"} style={{ fontWeight: "bold", padding: "8px" }}>
{children}
</div>
);
};
const DataTable: React.FC = () => {
return (
<div role="table">
<div role="row">
<TableHeader>Name</TableHeader>
<TableHeader>Age</TableHeader>
<TableHeader>City</TableHeader>
</div>
<div role="row">
<div role="cell">Alice</div>
<div role="cell">30</div>
<div role="cell">Paris</div>
</div>
<div role="row">
<div role="cell">Bob</div>
<div role="cell">25</div>
<div role="cell">London</div>
</div>
</div>
);
};
export default DataTable;
Negative
This example misses the explicit WAI-ARIA role on the header component, causing assistive technologies to misinterpret the header cells.
import React from "react";
const TableHeader: React.FC<{ children: React.ReactNode }> = ({ children }) => {
return (
<div style={{ fontWeight: "bold", padding: "8px" }}>
{children}
</div>
);
};
const DataTable: React.FC = () => {
return (
<div role="table">
<div role="row">
<TableHeader>Name</TableHeader>
<TableHeader>Age</TableHeader>
<TableHeader>City</TableHeader>
</div>
<div role="row">
<div role="cell">Alice</div>
<div role="cell">30</div>
<div role="cell">Paris</div>
</div>
<div role="row">
<div role="cell">Bob</div>
<div role="cell">25</div>
<div role="cell">London</div>
</div>
</div>
);
};
export default DataTable;