Skip to main content
WCAGrules
Quick navigation

Guides · Interface patterns

Tabs and Accordions, and the Difference Between Them

Two patterns that look similar, behave differently, and get confused for each other constantly.

Last reviewed August 30, 2026

An accordion is a stack of headings that expand. A tab list is a row of choices where exactly one panel shows at a time. They solve different problems and their keyboard contracts have almost nothing in common, which is why picking the wrong one costs so much.

Teams reach for tabs because the word sounds more precise. Tabs are far harder to implement, and a half-built tab list behaves worse than a plain accordion ever would, because declaring the role is a promise about behavior that the browser will not keep for you.

The Accordion Is the Easy One

Each section header is a button. The button carries aria-expanded, true when its panel is open and false when it is closed, and aria-controls pointing at the panel it opens. Tab moves between headers. Enter and Space toggle. There is no arrow key behavior at all, and that absence is the whole reason this pattern is cheap.

Four details finish it, and three of them are the ones we see skipped.

  • Wrap each button in a real heading element at whatever level fits the page's structure, so the accordion still makes sense in a screen reader's heading list. Skip this and you have flattened the page and quietly failed 1.3.1.
  • The button must be the only thing inside that heading. This is the rule teams break without noticing, because the chevron, the count badge and the little menu button all want to live in the header too. Put them beside the heading rather than inside it, or the heading text turns into a run-on.
  • If one panel always has to stay open, mark that header aria-disabled="true" while its panel is showing, so somebody is not pressing a control that cannot do anything.
  • role="region" on each panel is optional, and it is genuinely useful when panels hold headings or a nested accordion, because it gives a screen reader user something to jump to. It has a cap. Do not use it on an accordion with more than about six panels that can be open at once, or you have buried the page's real landmarks under a pile of new ones.

The details Element Is a Different Pattern, Not a Shortcut

Native details and summary give you a button, a state, and keyboard behavior with no JavaScript, and that is a real offer. It is also not the accordion pattern. It is the disclosure pattern, which is a related and simpler thing where aria-controls is optional rather than required and no heading wrapper is specified.

The reason that matters is the one piece of advice on this page it quietly cancels. The role browsers assign to summary varies, and some still give it a button role, which strips the roles off everything inside it. So the heading you carefully put in your summary is not a heading for those users, which is exactly the failure the section above told you to avoid. Use details for a simple show and hide where the header is not carrying structure. Test it across browsers before you use it for anything else.

Tabs Ask a Great Deal More of You

A tab list moves focus with arrow keys rather than with Tab. Tab enters the list and lands on the active tab. Left and right arrows move focus between tabs, wrapping from the last to the first. Tab again leaves the list, and where it goes next is the part most hand-built tab lists get wrong.

It lands in the open panel only if the panel's first meaningful content is focusable. A panel of plain text is skipped entirely, so the keyboard user tabs from the tab list straight past the content they just chose. The fix is tabindex="0" on the panel itself whenever it has no focusable content of its own, and it is the single most common tabs failure after the arrows.

Only one tab sits in the page's tab order at a time. The selected tab carries tabindex="0" and the rest carry tabindex="-1", and those values move as the selection moves. The technique is called a roving tabindex, and it is worth knowing the name because the same mechanism runs tab lists, toolbars, radio groups, grids and date pickers. Our tabindex entry covers what the values themselves do.

  • The container takes role="tablist", and it needs an accessible name. Point aria-labelledby at a visible label if there is one, and use aria-label if there is not.
  • Each tab takes role="tab", plus aria-controls pointing at its panel and aria-selected. The selected tab is true and every other tab is explicitly false, not absent.
  • Each panel takes role="tabpanel" and aria-labelledby pointing back at its tab.
  • Arrow keys move focus. Whether selection follows focus is a decision you make, not a default. Activating on focus is recommended when the panels are already loaded and appear with no noticeable delay. Where a panel takes a moment to arrive, activate on Enter or Space instead, because automatic activation slows every arrow press down to the speed of the slowest panel.
  • Home and End are optional, and worth adding. They jump to the first and last tab.
  • A vertical tab list needs aria-orientation="vertical", and then up and down do what left and right do elsewhere. A horizontal list must not listen for up and down at all, because those keys are how a keyboard user scrolls the page.

Get any of that wrong and you have a widget announcing itself as tabs while behaving like something else, which is worse than not claiming the role. The specification puts it as a requirement rather than a preference. An element with role="tab" must be inside an element with role="tablist". And the framing W3C uses for the whole family is the one to keep. A role is a promise, ARIA gives you no behavior for free, and no ARIA is better than bad ARIA. A mismatch between the announced role and the actual behavior is a 4.1.2 failure.

Choosing Between Them

SituationUse
Content people may want open at onceAccordion
Mutually exclusive views of the same thingTabs
Narrow screensAccordion, which stacks naturally
Long content people mostly skipAccordion
Fewer than three choicesNeither. Just show it
Which pattern fits

The Failure Nobody Notices

Content hidden inside a collapsed panel is invisible to the browser's own find-in-page. Somebody presses Ctrl-F, types the thing they came for, and the browser reports not found on a page that contains it. That is not a WCAG failure on its own and it is a real way to lose a reader.

The platform has an answer now that we used to work around. hidden="until-found" keeps a panel visually hidden while leaving its content reachable by find-in-page and by a link to an anchor inside it, and the browser opens the panel when it matches. One limit to know before you rely on it. It does nothing if the element's display is none, contents, or inline, which rules out most of the ways a panel is usually hidden, so this is a change to how you hide rather than an attribute you sprinkle on top.

Before You Copy the Example Code

Every worked example in the ARIA Authoring Practices Guide carries the same warning, and it is worth reading once. The code is not intended for production. It illustrates correct use of the specification, and it deliberately does not work around gaps in browser and screen reader support, especially on mobile and touch devices. So the examples are the right thing to build from and the wrong thing to ship untested.

Common questions

Do tabs need arrow key support to pass WCAG?
If you use the ARIA tab roles, yes in practice. Declaring role="tablist" is a promise that the keyboard behavior of a tab list is there, and ARIA gives you none of that behavior for free. If you do not want to write arrow key handling, use plain buttons and panels without the tab roles. That is a legitimate pattern and it fails nothing.
Do arrow keys change which tab is showing?
They move focus. Whether the panel changes with it is your decision. Activating on focus is recommended when panels are preloaded and appear instantly. Where a panel takes a moment to load, leave activation to Enter or Space, because otherwise every arrow press waits for a panel nobody asked to see.
Why does Tab skip past my tab panel?
Because a tab panel is only in the tab order if its first meaningful content is focusable. A panel of plain text is skipped. Put tabindex="0" on the panel itself and the keyboard user lands in the content they just selected.
Can I use details and summary for an accordion?
For a simple show and hide, yes. Know that it implements the disclosure pattern rather than the accordion pattern, and that the role browsers give to summary varies, with some assigning a button role that strips the roles off everything inside. A heading inside a summary may therefore not be a heading, which is the one thing an accordion header most needs to be. Test it before you rely on it.
Should an accordion be open or closed by default?
Open the section that answers the most common question and leave the rest closed. Opening everything defeats the point of the pattern, and closing everything hides the page's value behind a click.

Sources

Keep reading

More on interface patterns

Reading about it is the cheap part.

Find out where your site actually stands. The free scan checks 10 pages in a real browser against all 90 supported automated rules, keeps its 27 best-practice checks separate from WCAG findings, and names the rule behind every finding. The full audit adds an expert review and a real blind screen-reader user. From $499, with the report in 5 business days on Rapid and 10 on Standard, and the clock starting at cleared payment.

Go somewhere useful

Find tools, resources and your workspace.

29 destinations