HTML Guides for grid
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="row" must sit inside an element whose role is table, grid, treegrid, or rowgroup, or be claimed by one through aria-owns. A row on its own has no structure to belong to, so assistive technologies cannot tell which table or grid it is part of.
The row role describes one row of a composite structure. Those four allowed owners fall into two groups: table and rowgroup for static tabular data, and grid and treegrid for interactive widgets that support keyboard navigation. In every case the container supplies the context a screen reader needs to announce the row's position and its cells. Placed outside such a container, the row is orphaned and the whole table semantics break down.
The most reliable fix is to nest the role="row" element inside its container in the DOM. When your layout prevents that, keep the row where it is and point aria-owns from the container at the row's id.
Invalid example
The role="row" is a sibling of the table, so nothing contains or owns it:
<div role="table" aria-label="Prices"></div>
<div role="row">
<span role="cell">Basic</span>
<span role="cell">$9</span>
</div>
Valid example
Nest the row inside the element with role="table":
<div role="table" aria-label="Prices">
<div role="row">
<span role="cell">Basic</span>
<span role="cell">$9</span>
</div>
</div>
If the DOM nesting is not possible, leave the row in place and add aria-owns="price-row" to the container so assistive technologies still treat it as the owner.
An element with role="rowgroup" needs an ancestor with role="table", role="grid", or role="treegrid", or one of those elements has to claim it through aria-owns.
A rowgroup groups the rows of a table, the way <thead>, <tbody> and <tfoot> do in a native <table>. It only means something inside the structure it divides. With no table, grid or treegrid above it, a screen reader has no table to announce, so it can't report the number of rows and columns or let people move from cell to cell with its table commands.
This often shows up in cards or comparison layouts built from <div> elements. The rows and cells inside carry ARIA roles, but the outer container has only a class for styling. Add role="table" to that container and give it a name with aria-label or aria-labelledby, so screen readers can say which table it is. Pick grid or treegrid only when the widget lets people move between cells with the arrow keys.
The table doesn't have to be the rowgroup's direct parent. The validator accepts plain wrapper elements in between, as long as some ancestor has one of the three roles. When the rowgroup can't sit inside the table in the DOM, give it an id and list that id in aria-owns on the table.
If the content isn't really tabular, such as a short list of features in a card, remove role="rowgroup", role="row" and role="cell" together and use a list or plain text. If you remove only the rowgroup role, the validator reports each row instead. For real tabular data, a native <table> with <tbody>, <tr> and <td> gives you the same structure without any ARIA.
Invalid example
The rows sit in a rowgroup, but the wrapper around it has no table role.
<h2 id="specs-title">Technical details</h2>
<div class="specs">
<div role="rowgroup">
<div role="row">
<span role="cell">Weight</span>
<span role="cell">1.2 kg</span>
</div>
<div role="row">
<span role="cell">Battery life</span>
<span role="cell">18 hours</span>
</div>
</div>
</div>
Valid example
With role="table" and a name on the wrapper, the rowgroup has a table to belong to.
<h2 id="specs-title">Technical details</h2>
<div class="specs" role="table" aria-labelledby="specs-title">
<div role="rowgroup">
<div role="row">
<span role="cell">Weight</span>
<span role="cell">1.2 kg</span>
</div>
<div role="row">
<span role="cell">Battery life</span>
<span role="cell">18 hours</span>
</div>
</div>
</div>
The validator rejects justify-self values it doesn't recognize: usually a typo or a keyword from another alignment property, sometimes the valid but newer anchor-center.
justify-self aligns one box along the inline axis, which is horizontal in left-to-right and right-to-left text, and overrides the container's justify-items for that box. It works on grid items, inside their grid area, and on absolutely positioned boxes. Flexbox ignores it, because a flex container places its items along the main axis with justify-content and auto margins. The validator accepts auto, normal, stretch, center, start, end, self-start, self-end, flex-start, flex-end, left, right and the baseline keywords (baseline, first baseline, last baseline). A position can take a safe or unsafe prefix, as in safe center.
Wrong values often come from neighbouring properties. middle belongs to vertical-align; box alignment calls it center. top and bottom are vertical, and the vertical (block) axis belongs to align-self, which spells them start and end. space-between and space-around spread space across several items and belong to justify-content. legacy only exists on justify-items, so justify-self: legacy right fails while justify-self: right passes. Browsers drop a declaration with an unknown value, and the box falls back to whatever alignment justify-items gives it.
anchor-center is valid CSS that the validator hasn't caught up with. CSS Anchor Positioning adds it to justify-self and align-self so an absolutely positioned element can center itself on its anchor along that axis, for example a tooltip lined up with the middle of its button. The validator reports it even inside @supports (justify-self: anchor-center). When the box isn't absolutely positioned or has no default anchor, the spec says anchor-center behaves as center, so on a grid item you can write center and get the same layout.
If the element really is anchor positioned, keep anchor-center and mute this message for that value.
Invalid example
<div style="display: grid; grid-template-columns: repeat(3, 1fr);">
<span style="justify-self: middle;">Item</span>
<span style="justify-self: legacy right;">Qty</span>
<span style="justify-self: anchor-center;">Price</span>
</div>
Valid example
<div style="display: grid; grid-template-columns: repeat(3, 1fr);">
<span style="justify-self: center;">Item</span>
<span style="justify-self: right;">Qty</span>
<span style="justify-self: center;">Price</span>
</div>
A <tr> element already has an implicit ARIA role of row, so adding role="row" is redundant when the parent <table> uses its default semantics or has a role of table, grid, or treegrid.
HTML tables come with built-in accessibility semantics. The <table> element implicitly has role="table", and <tr> implicitly has role="row". Browsers and assistive technologies already understand this structure, so explicitly adding these roles is unnecessary and flagged by the W3C validator.
The only time you'd need to add a role to a <tr> is when the table's native semantics have been overridden — for example, if the <table> has been repurposed with a non-table role like role="presentation" or role="none". In that case, you'd need explicit ARIA roles to restore row semantics.
Incorrect Example
<table>
<tr role="row">
<th>Name</th>
<th>Email</th>
</tr>
<tr role="row">
<td>Alice</td>
<td>alice@example.com</td>
</tr>
</table>
Fixed Example
Simply remove the redundant role="row" from the <tr> elements:
<table>
<tr>
<th>Name</th>
<th>Email</th>
</tr>
<tr>
<td>Alice</td>
<td>alice@example.com</td>
</tr>
</table>
The same fix applies if your <table> explicitly has role="table", role="grid", or role="treegrid" — the <tr> elements still don't need an explicit role="row" because the browser infers it automatically.
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