/ โ† HTML Checker Fixes / ARIA Errors
In this guide
  1. Validity errors versus accessibility findings
  2. The messages and what they mean
  3. Misspelt and invented names
  4. Roles an element is not allowed to have
  5. Values, references and required structure
  6. The fix, step by step
  7. Where invalid ARIA comes from
  8. Keeping ARIA valid
  9. Questions and answers

Validity errors versus accessibility findings

There are two different questions you can ask about ARIA on a page. The first is whether the markup is valid: does each attribute exist, is each role real and allowed on that element, does each value fit the attribute, and does each reference point at something. The second is whether the result is usable: does every control have a sensible name, can a keyboard user reach it, does a screen reader announce it in a way that makes sense.

This guide deals with the first question, which is what the HTML Checker reports. Validity is the foundation, because an invalid attribute never reaches assistive technology at all. Questions of naming, such as icon buttons with no label or aria-label that contradicts visible text, are covered in the ARIA labels guide in the accessibility section.

The rules the checker applies come from two specifications. WAI-ARIA defines the roles, states and properties and the values each accepts. ARIA in HTML says which roles and attributes each HTML element may carry, for example that a heading may not be given the role of a button.

๐Ÿ’ก The first rule of ARIA use still applies: if a native HTML element already has the meaning and behaviour you need, use it instead of adding a role. Many validation errors disappear when a div with a role is replaced by the element it was imitating.

The messages and what they mean

Checker message (in outline)Typical cause
Bad value presentaton for attribute roleA misspelt role, an invented role such as role="card", or an abstract role.
Bad value button for attribute role on element h2A real role that ARIA in HTML does not allow on that particular element.
Attribute aria-labeledby not allowed on element div at this pointAn attribute name that does not exist, very often a spelling slip.
Bad value yes for attribute aria-expandedA value outside the attribute's allowed set, or an empty value.
The aria-controls attribute must point to an element in the same documentAn ID reference to an element that does not exist in the page.
An element with role=tab must be contained in, or owned by, an element with role=tablistA child role used without the parent role it depends on.
Element is missing a required attribute, such as aria-checkedA widget role used without the state that role requires.
The navigation role is unnecessary for element navA warning: the role repeats what the element already means.

The exact wording varies slightly between validator versions, so search your report for the attribute or role name rather than the full sentence.

Misspelt and invented names

ARIA has a fixed vocabulary. Anything outside it is ignored by browsers, so a typo means the attribute does nothing at all. These are the slips that turn up most often:

WrittenCorrect
aria-labeledbyaria-labelledby (double L, even in American English)
aria-rolerole (there is no aria-role attribute)
aria-description on older validatorsComes from the ARIA 1.3 draft; if your checker rejects it, use aria-describedby pointing at visible or hidden text
role="presentaton", role="dialogue"role="presentation", role="dialog"
role="card", role="dropdown", role="popup"Not ARIA roles. Remove them, or use a real one such as group, listbox or menu only if you implement that pattern fully
aria-hasPopuparia-haspopup (write ARIA attributes in lower case)

Abstract roles

Some role names exist in the specification only to organise the others and must never be used in markup: command, composite, input, landmark, range, roletype, section, sectionhead, select, structure, widget and window. role="section" is a common one, usually meant as region, and a plain <section> element with a heading is normally the better answer.

Roles an element is not allowed to have

ARIA in HTML lists which roles each element may take. The idea is that you should not strip an element of meaning it already has in a way that confuses users. A few patterns cause most of these errors.

A heading turned into a button

Accordions and collapsible sections often put the role on the heading itself. Headings may not take the button role, and doing so removes the heading from the page outline screen reader users navigate by. Put a real button inside the heading instead.

<!-- Invalid -->
<h3 role="button" aria-expanded="false" tabindex="0">Delivery and returns</h3>

<!-- Valid: the heading stays a heading, the button does the work -->
<h3>
  <button type="button" aria-expanded="false" aria-controls="panel-delivery">
    Delivery and returns
  </button>
</h3>
<div id="panel-delivery" hidden>
  <p>Orders placed before 2pm are dispatched the same working day.</p>
</div>

Lists given widget roles

A ul may be given roles such as menu, listbox, tablist or toolbar, but not button or navigation. For site navigation, wrap the list in a <nav> element rather than changing the list's role.

Images with an empty alt and a role

An image with alt="" is declared decorative. Giving it any role other than none or presentation contradicts that, and the checker flags it. Either give it real alt text or remove the role.

Elements that take no role

Some elements, including label, meta, script, style and title, may not carry any role. Form controls such as input type="text" allow very few. If you find yourself wanting to change the role of a form control, it is usually a sign that a different control is needed.

Values, references and required structure

Values must be spelt out

ARIA states are not HTML boolean attributes. Writing aria-hidden or aria-expanded on its own gives an empty value, which does not mean true. Each attribute has a defined set of values:

Watch for template code that prints a variable's value directly. A JavaScript boolean printed as undefined, or a PHP false printed as an empty string, produces an invalid value.

References must resolve

aria-labelledby, aria-describedby, aria-controls, aria-owns, aria-activedescendant and aria-errormessage all take IDs. The first four accept a space-separated list; the last two take a single ID. Each ID must match an element in the same document, exactly, including case. References break when an element is renamed, when a component is rendered only after a click, or when the referenced element sits inside a different iframe or shadow root.

Composite roles need their parts

Some roles only make sense inside a particular parent: tab inside tablist, option inside listbox, menuitem inside menu or menubar, row inside table, grid or treegrid, and listitem inside list. The reverse applies too: a tablist with no tabs is incomplete. Some widget roles also require a state; a role="checkbox" needs aria-checked, and a role="slider" needs aria-valuenow.

<div role="tablist" aria-label="Product information">
  <button type="button" role="tab" id="tab-spec" aria-selected="true" aria-controls="panel-spec">Specification</button>
  <button type="button" role="tab" id="tab-care" aria-selected="false" aria-controls="panel-care" tabindex="-1">Care</button>
</div>
<div role="tabpanel" id="panel-spec" aria-labelledby="tab-spec">โ€ฆ</div>
<div role="tabpanel" id="panel-care" aria-labelledby="tab-care" hidden>โ€ฆ</div>
โš ๏ธ Valid markup is only half a tab widget. The roles promise arrow-key navigation between tabs, and that behaviour has to be written in JavaScript. If you cannot provide it, plain headings and sections are more accessible than half-built tabs.

The fix, step by step

Step 1

Group the ARIA messages by type

Run the HTML Checker on one page of each template and pull out every message that mentions role or an attribute starting with aria-. Sort them into five piles: unknown names, roles not allowed on the element, bad values, broken ID references and missing required structure, with redundant-role warnings as a sixth. Most piles trace back to a small number of components, such as the menu, accordion, tabs or carousel.

Step 2

Correct misspelt attributes and role names

Search the codebase for the exact strings the checker rejected and fix the spelling, such as aria-labeledby to aria-labelledby and aria-role to role. Delete invented roles like card or dropdown, and replace abstract roles such as section or widget with a native element. Write every ARIA attribute and role in lower case, and search templates, JavaScript and CMS content, because the same typo is often copied into several places.

Step 3

Use only roles the element permits

Where the checker says a role is not allowed on an element, change the element rather than forcing the role. Put a button inside a heading instead of making the heading a button, wrap navigation lists in a nav element, and remove roles from images with empty alt text. If a div or span carries a widget role, consider replacing it with the native element that already has that role and its keyboard behaviour.

Step 4

Give every state a value it accepts

Replace bare or empty ARIA attributes with explicit values, such as aria-expanded="false" instead of aria-expanded. Use only the allowed tokens for each attribute, numbers for range and level attributes, and aria-valuetext for human-readable versions. Check template and script code that outputs these values, so that booleans are written as the strings true and false rather than as empty text, undefined or a number.

Step 5

Point ID references at real elements

For each aria-labelledby, aria-describedby, aria-controls and similar attribute, search the rendered page for every ID it names and confirm an element with that exact ID exists, with matching case. Rename references after renaming elements, generate matching IDs together in reusable components, and render the referenced element in the page from the start, hiding it with the hidden attribute, rather than adding it only when it opens.

Step 6

Complete the structure composite roles need

Check that every child role sits inside its required parent, with no stray wrapper element carrying a conflicting role in between: tabs in a tablist, options in a listbox, menu items in a menu. Add the required state to widget roles, such as aria-checked on a custom checkbox or aria-valuenow on a slider. If the component cannot support the full pattern, including keyboard behaviour, replace it with simpler native HTML.

Step 7

Strip redundant and deprecated ARIA

Remove roles that repeat what the element already means, such as role="navigation" on nav, role="button" on button and role="main" on main. Replace deprecated attributes like aria-grabbed and aria-dropeffect, and the deprecated directory role, with current patterns, such as a live region that announces what was picked up and where it was dropped, and a plain list in place of a directory. Rerun the HTML Checker, then test the affected components with a keyboard and a screen reader to confirm they still announce correctly.

Where invalid ARIA comes from

For content-managed sites, search the database for aria- and role= inside post content too. Editors sometimes paste ARIA from elsewhere without knowing what it does.

Keeping ARIA valid

Questions and answers

1. What happens to an ARIA attribute the browser does not recognise?

It is ignored. Browsers build the accessibility tree from attributes they know, so a misspelt attribute such as aria-labeledby, or an invented role such as card, has no effect at all. The control behaves as if the attribute were not there, which usually means a screen reader announces it without the name, state or relationship you intended. That is why these are worth fixing even though the page looks the same: the ARIA you wrote is not doing anything.

2. Is it aria-labelledby or aria-labeledby?

It is aria-labelledby, with a double L. The specification uses that spelling for this attribute even though it is otherwise written in American English, and aria-labeledby with one L is not recognised by browsers. The same double L does not apply elsewhere; the related attribute is aria-describedby. Search your codebase and content for the single-L version, because it is one of the most common ARIA errors the HTML Checker reports and it silently leaves controls without their intended label.

3. Why can't I put role=button on a heading?

ARIA in HTML restricts which roles each element may take, and headings are only allowed a few, such as none, presentation or tab. Turning a heading into a button would remove it from the list of headings screen reader users jump between, so part of the page outline disappears. The valid pattern for an accordion or collapsible section is a real button element inside the heading. It keeps the heading, and the button brings keyboard support without extra code.

4. Should I remove redundant roles like role=navigation on nav?

Yes, in most cases. The nav element already has the navigation role, so adding it again does nothing useful, and the checker reports it as unnecessary. The advice to add both dates from a time when some browsers and screen readers did not map the newer HTML elements; current browsers do. Removing redundant roles keeps markup tidy and avoids mistakes where the role and element later drift apart, for example when someone changes the element but leaves the role behind.

5. Can I write aria-hidden without a value like a normal boolean attribute?

No. ARIA states are not HTML boolean attributes. Writing aria-hidden on its own gives it an empty value, which is not the same as true, and the checker reports it as a bad value. Always write the value in full: aria-hidden equals true, aria-expanded equals false, and so on. Check template code that prints a variable directly, because an unset variable can produce an empty or undefined value that breaks the attribute without anyone noticing.

6. Why does the checker say my aria-controls must point to an element in the same document?

Because the ID it names does not exist in the page the checker received. Common causes are a typo or case difference in the ID, an element that was renamed without updating the reference, and panels or menus that are only added to the page when they are opened. Render the controlled element from the start and hide it with the hidden attribute, and generate the ID and the reference together so they cannot drift apart.

7. Are ARIA validation errors a WCAG failure?

Not automatically. WCAG 2.2 removed the old parsing criterion, so an invalid attribute on its own is no longer a failure. What matters is the effect. If a misspelt attribute leaves a button with no accessible name, or a bad role means a custom checkbox does not report whether it is checked, that fails criteria such as 4.1.2 Name, Role, Value. Fixing validation errors is the quickest way to remove those effects. This is not legal advice about any accessibility obligation.

Check the ARIA in your templates

The HTML Checker validates your markup against W3C standards and reports errors and warnings covering the doctype, encoding, tag nesting, duplicate IDs, ARIA attributes, form semantics and the language attribute, so you can fix each component at its source.

Open HTML Checker Fixes โ†’
๐Ÿ’ฌ Got a problem?
Related Guides: All HTML Checker Fixes  ·  Fixing ARIA Labels and Accessible Names  ·  Fixing Unclosed and Mismatched Tags  ·  HTML Checker Guide  ·  Learning Hub

About aiwebpageseo

aiwebpageseo.com is a data-driven SEO and AEO (Answer Engine Optimisation) platform providing a free suite of technical SEO tools. Rather than relying on AI-theorised assumptions, the platform analyses live URL performance, delivering objective diagnostics, page speed metrics, CLS debugging, and site crawl data alongside actionable technical tutorials.