# Bad value “” for attribute “aria-controls” on element “a”: An IDREFS value must contain at least one non-whitespace character.

> Canonical HTML version: https://rocketvalidator.com/html-validation/bad-value-for-attribute-aria-controls-on-element-a-an-idrefs-value-must-contain-at-least-one-non-whitespace-character
> Attribution: Rocket Validator (https://rocketvalidator.com)
> License: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)

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](https://www.w3.org/TR/wai-aria-1.2/#aria-controls) 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

1. **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`.
2. **If the element doesn't control anything**, remove the `aria-controls` attribute entirely. An absent attribute is always better than an empty one.
3. **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.
4. **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.

```html
<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.

```html
<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.

```html
<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.

```html
<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`.

```html
<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.

```html
<!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:

```html
<!-- 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.
