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-controlsas an IDREFS attribute, meaning its value must contain one or more space-separated tokens, each matching theidof 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
idwas accidentally removed during refactoring.
How to fix it
- If the element controls another region, set
aria-controlsto theidof that region. Make sure the target element exists in the DOM and has a uniqueid. - If the element doesn't control anything, remove the
aria-controlsattribute entirely. An absent attribute is always better than an empty one. - For dynamically rendered content, only add
aria-controlsafter the target element and itsidare present in the DOM. If using a framework, conditionally render the attribute. - Keep references in sync. If you rename or remove an
id, update everyaria-controlsthat references it.
Examples
Invalid: empty aria-controls
This triggers the validator error because the attribute value contains no id reference.
<ahref="#"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.
<buttontype="button"aria-controls="">Open menu</button>
Fixed: provide a valid id reference
Point aria-controls to the id of the element being controlled.
<buttontype="button"aria-controls="details-panel"aria-expanded="false">
Toggle details
</button>
<divid="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.
<buttontype="button"aria-controls="filters results">
Show filters and results
</button>
<sectionid="filters"hidden>
<p>Filter options...</p>
</section>
<sectionid="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.
<ahref="/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>
<htmllang="en">
<head>
<metacharset="utf-8">
<title>aria-controls Toggle Example</title>
</head>
<body>
<buttontype="button"aria-controls="info-panel"aria-expanded="false">
Toggle info
</button>
<divid="info-panel"hidden>
<p>Extra information goes here.</p>
</div>
<script>
constbtn=document.querySelector('button');
constpanel=document.getElementById('info-panel');
btn.addEventListener('click',()=>{
constexpanded=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 -->
<buttontype="button"aria-controls="sidebar-nav"aria-expanded="false">
Menu
</button>
<navid="sidebar-nav"hidden>
<ul>
<li><ahref="/">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 on a button element has an empty value, but it requires at least one valid ID reference.
The aria-controls attribute accepts a space-separated list of id values that point to the elements controlled by the current element. When a screen reader encounters a button with aria-controls, it can offer the user a way to navigate to the controlled element. An empty string ("") is not a valid IDREF, so the W3C validator rejects it.
This often happens when a template or JavaScript framework sets aria-controls before the target element's id is known, or when the value is conditionally generated and falls through to an empty string.
There are two ways to fix this: either set aria-controls to a valid id that exists in the document, or remove the attribute entirely when no target is available. An absent aria-controls attribute is perfectly fine and preferable to an empty one.
HTML examples
Invalid: empty aria-controls
<buttonaria-controls=""aria-expanded="false">
Toggle menu
</button>
<navid="main-menu"hidden>
<ahref="/about">About</a>
</nav>
Fixed: aria-controls references a valid ID
<buttonaria-controls="main-menu"aria-expanded="false">
Toggle menu
</button>
<navid="main-menu"hidden>
<ahref="/about">About</a>
</nav>
If the controlled element does not exist yet or the ID is not available, remove the attribute:
<buttonaria-expanded="false">
Toggle menu
</button>
JavaScript can add aria-controls later, once the target element and its id are present in the DOM.
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
idor thearia-controlsvalue. - Dynamically generated content where the controlled element hasn't been rendered yet or has been removed from the DOM.
- Copy-paste errors where
aria-controlswas copied from another component but the correspondingidwas 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
idthataria-controlsreferences. - Check for typos — ID matching is case-sensitive, so
mainPanelandmainpanelare 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-controlsprogrammatically when the target becomes available. - If the controlled element is genuinely absent (e.g., conditionally rendered), remove the
aria-controlsattribute until the target element exists.
Examples
Incorrect: aria-controls references a non-existent ID
<buttonaria-controls="info-panel"aria-expanded="false">
Toggle Info
</button>
<divid="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
<buttonaria-controls="info-panel"aria-expanded="false">
Toggle Info
</button>
<divid="info-panel">
<p>Here is some additional information.</p>
</div>
Correct: Tab and tab panel relationship
<divrole="tablist">
<buttonrole="tab"aria-controls="tab1-panel"aria-selected="true">
Overview
</button>
<buttonrole="tab"aria-controls="tab2-panel"aria-selected="false">
Details
</button>
</div>
<divid="tab1-panel"role="tabpanel">
<p>Overview content goes here.</p>
</div>
<divid="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
<divrole="scrollbar"aria-controls="main-content"aria-valuenow="0"aria-valuemin="0"aria-valuemax="100"aria-orientation="vertical"></div>
<divid="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.
<buttonaria-controls="section-a section-b">
Expand All Sections
</button>
<divid="section-a">
<p>Section A content.</p>
</div>
<divid="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