HTML Guides for heading
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.
The ARIA in HTML specification defines which roles are allowed on each HTML element. Heading elements (<h1>–<h6>) have an implicit role of heading, and the set of roles they can be explicitly assigned is limited. The button role is not among them, so applying role="button" directly to a heading element is invalid.
This matters for several reasons. First, headings play a critical role in document structure and accessibility — screen reader users rely on headings to navigate and understand the page hierarchy. Assigning role="button" to a heading overrides its semantic meaning, which confuses assistive technologies. Second, browsers and screen readers may handle conflicting semantics unpredictably, leading to an inconsistent experience for users. Third, it violates the W3C HTML specification, which means your markup won’t pass validation.
Why This Combination Is Problematic
When you apply role="button" to an element, assistive technologies treat it as an interactive button. This completely replaces the element’s native heading semantics. Users who navigate by headings would no longer find that heading in their list, and users who navigate by interactive controls would encounter a “button” that may not behave like one (lacking keyboard support, focus management, etc.).
If you genuinely need something that looks like a heading but acts as a button, there are valid approaches to achieve this without breaking semantics.
How to Fix It
There are several strategies depending on your intent:
-
Use a <button> element styled as a heading. This is often the cleanest approach when the primary purpose is interactivity. You can style the button with CSS to match your heading appearance.
-
Wrap the heading in a <div> with role="button". This preserves the heading in the document outline while making the wrapper interactive. However, be aware that the button role applies role="presentation" to all descendant elements, meaning assistive technologies will strip the heading semantics from the <h2> inside it. The text content remains accessible, but it won’t be recognized as a heading.
-
Place a <button> inside the heading. This keeps the heading semantics intact for document structure while making the text inside it interactive. This pattern is commonly used for accordion-style components and is the approach recommended by the WAI-ARIA Authoring Practices.
Examples
❌ Invalid: role="button" on a heading
<h2 role="button">Toggle Section</h2>
This triggers the validation error because button is not an allowed role for heading elements.
✅ Fix: Use a <button> styled as a heading
<button type="button" class="heading-style">Toggle Section</button>
.heading-style {
font-size: 1.5em;
font-weight: bold;
background: none;
border: none;
cursor: pointer;
}
✅ Fix: Wrap the heading in a container with role="button"
<div role="button" tabindex="0">
<h2>Toggle Section</h2>
</div>
Note that this approach causes the <h2> to lose its heading semantics for assistive technologies, since the button role does not support semantic children. Also remember to add tabindex="0" so the element is keyboard-focusable, and implement keydown handlers for Enter and Space to replicate native button behavior.
✅ Fix: Place a <button> inside the heading (recommended for accordions)
<h2>
<button type="button" aria-expanded="false">
Toggle Section
</button>
</h2>
This is the most robust pattern. The heading remains in the document outline, and the button inside it is fully interactive with built-in keyboard support. Screen reader users can find it both when navigating by headings and when navigating by interactive elements. The aria-expanded attribute communicates whether the associated section is open or closed.
Headings play a critical role in structuring a web page. They create an outline of the document that both users and machines rely on. Screen readers, for example, allow users to navigate a page by jumping between headings, making them one of the most important tools for accessible navigation. When a heading is empty, screen readers may announce “heading level 2” (or similar) with no accompanying text, leaving users confused about what section they’ve entered.
Empty headings also signal a structural problem. They often appear when developers use heading elements purely for spacing or styling purposes, when content is meant to be injected dynamically via JavaScript but the script fails, or when headings are left as placeholders during development and never filled in.
The W3C validator flags this as a warning because the HTML specification states that headings represent the topic of their section. An empty heading cannot fulfill this purpose. While it is technically parseable HTML, it is semantically meaningless and degrades the quality of the document.
How to fix it
- Add descriptive text to the heading that accurately represents the content of its section.
- Remove the empty heading if it serves no purpose. Don’t keep empty headings as spacers — use CSS margins or padding instead.
- If content is loaded dynamically, consider adding the heading element via JavaScript at the same time as its content, rather than leaving an empty shell in the markup.
- If you’re hiding the heading visually but still want it available for screen readers, use a visually-hidden CSS technique rather than leaving it empty.
Examples
❌ Empty headings (triggers the warning)
<h1></h1>
<h2> </h2>
<h3>
</h3>
All three of these are considered empty — even the ones containing whitespace or a newline.
❌ Using an empty heading for spacing
<h2></h2>
<p>This paragraph needs some space above it.</p>
This misuses a heading element for visual layout purposes.
✅ Heading with meaningful content
<h1>Welcome to Our Store</h1>
<h2>Featured Products</h2>
<p>Check out our latest arrivals.</p>
✅ Using CSS for spacing instead of empty headings
<p class="section-intro">This paragraph needs some space above it.</p>
.section-intro {
margin-top: 2rem;
}
✅ Visually hidden heading for screen readers
If you need a heading that is available to assistive technologies but not visible on screen, include text and hide it with CSS:
<h2 class="visually-hidden">Navigation Menu</h2>
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
</ul>
</nav>
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
✅ Adding headings dynamically with their content
Instead of placing an empty heading in the HTML and populating it later, create the heading along with its content:
<div id="results"></div>
<script>
const container = document.getElementById("results");
const heading = document.createElement("h2");
heading.textContent = "Search Results";
container.appendChild(heading);
</script>
This approach avoids any moment where an empty heading exists in the DOM, ensuring validity and accessibility at all times.
According to the HTML specification, heading elements (h1–h6) have a content model of “phrasing content,” which means they can only contain inline-level elements like span, strong, em, a, and text nodes. Other heading elements are not phrasing content — they are flow content — so placing one heading inside another is invalid HTML.
This matters for several reasons. Screen readers and other assistive technologies rely on a well-formed heading hierarchy to help users navigate a page. When headings are nested inside each other, the document outline becomes broken and confusing, making it harder for users to understand the structure of the content. Browsers may also attempt to “fix” the invalid markup by auto-closing the outer heading before starting the inner one, which can produce unexpected rendering and DOM structures that differ from what you intended.
There are two common causes of this error:
-
Intentionally nesting headings for styling. Developers sometimes nest an h2 inside an h1 hoping to create a visual “main heading + subtitle” pattern. This is invalid. Instead, use separate heading elements or use a span or p element for the subtitle.
-
A missing or malformed closing tag. If you accidentally write <h3> instead of </h3> for a closing tag, the browser sees two opening h3 tags in a row. The first heading is never properly closed, and the second heading appears to be nested inside it.
Examples
❌ Heading nested inside another heading
<h1>Main heading
<h2>Sub heading</h2>
</h1>
The h2 is a child of the h1, which is not allowed. To create a heading with a subtitle, use separate elements:
✅ Headings as siblings
<h1>Main heading</h1>
<h2>Sub heading</h2>
Or, if the subtitle should be part of a sectioned document structure:
<main>
<h1>Main heading</h1>
<section>
<h2>Section heading</h2>
<p>Paragraph content</p>
</section>
</main>
❌ Missing closing slash causes nesting
A very common typo is forgetting the / in the closing tag:
<h3>Meet the Feebles<h3>
<h3>Bad Taste<h3>
Here, <h3>Meet the Feebles<h3> opens a second h3 instead of closing the first one. The validator sees the second h3 as a child of the first. The same problem cascades to subsequent headings.
✅ Properly closed heading tags
<h3>Meet the Feebles</h3>
<h3>Bad Taste</h3>
❌ Using nested headings for visual hierarchy inside a heading
<h1>
Our Company
<h3>Established 1999</h3>
</h1>
✅ Using a span for supplementary text within a heading
<h1>
Our Company
<span class="subtitle">Established 1999</span>
</h1>
You can then style the .subtitle class with CSS to achieve the desired visual appearance — for example, displaying it on a new line with a smaller font size:
.subtitle {
display: block;
font-size: 0.5em;
font-weight: normal;
}
✅ Using the hgroup element
The hgroup element is specifically designed for grouping a heading with related content like subtitles:
<hgroup>
<h1>Our Company</h1>
<p>Established 1999</p>
</hgroup>
This keeps the heading hierarchy clean while semantically associating the subtitle with the heading. The hgroup element is supported in the current HTML living standard and works well with assistive technologies.
The th element has a specific role in HTML: it defines a header cell within a table. It already carries implicit heading semantics through its association with the rows or columns it describes. When you place an h1–h6 element inside a th, you’re nesting one type of heading structure inside another, which violates the HTML content model. The HTML specification explicitly excludes heading elements from the allowed content of th.
This causes several problems:
- Document outline confusion: Heading elements contribute to the document’s outline and sectioning structure. Placing them inside table headers injects unexpected entries into the outline that don’t represent actual document sections, making navigation unpredictable.
- Accessibility issues: Screen readers treat headings and table headers differently. A heading inside a th creates conflicting signals—assistive technology may announce the content as both a table header and a document heading, confusing users who rely on either navigation method.
- Standards compliance: Browsers may handle this invalid nesting inconsistently, leading to unpredictable rendering or behavior across different environments.
If your goal is to make the text inside a th visually larger or bolder, use CSS instead. The th element is already rendered bold by default in most browsers, and you can further style it with font-size, font-weight, or any other CSS property.
Examples
Incorrect: heading inside a th element
<table>
<tr>
<th><h1>Product</h1></th>
<th><h1>Price</h1></th>
</tr>
<tr>
<td>Widget</td>
<td>$25</td>
</tr>
</table>
This triggers the validation error because h1 elements are nested inside th elements.
Fixed: plain text in th, heading moved outside the table
<h1>Product Pricing</h1>
<table>
<tr>
<th>Product</th>
<th>Price</th>
</tr>
<tr>
<td>Widget</td>
<td>$25</td>
</tr>
</table>
The heading now introduces the table as a whole, and the th elements contain plain text.
Fixed: styling th with CSS instead of using headings
If you want the table headers to have a specific visual appearance, apply CSS directly to the th elements:
<style>
.styled-table th {
font-size: 1.5rem;
font-weight: bold;
text-transform: uppercase;
}
</style>
<table class="styled-table">
<tr>
<th>Product</th>
<th>Price</th>
</tr>
<tr>
<td>Widget</td>
<td>$25</td>
</tr>
</table>
Fixed: using caption for a table title
If the heading was meant to serve as a title for the table, the caption element is the semantically correct choice:
<table>
<caption>Product Pricing</caption>
<tr>
<th>Product</th>
<th>Price</th>
</tr>
<tr>
<td>Widget</td>
<td>$25</td>
</tr>
</table>
The caption element is specifically designed to label a table and is properly associated with it for assistive technology. You can style it with CSS to achieve any visual appearance you need.
The HTML specification defines a strict content model for the th element: it accepts flow content, but specifically excludes header, footer, sectioning content, and heading content (h1–h6). This restriction exists because th elements are themselves headers — they describe the data in their corresponding row or column. Placing a heading element inside a th creates a conflict in the document outline and semantic structure.
This matters for several reasons:
- Accessibility: Screen readers use headings to build a navigable document outline. Headings buried inside table header cells can confuse assistive technology, making it harder for users to understand the page structure and navigate between sections.
- Document outline: Heading elements define the hierarchical structure of a document’s content. When headings appear inside table cells, they disrupt this hierarchy and create unexpected, often meaningless, sections in the outline.
- Standards compliance: Browsers may handle this invalid nesting inconsistently, and the W3C validator will flag it as an error.
A common reason developers place headings in th cells is to achieve a specific visual style — larger or bolder text. The correct approach is to use CSS to style the th content directly, keeping the markup clean and valid.
How to Fix It
- Remove the heading element from inside the th.
- Move the heading above the table if you need a title or section heading for the table.
- Use CSS to style the th text if you need a particular visual appearance.
- Use the caption element if you want to provide a visible title that is semantically associated with the table.
Examples
❌ Incorrect: Heading inside a th element
<table>
<tr>
<th><h2>Product</h2></th>
<th><h2>Price</h2></th>
</tr>
<tr>
<td>Widget</td>
<td>$25</td>
</tr>
</table>
This triggers the validation error because h2 elements are not permitted as descendants of th.
✅ Correct: Plain text inside th, heading moved outside
<h2>Product Pricing</h2>
<table>
<tr>
<th>Product</th>
<th>Price</th>
</tr>
<tr>
<td>Widget</td>
<td>$25</td>
</tr>
</table>
✅ Correct: Using caption for the table title
<table>
<caption>Product Pricing</caption>
<tr>
<th>Product</th>
<th>Price</th>
</tr>
<tr>
<td>Widget</td>
<td>$25</td>
</tr>
</table>
The caption element is the semantically appropriate way to give a table a title. It is announced by screen readers in context with the table, providing a better experience than a heading placed before the table.
✅ Correct: Styling th with CSS for visual emphasis
If the heading was added purely for visual effect, use CSS instead:
<style>
.prominent-header th {
font-size: 1.5em;
font-weight: bold;
color: #333;
}
</style>
<table class="prominent-header">
<tr>
<th>Product</th>
<th>Price</th>
</tr>
<tr>
<td>Widget</td>
<td>$25</td>
</tr>
</table>
This gives you full control over the appearance of header cells without breaking the document structure or introducing validation errors. Remember: th elements are already semantically headers, so there’s no need to wrap their content in heading elements.
The th element is specifically designed to represent a header cell in a table. It inherently conveys header semantics to browsers, screen readers, and other assistive technologies. When you place a heading element like h3 inside a th, you’re creating a structural conflict — the content is simultaneously acting as a table header and a document section heading. The HTML specification restricts the content model of th to “flow content, but with no header, footer, sectioning content, or heading content descendants.”
This matters for several reasons:
- Accessibility: Screen readers use heading elements to build a document outline and allow users to navigate between sections. A heading buried inside a table header cell disrupts this navigation, creating confusion about the page structure. The th element already communicates its role as a header through the table’s own semantics.
- Document structure: Headings define the hierarchical structure of a document. Placing them inside table cells implies that a new document section begins within the table, which is almost never the intended meaning.
- Standards compliance: Browsers may handle this invalid nesting inconsistently, leading to unpredictable rendering or accessibility tree representations.
The fix is straightforward: remove the heading element from inside the th. If the text inside the th needs to be visually larger or bolder, apply CSS styles directly to the th element or use a span with a class. If the heading was meant to title the entire table, move it outside the table or use the caption element.
Examples
❌ Incorrect: heading inside th
<table>
<tr>
<th>Month</th>
<th><h3>Revenue</h3></th>
</tr>
<tr>
<td>January</td>
<td>$500</td>
</tr>
</table>
The h3 inside the second th triggers the validation error.
✅ Fixed: remove the heading, use plain text
<table>
<tr>
<th>Month</th>
<th>Revenue</th>
</tr>
<tr>
<td>January</td>
<td>$500</td>
</tr>
</table>
The th element already communicates that “Revenue” is a header. No heading element is needed.
✅ Fixed: use CSS for visual styling
If the heading was used to make the text look bigger or styled differently, apply CSS to the th instead:
<table>
<tr>
<th>Month</th>
<th class="prominent">Revenue</th>
</tr>
<tr>
<td>January</td>
<td>$500</td>
</tr>
</table>
<style>
.prominent {
font-size: 1.2em;
font-weight: bold;
}
</style>
✅ Fixed: use caption for a table title
If the heading was meant to describe the entire table, use the caption element:
<table>
<caption>Monthly Revenue</caption>
<tr>
<th>Month</th>
<th>Revenue</th>
</tr>
<tr>
<td>January</td>
<td>$500</td>
</tr>
</table>
The caption element is the semantically correct way to provide a title or description for a table. You can style it with CSS to match the appearance of a heading. If you still need a heading in the document outline to precede the table, place it before the table element:
<h3>Revenue Report</h3>
<table>
<tr>
<th>Month</th>
<th>Revenue</th>
</tr>
<tr>
<td>January</td>
<td>$500</td>
</tr>
</table>
This approach keeps the document structure clean while maintaining proper table semantics. The same rule applies to all heading levels — h1, h2, h3, h4, h5, and h6 are all equally invalid inside th (and td) elements.
The th element already carries semantic meaning as a table header cell. Nesting a heading element like h4 inside it creates a conflict in the document’s outline and semantic structure. Screen readers and other assistive technologies treat headings and table headers as distinct navigational landmarks, so combining them can confuse users who rely on these tools to understand page structure. A heading buried inside a table cell may break the expected heading hierarchy, making it harder for users to navigate by headings.
According to the HTML specification, the content model of th is “flow content, but with no header, footer, sectioning content, or heading content descendants.” This means h1, h2, h3, h4, h5, and h6 are all explicitly disallowed inside th.
The reason developers often place headings inside th is to achieve a specific visual style — larger, bolder text. But th elements are already rendered bold by default in most browsers, and any additional styling should be handled with CSS rather than repurposing heading elements.
How to Fix It
- Remove the heading element from inside the th and use the text directly.
- Style with CSS if you need the th content to look different from default styling.
- Move the heading outside the table if it serves as a title or caption for the table. Consider using the <caption> element for table titles.
Examples
❌ Incorrect: Heading inside th
<table>
<tr>
<th><h4>Product</h4></th>
<th><h4>Price</h4></th>
</tr>
<tr>
<td>Widget</td>
<td>$25</td>
</tr>
</table>
This triggers the validation error because h4 elements are not allowed as descendants of th.
✅ Fixed: Plain text in th
<table>
<tr>
<th>Product</th>
<th>Price</th>
</tr>
<tr>
<td>Widget</td>
<td>$25</td>
</tr>
</table>
The simplest fix — just remove the heading tags. The th element already conveys that these cells are headers.
✅ Fixed: Using CSS for custom styling
If you need the header cells to have a specific visual appearance, use CSS:
<table>
<tr>
<th class="styled-header">Product</th>
<th class="styled-header">Price</th>
</tr>
<tr>
<td>Widget</td>
<td>$25</td>
</tr>
</table>
<style>
.styled-header {
font-size: 1.2em;
text-transform: uppercase;
}
</style>
✅ Fixed: Moving the heading outside and using caption
If the heading was meant to serve as a title for the table, use the <caption> element instead:
<table>
<caption>Product Pricing</caption>
<tr>
<th>Product</th>
<th>Price</th>
</tr>
<tr>
<td>Widget</td>
<td>$25</td>
</tr>
</table>
The <caption> element is purpose-built for labeling tables and is well-supported by assistive technologies. You can also place a heading before the table if it fits your document’s heading hierarchy:
<h4>Product Pricing</h4>
<table>
<tr>
<th>Product</th>
<th>Price</th>
</tr>
<tr>
<td>Widget</td>
<td>$25</td>
</tr>
</table>
Both approaches keep your HTML valid while preserving clear semantics for both visual users and assistive technology.
The th element is specifically designed to act as a header cell within a table. It already carries implicit heading semantics — screen readers announce th content as a header when navigating table cells. When you place an h5 (or any h1–h6) inside a th, you’re creating a conflict: the content is simultaneously a table header and a document section heading. This breaks the document’s outline structure and creates confusing behavior for assistive technologies, which may announce the content as both a table header and a section heading.
The HTML specification restricts the content model of th to “flow content, but with no header, footer, sectioning content, or heading content descendants.” Heading elements (h1 through h6) fall under heading content, so placing any of them inside a th is invalid.
This issue typically arises when developers want the text inside a th to look like a heading — larger, bolder, or styled differently. The correct approach is to use CSS to style the th content directly, rather than wrapping it in a heading element.
How to Fix It
- Remove the heading element from inside the th.
- Keep the text content directly inside the th.
- Use CSS to apply any desired visual styling to the th element.
- If the heading is meant to describe the entire table (not just a column), move it outside the table or use the <caption> element.
Examples
❌ Incorrect: Heading inside a th
<table>
<tr>
<th><h5>Product</h5></th>
<th><h5>Price</h5></th>
</tr>
<tr>
<td>Widget</td>
<td>$9.99</td>
</tr>
</table>
This triggers the validation error because h5 elements are nested inside th elements.
✅ Correct: Plain text inside th, styled with CSS
<table>
<tr>
<th class="table-heading">Product</th>
<th class="table-heading">Price</th>
</tr>
<tr>
<td>Widget</td>
<td>$9.99</td>
</tr>
</table>
<style>
.table-heading {
font-size: 1.1em;
font-weight: bold;
text-transform: uppercase;
}
</style>
The th elements already convey header semantics. CSS handles the visual presentation without introducing invalid markup.
✅ Correct: Using <caption> for a table title
If the heading was meant to describe the table as a whole, use <caption> instead:
<table>
<caption>Monthly Revenue</caption>
<tr>
<th>Month</th>
<th>Revenue</th>
</tr>
<tr>
<td>January</td>
<td>$500</td>
</tr>
</table>
✅ Correct: Heading placed before the table
If you need a document-level heading that introduces the table, place it outside:
<h5>Revenue per Month</h5>
<table>
<tr>
<th>Month</th>
<th>Revenue</th>
</tr>
<tr>
<td>January</td>
<td>$500</td>
</tr>
</table>
This keeps the document outline clean and avoids nesting headings inside table cells. The same rule applies to all heading levels — h1, h2, h3, h4, h5, and h6 are all equally invalid inside th (and td) elements.
The <th> element already carries semantic meaning as a table header cell. Placing a heading element like <h6> inside it creates a conflict in the document’s semantic structure. Screen readers and other assistive technologies use headings to build a navigable outline of the page, and they also interpret <th> elements as table headers. Nesting one inside the other produces a confusing, redundant structure that can mislead assistive technologies about the page’s organization and the table’s meaning.
According to the WHATWG HTML living standard, the content model for <th> is “flow content, but with no header, footer, sectioning content, or heading content descendants.” This means <h1>, <h2>, <h3>, <h4>, <h5>, and <h6> are all explicitly disallowed inside <th>.
People commonly make this mistake when trying to visually style table header text to look bolder or larger. Since <th> cells are already rendered bold by default in most browsers, and CSS gives you full control over font size, weight, and appearance, there’s no need to use heading elements for visual styling inside table headers.
How to Fix It
- Remove the heading element from inside the <th> and place the text directly inside the <th>.
- Use CSS if you need the table header text to appear larger or styled differently.
- Use a <caption> element if the heading was meant to serve as a title for the table, or place a heading element before the <table>.
Examples
❌ Incorrect: Heading inside <th>
<table>
<tr>
<th><h6>Product</h6></th>
<th><h6>Price</h6></th>
</tr>
<tr>
<td>Widget</td>
<td>$19.99</td>
</tr>
</table>
This triggers the validation error because <h6> elements are descendants of <th> elements.
✅ Correct: Plain text inside <th>
<table>
<tr>
<th>Product</th>
<th>Price</th>
</tr>
<tr>
<td>Widget</td>
<td>$19.99</td>
</tr>
</table>
The <th> element is already semantically a header, so no additional heading tag is needed.
✅ Correct: Styled <th> with CSS
If you need the header cells to have a specific visual appearance, use CSS:
<style>
.table-header {
font-size: 0.85rem;
text-transform: uppercase;
letter-spacing: 0.05em;
}
</style>
<table>
<tr>
<th class="table-header">Product</th>
<th class="table-header">Price</th>
</tr>
<tr>
<td>Widget</td>
<td>$19.99</td>
</tr>
</table>
✅ Correct: Using <caption> for a table title
If the heading was intended as a title for the entire table, use the <caption> element instead:
<table>
<caption>Monthly Revenue</caption>
<tr>
<th>Month</th>
<th>Revenue</th>
</tr>
<tr>
<td>January</td>
<td>$500</td>
</tr>
</table>
The <caption> element is the semantically correct way to provide a title for a table. It is announced by screen readers and associated directly with the table, giving users proper context. You can also place a heading before the <table> element if a <caption> doesn’t suit your layout needs.
HTML heading elements (<h1> through <h6>) have built-in semantic meaning that browsers and assistive technologies already understand. According to the WAI-ARIA specification, each of these elements carries an implicit heading role with a corresponding aria-level — <h1> has aria-level="1", <h2> has aria-level="2", and so on. When you explicitly add role="heading" to one of these elements, you’re telling the browser something it already knows, which clutters your markup without providing any benefit.
This pattern is part of a broader principle in ARIA authoring known as the first rule of ARIA: don’t use ARIA when a native HTML element already provides the semantics you need. Redundant ARIA roles can cause confusion for developers maintaining the code, as it suggests that the role might be necessary or that the element might not otherwise be recognized as a heading. In some edge cases, adding an explicit aria-level that doesn’t match the heading level (e.g., aria-level="3" on an <h1>) can create conflicting information for screen readers, leading to an inconsistent experience for users of assistive technologies.
The role="heading" attribute is designed for situations where you need to give heading semantics to a non-heading element, such as a <div> or <span>. In those cases, you must also include the aria-level attribute to specify the heading’s level. However, whenever possible, using native heading elements is always preferred over this ARIA-based approach.
How to fix it
- Remove role="heading" from any <h1> through <h6> element.
- Remove aria-level if it was added alongside the redundant role and matches the heading’s native level.
- If you genuinely need a non-standard element to act as a heading, use role="heading" with aria-level on that element instead — but prefer native heading elements whenever possible.
Examples
❌ Redundant role on a native heading
<h1 role="heading" aria-level="1">Welcome to My Site</h1>
<h2 role="heading">About Us</h2>
<h3 role="heading" aria-level="3">Our Mission</h3>
All three headings will trigger the validator warning. The role="heading" and aria-level attributes are completely unnecessary here because the elements already convey this information natively.
✅ Native headings without redundant roles
<h1>Welcome to My Site</h1>
<h2>About Us</h2>
<h3>Our Mission</h3>
Simply removing the redundant attributes resolves the issue while preserving full accessibility.
✅ Correct use of the heading role on a non-heading element
In rare cases where you cannot use a native heading element, the heading role is appropriate on a generic element:
<div role="heading" aria-level="2">Section Title</div>
This tells assistive technologies to treat the <div> as a level-2 heading. Note that aria-level is required here since a <div> has no implicit heading level. That said, using a native <h2> is always the better choice:
<h2>Section Title</h2>
❌ Conflicting aria-level on a native heading
Be especially careful with this anti-pattern, where the explicit level contradicts the element:
<h1 role="heading" aria-level="3">Page Title</h1>
This sends mixed signals — the element is an <h1> but claims to be level 3. Screen readers may behave unpredictably. If you need a level-3 heading, use <h3>:
<h3>Page Title</h3>
Ready to validate your sites?
Start your free trial today.