Tabs & Toolbars
Toolbar
AvailableGroups a set of buttons so you Tab in once and navigate among them with arrow keys.
What Is a Toolbar?
A toolbar is a UI element that groups related action buttons together in a single row. Common examples include "Bold / Italic / Underline" in a text editor, a toolbox in an image editor, and playback controls in a media player.
Simply lining up buttons is easy, but the accessibility key is"making the entire toolbar a single group = one tab stop."
Why Does Accessibility Matter?
- People who use only the keyboard. If each button is its own tab stop, a toolbar with 10 tools requires pressing Tab 10 times to reach the content below. The convention for toolbars is toenter with a single Tab, then navigate inside with ←→.
- Screen reader users. With
role="toolbar"andaria-label, the toolbar is announced as a cohesive group (e.g., "Text formatting toolbar"), making it easy to understand and navigate the buttons within.
The mechanism that achieves this "one tab stop per group" behavior isroving tabindex (a technique that keeps only one item focusable at a time).
Live Demo (Recommended Implementation)
The toolbar below follows the APG specification. Try navigating it with the keyboard along with the input fields before and after it.
Try it: Tab into the toolbar (one press to enter) → ← → to move between buttons → Home / End to jump to edges → Enter / Space to toggle → Tab again to exit to the next element.
Tip
Buttons that toggle on/off (like Bold) should have aria-pressed. Screen readers will announce something like "Bold, toggle button, pressed," conveying the pressed state.
Keyboard Interaction
| Key | Action | Priority |
|---|---|---|
| Tab | Enter / leave the toolbar (the entire toolbar is a single tab stop) | Required |
| ← / → | Move focus to the previous / next button | Required |
| Home / End | Move to the first / last button | Recommended |
| Enter / Space | Activate / toggle the focused button | Required |
Note
When you Tab out of a toolbar and then return, focus goes back to thelast focused button (because roving tabindex moves thetabindex="0" position). This is the behavior recommended by the APG.
Required WAI-ARIA Roles & Properties
| Target | Attribute / Role | Meaning |
|---|---|---|
| Toolbar container | role="toolbar" | Conveys that it is a collection of action buttons. |
| Toolbar container | aria-label (or aria-labelledby) | Provides a name for the toolbar, such as "Text Formatting". |
| Each button | tabindex="0 | -1" | Only the focused button gets 0; all others get -1 (roving tabindex). |
| Toggle button | aria-pressed="true | false" | Conveys the on/off state of a toggle button. |
| Separator | role="separator" + aria-orientation | Visually and semantically separates groups of buttons (optional). |
Recommended Pattern (Good)
Good / Recommended
Wrap with role="toolbar", and use roving tabindex so thatonly one button has tabindex="0". Arrow keys move focus between buttons.
Markup:
<div role="toolbar" aria-label="Text formatting">
<!-- Only the first button has tabindex="0"; the rest are -1 (roving tabindex) -->
<button type="button" aria-pressed="false" tabindex="0">Bold</button>
<button type="button" aria-pressed="false" tabindex="-1">Italic</button>
<button type="button" aria-pressed="false" tabindex="-1">Underline</button>
</div>Roving tabindex and arrow key script:
document.querySelectorAll('[role="toolbar"]').forEach((bar) => {
const items = Array.from(bar.querySelectorAll('button'));
function focusItem(i) {
items.forEach((b, n) => (b.tabIndex = n === i ? 0 : -1)); // Only one is 0
items[i].focus();
}
items.forEach((btn, i) => {
// Toggle buttons flip aria-pressed
btn.addEventListener('click', () => {
btn.setAttribute('aria-pressed',
String(btn.getAttribute('aria-pressed') !== 'true'));
});
btn.addEventListener('keydown', (e) => {
let next = null;
if (e.key === 'ArrowRight') next = (i + 1) % items.length;
else if (e.key === 'ArrowLeft') next = (i - 1 + items.length) % items.length;
else if (e.key === 'Home') next = 0;
else if (e.key === 'End') next = items.length - 1;
if (next !== null) { e.preventDefault(); focusItem(next); }
});
});
});Anti-Pattern (Bad)
The toolbar below has every button as a separate tab stop with no arrow key support. Furthermore, "Left align" is a <div> button that can't even receive focus.
Try it: Each Tab press advances one button at a time (the more tools, the worse it gets), and ← → does nothing. 'Left align' is skipped by Tab and can't be activated with the keyboard.
<!-- ❌ Anti-pattern -->
<div class="toolbar">
<!-- Every button is a separate tab stop. No arrow key navigation; users must press Tab repeatedly -->
<button>Bold</button>
<button>Italic</button>
<button>Underline</button>
<!-- A div button can't even receive focus -->
<div class="btn" onclick="align('left')">Left align</div>
</div>Bad / Avoid
Problems with this implementation:
- One button = one tab stop — The more tools there are, the more Tab presses are needed. Not APG-compliant.
- No arrow key navigation — Neither roving tabindex nor
role="toolbar"is present. - Div buttons are not focusable — A
divwithouttabindexorrolecannot be reached by keyboard. - No group semantics — Without
role="toolbar"/aria-label, it sounds like a random list of buttons.
Implementation Checklist
- Button group is wrapped with role="toolbar" and aria-label
- The entire toolbar is a single tab stop (enter with one Tab press)
- Roving tabindex: only the focused button has tabindex="0", others have -1
- Left/Right arrow keys move between buttons; Home/End move to the edges
- Each button uses a native <button> element (not div)
- Toggle buttons have aria-pressed and the state is updated accordingly
- Returning to the toolbar restores focus to the last focused button
- Focus is visible (focus ring is not removed)
Source (English):Toolbar Pattern — W3C APG(opens in a new tab)