Blog · 10 min read
Accessible Tabs for Developers: 5 Markup Habits and When Scanners Fail

Build tabs with semantic HTML whenever possible, and when scripting is required, follow the WAI-ARIA tabs pattern with role=“tablist”, role=“tab”, and role=“tabpanel”, paired with roving tabindex and arrow key navigation. This combination gives you a keyboard-operable interface that works with screen readers out of the box. Test it with a keyboard and at least one screen reader before you ship.
TL;DR:
- Using
<button>elements for each tab ensures correct in-page state changes and better accessibility than<a>tags.- Only the active tab should have
tabindex="0"with focus movement managed by arrow keys, preventing focus chaos and improving keyboard navigation.- Automatic activation is suitable only when panels load instantly; otherwise, manual activation with Enter or Space is safer for remote or heavy content.
- Automated scans can catch structural errors but cannot verify timing or screen reader announcements, making manual testing essential.
- Proper ARIA roles include
role="tablist",role="tab", androle="tabpanel"linked viaaria-controlsandaria-labelledbyto ensure accessibility.
Table of Contents
- What counts as accessible tabs and when to use them
- Core ARIA roles, states, and attributes for tabs
- Keyboard interaction and focus management
- Choosing between automatic and manual activation
- Semantic HTML and markup patterns that actually work
- Implementation checklist and a minimal code example
- Where automated scanning fits into tab accessibility QA
- Building accessible tabs into your team’s workflow
- AccessWiser: scans, fixes, and documentation for accessible components
- FAQ
- Sources
What counts as accessible tabs and when to use them
A tabbed interface lets people switch between multiple panels of related content without leaving the page, showing only one panel at a time while keeping the others available on demand. Accessible tabs make that switch possible for everyone: a sighted mouse user clicks, a keyboard user presses arrow keys, and a screen reader user hears the current selection announced.
Tabs work best for grouping closely related content that shares a single context, like product specifications and reviews on the same item page. They’re the wrong choice when the content is better suited to separate pages or when a simple accordion would do:
- Use tabs when panels are tightly related and viewed in the same context, not as a sequence.
- Use navigation links when switching actually changes the page or URL meaningfully.
- Use an accordion when content is linear and users might want to see more than one section at once.
Every tab you add introduces extra interaction logic, so reach for the pattern only when it genuinely fits the content.
Core ARIA roles, states, and attributes for tabs
The tabs pattern depends on three roles working together, each with its own job in the accessibility tree. Get these right and most of the heavy lifting is done.
- role=“tablist” wraps the group of tabs and needs a label, either
aria-labeloraria-labelledby, so assistive technology can announce what the group represents. - role=“tab” goes on each clickable control; it uses
aria-controlsto point to its matching panel’s ID andaria-selectedto flag which tab is currently active, according to the Tabs Pattern from the WAI-ARIA Authoring Practices. - role=“tabpanel” marks each content region and should carry
aria-labelledbyreferencing its tab’s ID; inactive panels are hidden entirely, not just visually dimmed. - aria-orientation=“vertical” is added to the tablist only when tabs are stacked vertically, since horizontal is the assumed default.
- aria-haspopup applies only if a tab opens a secondary menu, and aria-expanded belongs to multi-select disclosure widgets, not standard single-select tabs.
Skipping aria-controls or leaving aria-selected static after a switch are two of the most common implementation gaps, and both break the experience for screen reader users even when the tabs look fine visually.
Keyboard interaction and focus management
Keyboard support is where most homemade tab implementations fall apart, usually because developers treat every tab as its own stop in the page’s tab order. The fix is roving tabindex: only the active tab is reachable with the regular Tab key, and arrow keys move focus among the rest.
| Key | Expected behavior |
|---|---|
| Tab | Moves focus into the tablist (landing on the active tab) or out of it to the next page element |
| Shift+Tab | Moves focus backward, treating the tablist as one stop |
| Left/Right arrow | Moves focus among tabs in a horizontal tablist |
| Up/Down arrow | Moves focus among tabs in a vertical tablist |
| Home | Moves focus to the first tab |
| End | Moves focus to the last tab |
This mapping comes directly from the Tabs Pattern in the WAI-ARIA Authoring Practices, and it’s worth copying exactly rather than improvising.
The roving tabindex mechanics: the active tab gets tabindex="0" and aria-selected="true", every other tab gets tabindex="-1" and aria-selected="false", and you update both attributes together whenever focus or selection changes, as described in MDN’s tab role reference. Never use a positive tabindex value; it scrambles the natural tab order for every other control on the page.

Pro Tip: Test your roving tabindex logic by unplugging your mouse for ten minutes. If you can’t reach every tab and panel with only arrow keys and Tab, neither can your keyboard-only users.
Choosing between automatic and manual activation
Tabs support two activation models, and picking the wrong one creates real friction for keyboard and screen reader users. Automatic activation means the panel switches the instant focus lands on a new tab, with no extra key press. Manual activation means focus can move freely among tabs, but the panel only switches when the user presses Enter or Space.
- Automatic activation fits only when every panel is already loaded and swaps in with no noticeable delay.
- Manual activation is the safer default for panels that fetch content remotely or render anything heavy, since latency during automatic switching forces keyboard and screen reader users to wait mid-navigation.
- The Tabs Pattern from WAI-ARIA recommends automatic activation only when panels display with negligible delay, and manual activation otherwise.
- Whichever model you pick, document it in your component so future contributors don’t flip behavior without realizing the accessibility tradeoff.
Test both models under a throttled network connection before deciding. What feels instant on your development machine can feel sluggish and disorienting for someone relying on a screen reader to confirm the switch happened.
Semantic HTML and markup patterns that actually work
The less ARIA you need, the fewer ways you have to get it wrong, so start with the right base elements before layering on roles and attributes.
- Use
<button>elements for each tab, never<a>tags; buttons communicate an in-page state change, while links imply navigation to a new destination, per guidance from the University of Washington. - Hide inactive panels with the
hiddenattribute ordisplay: none, so assistive technology skips them entirely rather than reading hidden content aloud. - Give the active tabpanel
tabindex="0"when its first piece of content isn’t already focusable, so keyboard and screen reader users land somewhere predictable after switching tabs, as MDN’s tabpanel role documentation recommends. - Wire
aria-controlson each tab to its panel’s ID, andaria-labelledbyon each panel back to its tab’s ID, creating a two-way relationship assistive technology can traverse. - Avoid
role="presentation"on any focusable descendant inside a tab or panel, since it strips meaning from a control that keyboard users still need to operate.
These five habits eliminate most of the ARIA errors that show up in real component libraries.
Implementation checklist and a minimal code example
Before you call a tab component done, run through a short list covering markup, states, and interaction:
- Each tab is a
<button>inside arole="tablist"container with a label. - Each tab has
aria-controls,aria-selected, and a rovingtabindex. - Each panel has
role="tabpanel",aria-labelledby, and is hidden when inactive. - Arrow keys, Home, and End move focus among tabs; Tab and Shift+Tab treat the tablist as one stop.
- Visual focus styles are visible and high contrast, since removing outlines defeats keyboard navigation entirely, a point WebAIM’s keyboard accessibility guidance stresses directly.
For dynamic content, announce panel changes politely to assistive technology with an aria-live="polite" region near the panel, so screen reader users hear that new content loaded without an interruption. On small screens, keep the same keyboard logic intact even if tabs wrap or scroll horizontally, and make sure touch targets stay large enough to tap reliably. For multilingual sites, keep tab labels and panel content in sync with the page’s lang attribute so screen readers switch pronunciation correctly.
Pro Tip: When tabs load content from an API, switch to manual activation and show a loading state inside the panel so screen reader users aren’t left wondering if anything happened.
A minimal pattern: a tablist with buttons carrying role="tab", aria-selected, aria-controls, and roving tabindex, next to panels with role="tabpanel", aria-labelledby, and hidden toggled on inactive ones. A short script listens for arrow key presses, moves focus, updates aria-selected and tabindex together, and toggles the hidden attribute on the matching panel.
Where automated scanning fits into tab accessibility QA
Automated scans reliably catch structural problems: a missing role="tablist", a tab without aria-controls, or a panel stuck with the wrong tabindex. What they can’t verify is timing: whether activation feels instant, whether a screen reader announces the switch correctly, or whether keyboard focus lands where you expect, according to WebAIM’s tabbed interfaces guidance. A solid pipeline runs code through an automated scan first, fixes flagged issues, then follows with manual keyboard and screen reader passes before a final re-scan confirms the fixes held.

Building accessible tabs into your team’s workflow
We’d encourage any team shipping a component library to treat the tabs pattern as a tested, documented unit, not a one-off built per feature; see practical guidance on accessible tab patterns in component libraries at Coumbawin Components. Note which activation model each instance uses and why, keep automated checks running against every release to catch regressions, and hold onto dated records of what you scanned and fixed. That history becomes useful the next time someone questions whether a component actually meets its accessibility bar.
— The AccessWiser Team
AccessWiser: scans, fixes, and documentation for accessible components
Our scanning tool is designed to catch structural issues that break tab components: missing roles, broken aria-controls references, and tabindex mistakes, referencing WCAG 2.2 AA along with mappings to Section 508 and EN 301 549. Each finding includes plain-language guidance to help teams fix the actual markup instead of patching behavior at runtime.
Automated scans won’t replace the keyboard and screen reader testing your tabs still need, but they catch regressions fast and keep dated records of every scan and fix for your compliance documentation. If you want to see what a scan surfaces on your own components, our pricing page covers the Starter, Basic, and Pro plans along with custom Business options.
FAQ
What is the correct ARIA pattern for accessible tabs?
The correct pattern uses role="tablist" on the container, role="tab" on each control, and role="tabpanel" on each content region, linked together with aria-controls and aria-labelledby. This structure is defined in the Tabs Pattern from the WAI-ARIA Authoring Practices and is the version screen readers expect.
Should tabs use buttons or links?
Tabs should use <button> elements, not <a> tags, because buttons represent an in-page state change while links imply navigating somewhere new. This distinction matters for how assistive technology announces the control to the person using it.
What is roving tabindex and why do tabs need it?
Roving tabindex means only the active tab has tabindex="0" while every other tab has tabindex="-1", so Tab moves focus into and out of the group as a single stop and arrow keys handle movement within it. This pattern, detailed in MDN’s tab role documentation, keeps keyboard navigation predictable.
Should I use automatic or manual tab activation?
Use automatic activation only when every panel is already loaded and switches with no noticeable delay; otherwise, manual activation, where the user presses Enter or Space to switch, prevents frustrating waits for keyboard and screen reader users. The WAI-ARIA Authoring Practices recommend manual activation as the safer default for remote-loaded content.
Can automated accessibility scanners fully test tab components?
Automated scanners catch structural issues like missing roles or incorrect tabindex values, but they can’t fully verify keyboard timing, activation latency, or how different screen readers announce the switch. WebAIM’s tabbed interfaces guidance notes that manual keyboard and screen reader testing remains necessary alongside automation.
Sources
- Tabs Pattern | APG | WAI
- ARIA: tab role - MDN Web Docs
- WebAIM: Keyboard accessibility
- Links vs. buttons guidance
Articles on this blog are general information about web accessibility, not legal advice. Laws change and their application depends on your specific situation — for decisions with legal consequences, consult a qualified legal professional.
Articles on this blog are general information about web accessibility, not legal advice. Laws change and their application depends on your specific situation — for decisions with legal consequences, consult a qualified legal professional.