HTML Guides for cell
Learn how to identify and fix common HTML validation errors flagged by the W3C Validator — so your pages are standards-compliant and render correctly across every browser. Also check our Accessibility Guides.
An element with role="cell" has no ancestor with role="row", and no row claims it through aria-owns.
An ARIA table nests its roles in a fixed order: table, then an optional rowgroup, then row, then cell (or columnheader and rowheader). Screen readers use that structure to announce a cell's row and column and to move between cells. A cell outside any row has no position in the table, so it may be read as loose text or skipped.
The row doesn't have to be the cell's direct parent. Plain <div> or <span> wrappers between a role="row" element and its cells pass, so layout wrappers can stay. The error appears when no ancestor has the row role at all: cells placed straight inside role="table" or role="rowgroup", a row wrapper that only has a class, or a role="cell" used outside any table. When the cells can't sit inside the row in the DOM, give each one an id and list those ids in aria-owns on the row.
Newer versions of the validator word this message as an element with the “role” value “row”, with the same cause and fix.
For tabular data, a native <table> with <tr> and <td> gives you this structure without any role attributes.
Invalid example
The second row's wrapper has a class but no role="row":
<div role="table" aria-label="Team members">
<div role="row">
<div role="columnheader">Name</div>
<div role="columnheader">Role</div>
</div>
<div class="member">
<div role="cell">Alice</div>
<div role="cell">Designer</div>
</div>
</div>
Valid example
With role="row" on the wrapper, the cells belong to a row, even with an extra layout <div> in between:
<div role="table" aria-label="Team members">
<div role="row">
<div role="columnheader">Name</div>
<div role="columnheader">Role</div>
</div>
<div role="row">
<div class="row-inner">
<div role="cell">Alice</div>
<div role="cell">Designer</div>
</div>
</div>
</div>
Every element with role="cell" needs an ancestor with role="row", or a row that claims it through aria-owns.
An ARIA table nests its roles in a fixed order: table, then an optional rowgroup, then row, then cell (or columnheader and rowheader). Screen readers use that structure to announce a cell's row and column and to let people jump from cell to cell. A cell with no row above it has no position in the table, so a screen reader may read it as loose text or skip it during table navigation.
This usually happens when a table built from <div> elements has a wrapper for each row that carries a class for styling but never gets role="row". If that wrapper sits inside a role="rowgroup", the validator also reports that a child of the rowgroup must have role="row". Adding the missing role to the wrapper fixes both messages.
The row doesn't have to be the cell's direct parent. The validator accepts plain <div> or <span> wrappers between a role="row" element and its cells, so you can keep layout wrappers as long as some ancestor has the row role. When the cells can't sit inside the row in the DOM, give each cell an id and list those ids in aria-owns on the row.
Older validator versions word this message as an element with “role=row” instead of an element with the “role” value “row”, with the same cause and fix.
If the data is really tabular, a native <table> with <tr> and <td> gives you this structure without any role attributes.
Invalid example
Each plan wrapper groups two cells, but nothing marks it as a row.
<div role="table" aria-label="Subscription plans">
<div role="rowgroup">
<div class="plan">
<div role="cell">Basic</div>
<div role="cell">$10 per month</div>
</div>
<div class="plan">
<div role="cell">Pro</div>
<div role="cell">$25 per month</div>
</div>
</div>
</div>
Valid example
With role="row" on each wrapper, every cell has a row to belong to.
<div role="table" aria-label="Subscription plans">
<div role="rowgroup">
<div role="row" class="plan">
<div role="cell">Basic</div>
<div role="cell">$10 per month</div>
</div>
<div role="row" class="plan">
<div role="cell">Pro</div>
<div role="cell">$25 per month</div>
</div>
</div>
</div>
A <td> in a data table already has the implicit ARIA role cell, so writing role="cell" on it gives screen readers nothing they don't already get from the HTML.
Browsers map table markup to ARIA roles on their own. The <table> becomes a table, each <tr> a row, each <th> a column or row header, and each <td> a cell. Screen readers use that structure to announce the matching headers as someone moves from cell to cell. An explicit role="cell" leaves all of this unchanged and adds an attribute to every cell in the table.
In most tables you'll see an error next to this warning. When the <table> has no role attribute, or has role="table", role="grid" or role="treegrid", its <td> cells can't have a role attribute at all. Removing role="cell" fixes both messages.
The attribute usually comes from table plugins or from markup copied out of online word processors, which put a role on every row and cell. The cell role exists for tables built from generic elements such as <div>, where nothing in the HTML marks an element as a cell. A native <td> doesn't need it, so delete the attribute. If the same table has roles on its rows or header cells, remove those as well.
Invalid example
<table>
<thead>
<tr>
<th>Region</th>
<th>Delivery time</th>
</tr>
</thead>
<tbody>
<tr>
<td role="cell">Europe</td>
<td role="cell">3 to 5 days</td>
</tr>
<tr>
<td role="cell">North America</td>
<td role="cell">5 to 7 days</td>
</tr>
</tbody>
</table>
Valid example
<table>
<thead>
<tr>
<th>Region</th>
<th>Delivery time</th>
</tr>
</thead>
<tbody>
<tr>
<td>Europe</td>
<td>3 to 5 days</td>
</tr>
<tr>
<td>North America</td>
<td>5 to 7 days</td>
</tr>
</tbody>
</table>
A <td> inside a <table> with no role, or with a role of table, grid or treegrid, gets its ARIA role from the table and must not have a role attribute of its own.
Browsers map table cells automatically. In a plain table, or one with role="table", each <td> is exposed as a cell. In a table with role="grid" or role="treegrid", each <td> is exposed as a gridcell. The ARIA in HTML specification doesn't let authors set any role on these cells, so the validator rejects redundant values like role="cell" and role="gridcell" as well as roles that turn the cell into something else, such as role="button" or role="presentation".
A <td> exposed as a button is no longer a cell, so screen readers can't announce its row and column headers or reach it with table navigation commands.
To make a cell interactive, put a real control such as a <button> or an <a> inside the <td>. For layout tables, which are common in HTML email, set role="presentation" on the <table> instead of on each cell. Once the table has a role other than table, grid or treegrid, its cells lose their table semantics and the rule no longer applies.
Invalid examples
Data table
<table>
<tr>
<th>Name</th>
<th>Actions</th>
</tr>
<tr>
<td role="cell">Alice</td>
<td role="button" tabindex="0">Edit</td>
</tr>
</table>
Layout table
<table>
<tr>
<td role="presentation">Logo</td>
<td role="presentation">Newsletter title</td>
</tr>
</table>
Valid examples
Data table
<table>
<tr>
<th>Name</th>
<th>Actions</th>
</tr>
<tr>
<td>Alice</td>
<td><button type="button">Edit</button></td>
</tr>
</table>
Layout table
<table role="presentation">
<tr>
<td>Logo</td>
<td>Newsletter title</td>
</tr>
</table>
Validate at scale.
Ship accessible websites, faster.
Automated HTML & accessibility validation for large sites. Check thousands of pages against WCAG guidelines and W3C standards in minutes, not days.
1K Weekly
1,000 monthly checks. Cancel anytime.
Get 1K Weekly →Join teams across 40+ countries