Disclosure, accordion and tabs: the ARIA patterns done right
Build disclosures and accordions with aria-expanded and aria-controls, and tabs with tablist, tab and tabpanel roles, arrow keys, Home and End, roving tabindex, and automatic vs manual activation.
Comes up in: Web fundamentals, Machine coding, React
Learn#
Kavya’s product page has tabs built from divs with click handlers. Keyboard users cannot reach them, and screen readers announce a flat list of text with no idea which tab is open.
A disclosure is a button that shows and hides a section. Use a real button, set aria-expanded to true or false, and point aria-controls at the section. An accordion is a stack of disclosures (or <details> elements, WEB.07.02).
Tabs need three roles. A tablist holds elements with role tab; each tab has aria-selected and aria-controls pointing at its tabpanel, which is labelled by its tab:
<div role="tablist" aria-label="Product information">
<button role="tab" id="t1" aria-selected="true" aria-controls="p1" tabindex="0">Details</button>
<button role="tab" id="t2" aria-selected="false" aria-controls="p2" tabindex="-1">Reviews</button>
</div>
<div role="tabpanel" id="p1" aria-labelledby="t1" tabindex="0">…</div>
<div role="tabpanel" id="p2" aria-labelledby="t2" tabindex="0" hidden>…</div>Keyboard: Tab enters the tab list on the selected tab, arrow keys move between tabs, Home and End jump to the ends, and Tab again moves into the panel.
Page
- [Details ✓ tabindex=0] [Reviews -1] [Shipping -1]
A keyboard user presses Tab and lands on the selected tab, "Details". Only that tab has tabindex="0"; the others have -1.
Read the steps as text
- A keyboard user presses Tab and lands on the selected tab, "Details". Only that tab has tabindex="0"; the others have -1.
- Right Arrow moves focus to "Reviews" and, with automatic activation, selects it and shows its panel.
- End jumps to "Shipping"; Right Arrow from the last tab wraps to the first.
- Tab now leaves the tab list and moves into the panel. Arrow keys move between tabs; Tab moves between parts of the page.
Predict#
Predict first
A tab list has 6 tabs, all with tabindex="0". What is the problem for keyboard users?
The pattern uses a roving tabindex: the selected tab has tabindex="0", the others -1, and arrow keys move focus within the list. Six Tab stops make the page slow to navigate and differ from what screen-reader users expect.
Interview angle#
Say it like this — about 30 seconds
“Tabs use role tablist on the container, role tab on each tab with aria-selected and aria-controls, and role tabpanel labelled by its tab. Only the selected tab is in the Tab order, with a roving tabindex. Left and Right arrows move between tabs and wrap, Home and End jump to the ends, and Tab moves into the panel. With automatic activation, moving focus also selects the tab; I use manual activation, with Enter or Space, when loading a panel is slow.”
Follow-up How should keyboard navigation work in a tabs component?
Follow-up When should tabs use manual activation?
Follow-up Disclosure vs tabs: when to use which?
Practice#
Guided
Implement the tabs keyboard model: arrows that wrap, Home and End, and skipping disabled tabs.
Done when
- Arrow keys move and wrap around both ends.
- Home and End go to the first and last enabled tab.
- Disabled tabs are skipped, and other keys change nothing.
Remember#
Try to answer each card before you open it. Then add them to your reviews.
Which attribute tells assistive tech a disclosure is open?
aria-expanded="true".
Which three roles make up a tabs widget?
tablist, tab and tabpanel.
Which keys move between tabs?
Arrow keys (wrapping), plus Home and End.
What is a roving tabindex?
Only the active item has tabindex=0; the others have -1, and arrows move focus.
Go deeper
Four layers. Stop at the one your interview needs; each ends with a quick check.
The core ideaEvery round
A button gives focus and Enter and Space for free. Update aria-expanded on every toggle; hide the content with hidden or CSS display, so it leaves the accessibility tree too.
Check yourself: Why use a button element for a disclosure trigger?
It is focusable and works with Enter and Space without extra code.
How it works underneathSenior rounds
A panel with no focusable content gets tabindex="0" so Tab can reach it. Vertical tab lists use Up and Down instead of Left and Right, and set aria-orientation="vertical" Source: WAI-ARIA APG: Tabs pattern.
Check yourself: Why give tabpanels tabindex="0"?
So keyboard users can Tab into a panel that has no focusable content.
Edge cases and trapsWhere people slip
Accordion headers are buttons inside headings; Enter and Space toggle them. Arrow-key movement between headers is optional Source: WAI-ARIA APG: Accordion pattern. Do not trap focus inside either widget.
Check yourself: Should arrow keys open accordion sections?
No requirement; Up and Down may move between headers, but Enter and Space toggle.
In productionStaff-level answers
Use a tested library (Radix, React Aria, Headless UI) or follow the APG closely, and test with a keyboard and a screen reader (QLT.04.05). In React, a compound Tabs component is a good fit (RCT.08.02).
Check yourself: Name a library that implements these patterns accessibly.
Radix, React Aria or Headless UI.
Finished the five steps? Mark it, and its cards join your reviews.
Last checked 2026-11-01. Spot a mistake? Use “Report an error” below — fixes are listed on the corrections page.
Interview questions this answers
- How should keyboard navigation work in a tabs component?