Skip to main content

Notes

Making Product Info Tabs Accessible: The ARIA Tabs Pattern for E-Commerce Product Pages

Quietramp ·

Making Product Info Tabs Accessible: The ARIA Tabs Pattern for E-Commerce Product Pages

Quick answer: Almost every product detail page splits its content — Description, Specifications, Shipping & Returns, Reviews — into tabs so only one panel shows at a time. Most of these are built from a row of <div> or <li> elements with click handlers that swap panel visibility with CSS, which leaves a keyboard or screen-reader user with no way to tell there’s a tab interface at all, no way to move between tabs without hunting for each one in the page’s Tab order, and no announcement of which tab is currently active. The WAI-ARIA Authoring Practices Guide (APG) Tabs pattern exists specifically for this component — three roles, two state attributes, and one keyboard behavior fix everything below.

This article covers technical accessibility practice, not legal advice — it explains what WCAG 2.1 AA requires for interactive controls and how to implement it, not whether a specific site carries legal risk.

Why tabs fail so consistently

Tabs are one of the oldest custom UI patterns on the web, which is part of the problem: most component libraries have shipped a “tabs” component for over a decade, long before ARIA roles for tabs were standard practice, and newer implementations often copy an older one’s markup instead of starting from the current APG pattern. The result is a row of labels that behave like tabs to a mouse user and like a handful of unrelated, unlabeled clickable elements to everyone else.

The APG’s own definition is direct: “Tabs are a set of layered sections of content, known as tab panels, that display one panel of content at a time.” That’s a precise technical shape — a container, a set of triggers, and a set of panels, each trigger tied to exactly one panel — and a <div> with an onclick handler expresses none of it programmatically, even when it looks correct on screen.

1. No role, so assistive technology can’t identify the widget (fails SC 4.1.2 Name, Role, Value)

WCAG SC 4.1.2 Name, Role, Value (Level A) states its intent as ensuring “that Assistive Technologies (AT) can gather appropriate information about, activate (or set) and keep up to date on the status of user interface controls in the content.” The Understanding doc is specific about why this matters for a component like tabs: “If custom controls are created, however, or interface elements are programmed…to have a different role and/or function than usual, then additional measures need to be taken to ensure that the controls provide important and appropriate information to assistive technologies.”

A row of <div class="tab">Description</div> elements has none of that. A screen reader has no way to announce “tab,” no way to announce how many tabs exist or which position the current one holds (“Description, tab 1 of 4”), and no way to announce whether it’s currently selected.

2. No keyboard support beyond whatever Tab order happens to produce (fails SC 2.1.1 Keyboard)

WCAG SC 2.1.1 Keyboard (Level A) exists so that “wherever possible, content can be operated through a keyboard or keyboard interface” — full stop, not just reachable. Without tabindex management, a set of <div> tabs is either skipped entirely by the keyboard (nothing to focus) or, if a blanket tabindex="0" was added to make them focusable, each tab takes its own stop in the page’s linear Tab order — a keyboard user has to press Tab repeatedly to get from “Description” to “Reviews” instead of using the arrow keys sighted users never need but a real tab widget should offer for free.

The real Tabs pattern replaces that with a documented interaction model: only the active tab sits in the page’s Tab order (tabindex="0"), the rest are tabindex="-1", and once focus is inside the tab list, Left/Right arrow keys move focus between tabs directly — the APG states “Right Arrow: Moves focus to the next tab,” with Home/End optionally jumping to the first or last tab. One Tab keystroke enters the widget, then arrow keys move around inside it — a single, predictable stop instead of four or five.

WCAG SC 1.3.1 Info and Relationships (Level A) requires that relationships “implied by visual…formatting” be “programmatically determined” rather than left to layout alone. Sighted users infer that “Shipping & Returns” controls the panel below it purely from proximity; that proximity doesn’t survive for a screen reader unless the markup states it explicitly. The APG pattern closes this with two attributes: each tab carries aria-controls “referring to its associated tabpanel element,” and each tabpanel is tied back with aria-labelledby pointing at its tab — a two-way link a screen reader can actually traverse.

The fix: the APG Tabs pattern, roles and states

The pattern uses three roles and two key state attributes, all fetched directly from the APG Tabs page:

  • tablist — “the element that serves as the container for the set of tabs,” wrapping all the triggers.
  • tab — each trigger element, “contained within the element with role tablist.”
  • tabpanel — each content region, one per tab.
  • aria-selected — “The active tab element has the state aria-selected set to true and all other tab elements have it set to false.”
  • aria-controls on each tab, pointing at its tabpanel’s id.
<div role="tablist" aria-label="Product information">
  <button role="tab" id="tab-desc" aria-selected="true" aria-controls="panel-desc" tabindex="0">
    Description
  </button>
  <button role="tab" id="tab-specs" aria-selected="false" aria-controls="panel-specs" tabindex="-1">
    Specifications
  </button>
  <button role="tab" id="tab-shipping" aria-selected="false" aria-controls="panel-shipping" tabindex="-1">
    Shipping &amp; Returns
  </button>
</div>

<div role="tabpanel" id="panel-desc" aria-labelledby="tab-desc" tabindex="0">
  <!-- description content -->
</div>
<div role="tabpanel" id="panel-specs" aria-labelledby="tab-specs" hidden>
  <!-- specs content -->
</div>
<div role="tabpanel" id="panel-shipping" aria-labelledby="tab-shipping" hidden>
  <!-- shipping content -->
</div>

Two details in that markup matter beyond the roles themselves. First, the APG notes “When the tabpanel does not contain any focusable elements…the tabpanel should set tabindex='0'” so a keyboard user landing on a text-only panel (a plain-prose Description tab) can still reach and scroll it rather than losing focus entirely. Second, only the selected tab keeps tabindex="0" — the rest are -1, reachable by arrow key but not by sequential Tab, matching the “one Tab stop, then arrows” model above.

Automatic vs. manual activation — pick one deliberately

The APG documents two activation models, and picking the wrong one for the content is a common source of send-backs even after the roles are correct. Automatic activation switches the panel the instant a tab receives focus — arrowing to “Specifications” shows it immediately. Manual activation requires an explicit action: the APG describes it as “a tabs widget where users activate a tab and display its panel by pressing Space or Enter” after arrowing to it.

Automatic activation suits short, lightweight panels (a Description/Specs/Shipping split where switching costs nothing). Manual activation fits better when a panel triggers real work — lazy-loaded content or a network request — because a deliberate Space/Enter keypress keeps arrow-key browsing from firing off requests the user was only passing through on the way to a different tab.

What a scan catches, and what a person has to check

An automated scanner reliably flags the most obvious gap — a clickable element with no ARIA role at all typically surfaces as “interactive control missing accessible name/role.” What it can’t verify: whether aria-controls on each tab actually points at the right panel’s id (a copy-paste error here breaks the announced relationship silently, with no visual symptom), whether aria-selected updates when a tab is clicked with a mouse and not just when arrow-keyed, and whether the panel’s content is actually reachable and readable with a screen reader once shown — not just present in the DOM with the right attribute toggled. Those require someone operating the widget with a keyboard and a screen reader and listening to what’s actually announced.

This is a different fix from expand/collapse filter groups on a category page — see Accessible Product Filtering and Faceted Search for the Disclosure pattern used there, which shows and hides a single region rather than switching between several mutually exclusive panels.

FAQ

Do all four tabs need to be in the page’s Tab order? No — only the currently selected tab should have tabindex="0". The rest are tabindex="-1" and reached with arrow keys once focus is inside the tab list, per the APG pattern above.

Is a set of same-page anchor links (<a href="#specs">) an acceptable substitute for real tabs? Not on its own. Anchor links can work as a progressive-enhancement fallback, but without the tablist/tab/tabpanel roles and aria-selected state, a screen reader still can’t tell a user which section is “active” or that a tab interface exists at all.

Does each tabpanel need its own heading? It’s good practice but not a hard WCAG requirement — the aria-labelledby link to the tab already gives the panel its accessible name. A visible heading matching the tab label helps a sighted user scrolling with page-zoom, but the accessible-name requirement is satisfied without one.

Get your product pages checked by a person, not just a parser

Quietramp’s $890 audits include a manual pass through interactive components like tabbed product panels — checking role, keyboard behavior, and the announced tab-to-panel relationship with an actual screen reader, not just confirming that some ARIA attribute exists somewhere in the DOM. See a real sample report or check pricing.


This is educational content about technical accessibility practice, not legal advice. Meeting WCAG 2.1 AA does not constitute a legal compliance determination under the ADA or any other standard — consult qualified counsel for legal risk questions.

This article was drafted with AI assistance and reviewed by a person for accuracy before publication.

← Back to Notes