# Expandable components use aria-expanded

- **Pattern:** `ab-000072` (`accessibility-basics.forms-interactive.expandable-components`)
- **Severity:** medium
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000072
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000072)

## Why it matters

An accordion section that shows or hides content via CSS `display: none / block` toggling — but has no `aria-expanded` on the trigger button — is invisible in state to screen readers. The screen reader announces 'button: FAQ: What is your refund policy?' whether the section is open or closed. Users who want to collapse an open section have no way to confirm its current state before clicking. WCAG 2.2 SC 4.1.2 (Name, Role, Value) requires the state of UI components to be programmatically determinable. Section 508 2018 Refresh 502.3.4 covers programmatic state. The ARIA disclosure widget pattern specifies `aria-expanded` on the controlling button and `id`/`aria-controls` linking the button to its panel.

## Severity rationale

Medium because absent `aria-expanded` removes state awareness from screen reader users, degrading their ability to navigate and manage accordions and disclosure widgets.

## Remediation

Add `aria-expanded` to the trigger button and link it to the controlled panel with `aria-controls`. Ensure the panel is hidden in a way that assistive technology respects — use `hidden` attribute or `display: none`, not `visibility: hidden` or `opacity: 0`.

```tsx
const Accordion = ({ items }: { items: { id: string; title: string; body: string }[] }) => {
  const [openId, setOpenId] = useState<string | null>(null);

  return (
    <div className="accordion">
      {items.map(item => (
        <div key={item.id} className="accordion-item">
          <h3>
            <button
              aria-expanded={openId === item.id}
              aria-controls={`panel-${item.id}`}
              id={`btn-${item.id}`}
              onClick={() => setOpenId(id => id === item.id ? null : item.id)}
            >
              {item.title}
            </button>
          </h3>
          <div
            id={`panel-${item.id}`}
            role="region"
            aria-labelledby={`btn-${item.id}`}
            hidden={openId !== item.id}
          >
            <p>{item.body}</p>
          </div>
        </div>
      ))}
    </div>
  );
};
```

Using `hidden` (not CSS `display: none` alone) ensures assistive technology does not read the panel content when collapsed. If using CSS for animation, remove `hidden` but add `aria-hidden={openId !== item.id}` to maintain the programmatic state signal.

## Detection

- **ID:** `expandable-components`
- **Severity:** `medium`
- **What to look for:** Enumerate every relevant item. Check collapsible/expandable components (accordions, disclosure widgets, expand/collapse sections). These should use `aria-expanded="true"` or `aria-expanded="false"` to communicate whether the content is visible.
- **Pass criteria:** At least 1 of the following conditions is met. Expandable components have `aria-expanded` attribute. The attribute correctly reflects the visibility state.
- **Fail criteria:** Expandable components lack `aria-expanded`, or the attribute does not match the visible state.
- **Skip (N/A) when:** The application has no expandable components.
- **Detail on fail:** Identify expandable components without aria-expanded. Example: `"Accordion panels use CSS to hide/show content but have no aria-expanded. Users cannot tell if sections are expanded or collapsed."`
- **Remediation:** Add aria-expanded to expandable components:

  ```tsx
  const AccordionItem = ({ title, content, isExpanded, onToggle }) => (
    <div className="accordion-item">
      <button
        aria-expanded={isExpanded}
        aria-controls="accordion-content"
        onClick={onToggle}
      >
        {title}
      </button>
      {isExpanded && (
        <div id="accordion-content" className="accordion-content">
          {content}
        </div>
      )}
    </div>
  );
  ```

## External references

- wcag 4.1.2
- section-508 502.3.4

Taxons: accessibility

HTML version: https://auditbuffet.com/patterns/ab-000072
