Tab Order Best Practices
Tab Order Best Practices
Tab order determines the sequence in which users navigate through interactive elements using the Tab key. A logical, predictable tab order is essential for keyboard users and assistive technology users. This guide provides comprehensive best practices for managing tab order effectively.
How Tab Order Works
By default, tab order follows the DOM order. Elements receive focus in the order they appear in your HTML:
<!-- Focus order: 1 → 2 → 3 -->
<button>1. First</button>
<button>2. Second</button>
<button>3. Third</button>The Golden Rules
1. DOM Order = Visual Order
Your HTML structure should match the visual layout:
<!-- Good: DOM matches visual order -->
<header>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/contact">Contact</a>
</nav>
</header>
<main>
<h1>Welcome</h1>
<button>Primary Action</button>
<button>Secondary Action</button>
</main>2. Never Use Positive tabindex
<!-- BAD: Positive tabindex creates confusion -->
<button tabindex="3">Third</button>
<button tabindex="1">First</button>
<button tabindex="2">Second</button>
<!-- Good: Natural order -->
<button>First</button>
<button>Second</button>
<button>Third</button>Using positive tabindex values creates a separate "priority" focus order that precedes normal tab order, this quickly becomes unmanageable.
3. Only Use tabindex="0" and tabindex="-1"
<!-- tabindex="0": Make non-interactive element focusable -->
<div tabindex="0" role="button" onclick="action()">
Custom widget
</div>
<!-- tabindex="-1": Programmatic focus only -->
<h2 tabindex="-1" id="section-header">
Section Title
</h2>
<script>
// Focus after user action
document.getElementById('section-header').focus();
</script>CSS Layout and Tab Order
Flexbox Direction Traps
/* Visual: C B A | Tab: A B C */
.container {
display: flex;
flex-direction: row-reverse;
}Solution: Reorder DOM elements, or use order property with caution:
<!-- Reordered DOM for correct tab order -->
<div class="container">
<button style="order: 3">A (Visual first)</button>
<button style="order: 2">B (Visual second)</button>
<button style="order: 1">C (Visual third)</button>
</div>Grid Placement Issues
/* Visual order disrupted by grid placement */
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
}
.item-c {
grid-column: 1; /* Moves C visually to the left */
}Common Tab Order Patterns
Header Navigation
<header>
<a href="/" class="logo">Logo</a>
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/products">Products</a></li>
<li><a href="/about">About</a></li>
</ul>
</nav>
<button class="menu-toggle" aria-label="Menu">☰</button>
</header>Tab order: Logo → Home → Products → About → Menu toggle
Form Layout
<form>
<div class="form-row">
<label for="first">First Name</label>
<input id="first" type="text">
</div>
<div class="form-row">
<label for="last">Last Name</label>
<input id="last" type="text">
</div>
<div class="form-row">
<label for="email">Email</label>
<input id="email" type="email">
</div>
<button type="submit">Submit</button>
</form>Tab order follows reading order: First Name → Last Name → Email → Submit
Multi-Column Layouts
For multi-column layouts, decide on the reading pattern:
<!-- Row-by-row reading (most common for Western audiences) -->
<div class="grid">
<!-- Row 1 -->
<article>Item 1</article>
<article>Item 2</article>
<article>Item 3</article>
<!-- Row 2 -->
<article>Item 4</article>
<article>Item 5</article>
<article>Item 6</article>
</div>Removing from Tab Order
Sometimes elements shouldn't be in the tab order:
<!-- Hidden content -->
<div hidden>
<button>Not focusable</button>
</div>
<!-- Off-screen but announced -->
<nav aria-hidden="true" tabindex="-1">
<a href="/" tabindex="-1">Home</a>
</nav>
<!-- Disabled elements -->
<button disabled>Can't focus me</button>
<!-- inert attribute (when supported) -->
<div inert>
<button>Not focusable</button>
</div>Dynamic Content Tab Order
When adding content dynamically, maintain logical order:
// Adding a new item
function addItem(container, content) {
const item = document.createElement('button');
item.textContent = content;
// Append at the end (natural tab order)
container.appendChild(item);
// Or insert at a specific position
const referenceNode = container.children[2];
container.insertBefore(item, referenceNode);
}Testing Tab Order
- Tab through the entire page: Does the order make sense?
- Shift+Tab backwards: Reverse order logical?
- Compare visual to tab order: Any mismatches?
- Test all viewport sizes: Order consistent at all sizes?
- Check dynamic content: New content in logical position?
Tab Order Checklist
- No positive tabindex values used
- Tab order follows visual layout
- CSS layout doesn't disrupt focus order
- All interactive elements reachable via Tab
- Hidden content removed from tab order
- Dynamic content maintains logical order
- Tested on mobile and desktop viewports
Was this article helpful?