Accessibility & semantics

aria-expanded has four honest jobs left. The rest belong to the platform.

aria-expanded has four honest jobs left. The rest belong to the platform.

Every extra ARIA attribute you type is a promise to a screen-reader user. aria-expanded="false" says: this control opens something, that something is not open right now, and my JavaScript will change this value the moment that changes. When the promise holds, the user gets a clean announcement and knows what happened. When it drifts, they hear the wrong state as they look at an open panel.

Steve Frenzel's new piece on Piccalilli is a map of which of those promises you still have to make in 2026, and which have quietly become the platform's problem instead of yours. His framing is direct: aria-expanded is no longer strictly needed for some of the patterns you were taught to use it on, because the platform now covers them.

Here is the short version, so you can read it as a working policy.

The four patterns where aria-expanded still earns it

Per Frenzel, four collapsible interactive patterns still require aria-expanded on the trigger: menus, navigation, tree views, and combo boxes.

Navigation is the plainest of the four. If a link opens a fly-out with more links inside it, the trigger carries aria-expanded and toggles between true and false. Frenzel's example is a nested <ul> inside a <nav> with aria-labelledby pointing to a hidden label. The parent link carries aria-expanded. He notes there is currently no way to build this pattern without JavaScript, so you are on the hook for keeping the attribute in sync yourself.

A menu is different. Frenzel is careful to say a menu is not navigation. It is a set of application commands, close to a desktop menubar, and the trigger for one needs both aria-haspopup and aria-expanded. If you reach for aria-expanded alone, the assistive tech will announce a disclosure, not a menu, and the user's model of the widget starts wrong.

Tree views inherit the same rule at every expandable branch. Frenzel's example places aria-expanded on each <li role="treeitem"> whose children can collapse, alongside the positional attributes the pattern needs anyway (aria-level, aria-posinset, aria-setsize).

Combo boxes are the fourth. The trigger opens a listbox, so aria-expanded tells the user whether that listbox is currently visible. But Frenzel notes that if the only reason you were building a combo box was to style a <select>, the native element is now a better option. That is one fewer combo box to ship.

Where you should stop reaching for it

The most common misuse Frenzel names is the disclosure widget: a button that toggles a hidden container. His example has the full pattern — a <button> with aria-controls and aria-expanded, plus the JavaScript to flip the attribute — and then his recommendation, which you should read as a hard preference: if that is all your control does, use <details> and <summary> instead. No script. No attribute to keep in sync. The state is the DOM state.

Dialogs are the other big one. A dialog is not "expanded", it is modal, and Frenzel's guidance is aria-modal on the container, not aria-expanded on the trigger. He goes further and shows a <dialog> opened with the new invoker command syntax:

<button command="show-modal" commandfor="my-dialog">
  Open dialog
</button>
<dialog id="my-dialog">
  <h2>Progressive enhancement</h2>
  <button commandfor="my-dialog" command="close">Close</button>
</dialog>

That is a real dialog, wired without JavaScript, and none of it involves aria-expanded.

Tooltips get the same treatment. The Popover API and invoker commands give you a <button command="toggle-popover" commandfor="info"> pointing at a <div popover="auto" role="tooltip">, and once again the attribute you were going to add is redundant.

Tabbed interfaces are worth calling out for a different reason. Frenzel's note is that a tab uses aria-haspopup, not aria-expanded. If you have the former on a trigger and the latter is missing, that is the correct shape.

Read the APG with your eyes open

One aside from Frenzel worth carrying into your next code review: he says the ARIA Authoring Practices patterns are not suitable for production use. Treat the APG as a study of the semantics you are aiming for, then test the result with actual assistive tech. A widget that follows the APG to the letter can still fail the user. A widget with fewer attributes and the right native element often will not.

The checklist for your next PR

  • Reach for <details> and <summary> before you write your first line of disclosure JavaScript.
  • Keep aria-expanded on triggers for menus, navigation, tree views, and combo boxes. Add aria-haspopup on the menu trigger; combo boxes may need it too.
  • Use <dialog> and aria-modal for modal dialogs. If you are opening one from a button, look at the command and commandfor attributes before you write another click handler.
  • Reach for the Popover API and invoker commands for tooltips and toggled surfaces.
  • Do not ship an APG pattern straight to production without testing it end to end.

Frenzel's verdict is not that aria-expanded is broken. His point is that a shrinking number of widgets still need it, and every extra attribute you write should be one the platform genuinely cannot provide for you.

Source: Piccalilli (piccalil.li)

Related
Accessibility & semantics

A primary-nav flyout built on `popover`, not `aria-expanded`

Adrian Roselli has rebuilt his 2019 disclosure-widget primary nav on the native HTML `popover` attribute. The rebuild is a clean map of what the platform now carries for you, and where you still need ARIA.

July 9, 2026
Accessibility & semantics

Chrome 150 hands composite-widget arrow keys to the browser

Chrome 150 ships the `focusgroup` HTML attribute, a way to get roving arrow-key focus inside composite widgets like listbox, tablist and toolbar without a line of JavaScript. Adrian Roselli has published script-free demos, and the spec story is still an Open-UI explainer.

July 6, 2026
Accessibility & semantics

Your `<nav>` label already says 'navigation'

Writing 'Primary navigation' on a `<nav>` makes a screen reader announce the word twice. The fix is to trust the landmark and let the label do its actual job.

June 26, 2026