HTML Guides for aria-controls
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-controls attribute establishes a programmatic relationship between an interactive element (like a button or link) and the content it controls. Assistive technologies such as screen readers use this relationship to help users navigate between a control and the region it affects—for example, a button that toggles a panel or a tab that switches visible content. When the attribute is present but empty (aria-controls=""or aria-controls=" "), it signals a broken relationship: the browser and assistive technology expect a target element but find nothing.
This issue commonly occurs when aria-controls is added by a JavaScript framework or template before the target element’s id is known, leaving behind an empty placeholder. It can also happen when a CMS or component library outputs the attribute unconditionally, even when no controlled region exists.
Why this matters
- Accessibility: Screen readers may announce that a control is associated with another element, but an empty reference leads nowhere. This creates a confusing or misleading experience for users who rely on assistive technology.
- Standards compliance: The HTML specification defines aria-controls as an IDREFS attribute, meaning its value must contain one or more space-separated tokens, each matching the id of an element in the same document. An empty value is invalid per both the WAI-ARIA specification and the HTML standard.
- Maintainability: Empty or placeholder ARIA attributes are a sign of incomplete implementation. They can mask bugs in dynamic UIs where the controlled element was supposed to be rendered but wasn’t, or where an id was accidentally removed during refactoring.
How to fix it
- If the element controls another region, set aria-controls to the id of that region. Make sure the target element exists in the DOM and has a unique id.
- If the element doesn’t control anything, remove the aria-controls attribute entirely. An absent attribute is always better than an empty one.
- For dynamically rendered content, only add aria-controls after the target element and its id are present in the DOM. If using a framework, conditionally render the attribute.
- Keep references in sync. If you rename or remove an id, update every aria-controls that references it.
Examples
Invalid: empty aria-controls
This triggers the validator error because the attribute value contains no id reference.
<a href="#" aria-controls="">Toggle details</a>
Invalid: whitespace-only value
A value with only spaces is also invalid—IDREFS requires at least one non-whitespace token.
<button type="button" aria-controls=" ">Open menu</button>
Fixed: provide a valid id reference
Point aria-controls to the id of the element being controlled.
<button type="button" aria-controls="details-panel" aria-expanded="false">
Toggle details
</button>
<div id="details-panel" hidden>
<p>Here are some additional details.</p>
</div>
Fixed: controlling multiple regions
You can reference multiple id values separated by spaces. Each id must correspond to an element in the document.
<button type="button" aria-controls="filters results">
Show filters and results
</button>
<section id="filters" hidden>
<p>Filter options...</p>
</section>
<section id="results" hidden>
<p>Search results...</p>
</section>
Fixed: remove the attribute when not needed
If the element doesn’t actually control another region, simply omit aria-controls.
<a href="/details">View details</a>
Complete document with proper toggle behavior
This example shows a working toggle pattern combining aria-controls with aria-expanded for full accessibility.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>aria-controls Toggle Example</title>
</head>
<body>
<button type="button" aria-controls="info-panel" aria-expanded="false">
Toggle info
</button>
<div id="info-panel" hidden>
<p>Extra information goes here.</p>
</div>
<script>
const btn = document.querySelector('button');
const panel = document.getElementById('info-panel');
btn.addEventListener('click', () => {
const expanded = btn.getAttribute('aria-expanded') === 'true';
btn.setAttribute('aria-expanded', String(!expanded));
panel.hidden = expanded;
});
</script>
</body>
</html>
Conditional rendering in a framework
When using a templating system or JavaScript framework, only render aria-controls when the target id is available. Here’s a conceptual example:
<!-- Good: only add the attribute when targetId has a value -->
<button type="button" aria-controls="sidebar-nav" aria-expanded="false">
Menu
</button>
<nav id="sidebar-nav" hidden>
<ul>
<li><a href="/">Home</a></li>
</ul>
</nav>
<!-- Bad: template outputs an empty value when targetId is undefined -->
<!-- <button type="button" aria-controls="">Menu</button> -->
In frameworks like React, you can conditionally spread the attribute: use aria-controls={targetId || undefined} so that when the value is empty, the attribute is omitted from the rendered HTML entirely rather than being set to an empty string.
The aria-controls attribute establishes a programmatic relationship between a controlling element (like a button, tab, or scrollbar) and the element it controls (like a panel, region, or content area). Assistive technologies such as screen readers use this relationship to help users navigate between related elements — for example, announcing that a button controls a specific panel and allowing the user to jump to it.
When the id referenced in aria-controls doesn’t exist in the document, the relationship is broken. Screen readers may attempt to locate the target element and fail silently, or they may announce a control relationship that leads nowhere. This degrades the experience for users who rely on assistive technology and violates the WAI-ARIA specification, which requires that the value of aria-controls be a valid ID reference list pointing to elements in the same document.
Common causes of this error include:
- Typos in the id or the aria-controls value.
- Dynamically generated content where the controlled element hasn’t been rendered yet or has been removed from the DOM.
- Copy-paste errors where aria-controls was copied from another component but the corresponding id was not updated.
- Referencing elements in iframes or shadow DOM, which are considered separate document contexts.
The aria-controls attribute accepts one or more space-separated ID references. Every listed ID must match an element in the same document.
How to Fix
- Verify the target element exists in the document and has the exact id that aria-controls references.
- Check for typos — ID matching is case-sensitive, so mainPanel and mainpanel are not the same.
- If the controlled element is added dynamically, ensure it is present in the DOM before or at the same time as the controlling element, or update aria-controls programmatically when the target becomes available.
- If the controlled element is genuinely absent (e.g., conditionally rendered), remove the aria-controls attribute until the target element exists.
Examples
Incorrect: aria-controls references a non-existent ID
<button aria-controls="info-panel" aria-expanded="false">
Toggle Info
</button>
<div id="infopanel">
<p>Here is some additional information.</p>
</div>
This triggers the error because aria-controls="info-panel" does not match the actual id of "infopanel" (note the missing hyphen).
Correct: aria-controls matches an existing element’s ID
<button aria-controls="info-panel" aria-expanded="false">
Toggle Info
</button>
<div id="info-panel">
<p>Here is some additional information.</p>
</div>
Correct: Tab and tab panel relationship
<div role="tablist">
<button role="tab" aria-controls="tab1-panel" aria-selected="true">
Overview
</button>
<button role="tab" aria-controls="tab2-panel" aria-selected="false">
Details
</button>
</div>
<div id="tab1-panel" role="tabpanel">
<p>Overview content goes here.</p>
</div>
<div id="tab2-panel" role="tabpanel" hidden>
<p>Details content goes here.</p>
</div>
Both aria-controls values — tab1-panel and tab2-panel — correctly correspond to elements present in the document.
Correct: Custom scrollbar controlling a region
<div role="scrollbar" aria-controls="main-content" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" aria-orientation="vertical"></div>
<div id="main-content" role="region" aria-label="Main content">
<p>Scrollable content goes here.</p>
</div>
Correct: Controlling multiple elements
The aria-controls attribute can reference multiple IDs separated by spaces. Each ID must exist in the document.
<button aria-controls="section-a section-b">
Expand All Sections
</button>
<div id="section-a">
<p>Section A content.</p>
</div>
<div id="section-b">
<p>Section B content.</p>
</div>
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.
Pro Trial
Full Pro access. Cancel anytime.
Start Pro Trial →Join teams across 40+ countries