Skip to main content
HTML Validation

An element with “role=columnheader” must be contained in, or owned by, an element with “role=row”.

About This HTML Issue

The ARIA specification defines a strict ownership hierarchy for table-related roles. A columnheader represents a header cell for a column, analogous to a <th> element in native HTML. For assistive technologies like screen readers to correctly announce column headers and associate them with the data cells beneath them, the columnheader must exist within a row context. The required structure is:

  • An element with role="table", role="grid", or role="treegrid" serves as the container.
  • Inside it, elements with role="rowgroup" (optional) or role="row" organize the rows.
  • Each role="row" element contains one or more elements with role="columnheader", role="rowheader", or role="cell".

When a role="columnheader" element is placed directly inside a role="table" or role="grid" container — or any other element that is not role="row" — the validator raises this error. Without the row wrapper, screen readers cannot navigate the table structure properly. Users who rely on assistive technology may hear disjointed content or miss the column headers entirely, making the data table unusable.

The best practice, whenever feasible, is to use native HTML table elements (<table>, <thead>, <tr>, <th>, <td>). These carry implicit ARIA roles and establish the correct ownership relationships automatically, eliminating this entire category of errors. Only use ARIA table roles when you genuinely cannot use native table markup — for example, when building a custom grid widget with non-table elements for layout reasons.

Examples

Incorrect: columnheader not inside a row

In this example, the columnheader elements are direct children of the table container, with no role="row" wrapper:

<div role="table" aria-label="Employees">
  <div role="columnheader">Name</div>
  <div role="columnheader">Department</div>
  <div role="row">
    <div role="cell">Alice</div>
    <div role="cell">Engineering</div>
  </div>
</div>

Correct: columnheader inside a row

Wrapping the column headers in an element with role="row" fixes the issue:

<div role="table" aria-label="Employees">
  <div role="row">
    <div role="columnheader">Name</div>
    <div role="columnheader">Department</div>
  </div>
  <div role="row">
    <div role="cell">Alice</div>
    <div role="cell">Engineering</div>
  </div>
</div>

Correct: Using rowgroup for additional structure

You can optionally use role="rowgroup" to separate headers from body rows, similar to <thead> and <tbody>. The columnheader elements must still be inside a row:

<div role="table" aria-label="Employees">
  <div role="rowgroup">
    <div role="row">
      <div role="columnheader">Name</div>
      <div role="columnheader">Department</div>
    </div>
  </div>
  <div role="rowgroup">
    <div role="row">
      <div role="cell">Alice</div>
      <div role="cell">Engineering</div>
    </div>
  </div>
</div>

Best practice: Use native table elements

Native HTML tables have built-in semantics that make ARIA roles unnecessary. A <th> inside a <tr> already behaves as a columnheader inside a row:

<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Department</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Alice</td>
      <td>Engineering</td>
    </tr>
  </tbody>
</table>

This approach is simpler, more robust across browsers and assistive technologies, and avoids the risk of ARIA misuse. Reserve ARIA table roles for situations where native table markup is not an option.

Find issues like this automatically

Rocket Validator scans thousands of pages in seconds, detecting HTML issues across your entire site.

Help us improve our guides

Was this guide helpful?

Ready to validate your sites?
Start your free trial today.