Imagine trying to navigate a busy airport without any signs, maps, or logical pathways—just wandering randomly until you hopefully stumble upon your gate. This is what websites feel like for keyboard users when tab order isn't properly managed. The tabindex attribute is like creating clear, logical pathways that guide users exactly where they need to go.
Tabindex is an HTML attribute that controls the keyboard navigation order of interactive elements on your webpage. When users press the Tab key to move through your site, tabindex determines which element receives focus next, creating a logical flow that should match the visual layout and user expectations.
Keyboard navigation isn't just important for screen reader users—it serves a much broader audience than many developers realize:
When keyboard navigation is broken or illogical, these users may find your website completely unusable, regardless of how visually appealing or functionally rich it might be.
Keyboard accessibility is a core requirement of web accessibility standards like WCAG, and it's increasingly enforced through legal action. Courts have ruled that websites must be navigable by keyboard alone, making proper tabindex implementation not just a usability concern, but a legal necessity for many organizations.
The tabindex attribute can have three different types of values, each serving a specific purpose:
This makes non-interactive elements focusable and includes them in the natural tab order at their position in the DOM.
<!-- Making a div focusable for custom interactive widgets -->
<div tabindex="0" role="button" onclick="toggleMenu()">
Menu Button
</div>
<!-- Making a span focusable for keyboard interaction -->
<span tabindex="0"
role="tab"
onclick="switchTab(1)"
onkeydown="handleTabKeyPress(event)">
Tab 1
</span>
Use tabindex="0" when you need to make custom interactive elements keyboard accessible, but want them to follow the natural document order.
This makes elements focusable programmatically but removes them from the normal tab sequence.
<!-- Skip link that's only focused when activated -->
<a href="#main-content" tabindex="-1" id="skip-link">
Skip to main content
</a>
<!-- Modal elements that should only be focusable when modal is open -->
<div id="modal" style="display: none;">
<button tabindex="-1" onclick="closeModal()">Close</button>
<input type="text" tabindex="-1" placeholder="Search...">
</div>
<!-- Temporarily disabled form field -->
<input type="text" tabindex="-1" aria-disabled="true" readonly>
Use tabindex="-1" for elements that need to be focusable by JavaScript but shouldn't be reached through normal tab navigation.
Positive tabindex values create an explicit tab order, but they override the natural document flow and can create confusing navigation patterns.
<!-- Generally discouraged approach -->
<input type="text" tabindex="3" placeholder="Third">
<input type="text" tabindex="1" placeholder="First">
<input type="text" tabindex="2" placeholder="Second">
<!-- This creates tab order: First, Second, Third
But the visual order is: Third, First, Second
This is confusing for users! -->
Important: Positive tabindex values are generally discouraged because they break the natural flow and are difficult to maintain. It's almost always better to reorganize your HTML structure instead.
Effective tabindex usage follows several key principles that create intuitive keyboard navigation:
Tab order should match the visual reading order of your content—typically left to right, top to bottom in Western layouts.
<!-- Good: HTML order matches visual order -->
<div class="form-row">
<input type="text" placeholder="First Name">
<input type="text" placeholder="Last Name">
</div>
<div class="form-row">
<input type="email" placeholder="Email">
<input type="tel" placeholder="Phone">
</div>
<button type="submit">Submit</button>
<!-- This creates natural tab order:
First Name → Last Name → Email → Phone → Submit -->
If your visual layout doesn't match the HTML order, consider using CSS flexbox or grid with order properties instead of tabindex.
Every element that users can interact with should be reachable via keyboard navigation.
<!-- Ensure custom buttons are focusable -->
<div class="custom-button"
tabindex="0"
role="button"
onclick="performAction()"
onkeydown="if(event.key === 'Enter' || event.key === ' ') performAction()">
Custom Action Button
</div>
<!-- Make sure dropdown triggers are accessible -->
<span class="dropdown-trigger"
tabindex="0"
role="button"
aria-expanded="false"
onclick="toggleDropdown()"
onkeydown="handleDropdownKey(event)">
Options ▼
</span>
When content changes dynamically, manage focus appropriately to keep users oriented.
// JavaScript for managing focus in single-page applications
function navigateToPage(pageId) {
// Hide current page
document.querySelector('.current-page').style.display = 'none';
// Show new page
const newPage = document.getElementById(pageId);
newPage.style.display = 'block';
// Focus the main heading or first interactive element
const heading = newPage.querySelector('h1');
if (heading) {
heading.tabIndex = -1; // Make it focusable
heading.focus();
}
}
// For modal dialogs
function openModal(modalId) {
const modal = document.getElementById(modalId);
modal.style.display = 'block';
// Focus the first focusable element in the modal
const firstFocusable = modal.querySelector('button, input, select, textarea, [tabindex="0"]');
if (firstFocusable) {
firstFocusable.focus();
}
}
When modals or dialogs are open, focus should stay within them until they're closed.
function trapFocus(modal) {
const focusableElements = modal.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const firstFocusable = focusableElements[0];
const lastFocusable = focusableElements[focusableElements.length - 1];
modal.addEventListener('keydown', function(e) {
if (e.key === 'Tab') {
if (e.shiftKey) { // Shift + Tab
if (document.activeElement === firstFocusable) {
lastFocusable.focus();
e.preventDefault();
}
} else { // Tab
if (document.activeElement === lastFocusable) {
firstFocusable.focus();
e.preventDefault();
}
}
}
});
}
Users need to see where keyboard focus is at all times.
/* Ensure focus indicators are visible and consistent */
button:focus,
input:focus,
select:focus,
textarea:focus,
[tabindex="0"]:focus {
outline: 3px solid #4A90E2;
outline-offset: 2px;
}
/* For custom interactive elements */
.custom-button:focus {
background-color: #E3F2FD;
box-shadow: 0 0 0 3px #4A90E2;
}
/* Ensure focus indicators work with your design */
.card:focus {
border: 2px solid #4A90E2;
box-shadow: 0 0 5px rgba(74, 144, 226, 0.5);
}
/* Don't remove focus indicators entirely */
/* BAD: :focus { outline: none; } */
/* Instead, style them to match your design */
Even well-intentioned developers often make mistakes that create confusing or broken keyboard navigation:
What's happening: Using tabindex="1", tabindex="2", etc., to try to control tab order.
Why it's problematic: Positive tabindex values override natural document flow and make maintenance difficult. Elements with positive tabindex are focused first, then elements with tabindex="0" or natural focusability.
Simple solution: Restructure your HTML to match the desired visual order, then rely on natural tab flow:
<!-- Bad: Using positive tabindex -->
<div class="sidebar">
<button tabindex="3">Sidebar Button</button>
</div>
<div class="main-content">
<button tabindex="1">Primary Action</button>
<button tabindex="2">Secondary Action</button>
</div>
<!-- Good: Restructure HTML to match desired order -->
<div class="main-content">
<button>Primary Action</button>
<button>Secondary Action</button>
</div>
<div class="sidebar">
<button>Sidebar Button</button>
</div>
<!-- Use CSS to maintain visual layout -->
.container {
display: flex;
}
.sidebar {
order: -1; /* Visually move sidebar first */
}
What's happening: Adding tabindex="0" to elements like paragraphs, headings, or decorative divs that don't need to be interactive.
Simple solution: Only make elements focusable if users need to interact with them or if they serve as landmarks for screen readers:
<!-- Bad: Making decorative elements focusable -->
<div class="decorative-banner" tabindex="0">
Welcome to Our Site!
</div>
<!-- Good: Only interactive elements are focusable -->
<div class="decorative-banner">
Welcome to Our Site!
</div>
<button onclick="startTour()">Take a Tour</button>
<!-- Exception: Sometimes headings are made focusable for skip links -->
<h1 id="main-heading" tabindex="-1">Main Content</h1>
<!-- This can be focused by skip links but won't be in tab order -->
What's happening: Making custom elements focusable but not responding to expected keyboard interactions.
Simple solution: When you make custom elements focusable, ensure they respond to keyboard events appropriately:
// Bad: Only handling click events
<div tabindex="0" onclick="performAction()">Custom Button</div>
// Good: Handling both click and keyboard events
<div tabindex="0"
role="button"
onclick="performAction()"
onkeydown="handleKeyPress(event)">
Custom Button
</div>
<script>
function handleKeyPress(event) {
// Activate on Enter or Space key
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
performAction();
}
}
</script>
What's happening: In single-page applications, focus isn't managed when content changes, leaving users disoriented.
Simple solution: Implement consistent focus management patterns:
// Good focus management for route changes
function handleRouteChange(newRoute) {
// Update content
updatePageContent(newRoute);
// Move focus to main heading or skip to content
const mainHeading = document.querySelector('h1');
if (mainHeading) {
mainHeading.tabIndex = -1;
mainHeading.focus();
// Remove tabindex after focus (it's just for programmatic focus)
mainHeading.addEventListener('blur', function() {
this.removeAttribute('tabindex');
}, { once: true });
}
}
What's happening: Elements that are hidden or positioned off-screen remain in the tab order, confusing users.
Simple solution: Remove hidden elements from tab order or manage their focusability dynamically:
// For elements hidden with display: none or visibility: hidden
// They're automatically removed from tab order - good!
// For elements hidden off-screen or with opacity
.visually-hidden-but-focusable {
position: absolute;
left: -10000px;
width: 1px;
height: 1px;
overflow: hidden;
}
// For mobile menu items hidden on desktop
@media (min-width: 768px) {
.mobile-menu-item {
display: none; /* Removes from tab order */
}
}
// JavaScript approach for dynamic content
function hideElement(element) {
element.style.display = 'none';
// or element.setAttribute('tabindex', '-1');
}
function showElement(element) {
element.style.display = 'block';
// or element.removeAttribute('tabindex');
}
Some complex interfaces require sophisticated focus management approaches:
For groups of related elements (like toolbars or menus), use roving tabindex to treat the group as a single tab stop.
<!-- HTML structure for toolbar -->
<div role="toolbar" aria-label="Text formatting">
<button tabindex="0" aria-pressed="false">Bold</button>
<button tabindex="-1" aria-pressed="false">Italic</button>
<button tabindex="-1" aria-pressed="false">Underline</button>
</div>
<script>
// JavaScript for roving tabindex
function initializeRovingTabindex(container) {
const items = container.querySelectorAll('[role="button"]');
let currentIndex = 0;
container.addEventListener('keydown', function(e) {
switch(e.key) {
case 'ArrowRight':
e.preventDefault();
currentIndex = (currentIndex + 1) % items.length;
updateFocus();
break;
case 'ArrowLeft':
e.preventDefault();
currentIndex = currentIndex > 0 ? currentIndex - 1 : items.length - 1;
updateFocus();
break;
}
});
function updateFocus() {
items.forEach((item, index) => {
item.tabIndex = index === currentIndex ? 0 : -1;
});
items[currentIndex].focus();
}
}
</script>
For complex interactive components, carefully manage which elements are focusable at any given time.
// Example: Accordion component
function initializeAccordion(accordion) {
const triggers = accordion.querySelectorAll('.accordion-trigger');
const panels = accordion.querySelectorAll('.accordion-panel');
triggers.forEach((trigger, index) => {
trigger.addEventListener('click', () => togglePanel(index));
trigger.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
togglePanel(index);
}
});
});
function togglePanel(index) {
const panel = panels[index];
const trigger = triggers[index];
const isOpen = panel.style.display === 'block';
// Close all panels
panels.forEach(p => p.style.display = 'none');
triggers.forEach(t => t.setAttribute('aria-expanded', 'false'));
if (!isOpen) {
panel.style.display = 'block';
trigger.setAttribute('aria-expanded', 'true');
// Focus first focusable element in opened panel
const firstFocusable = panel.querySelector('button, input, select, textarea, a[href], [tabindex="0"]');
if (firstFocusable) {
firstFocusable.focus();
}
}
}
}
Regular testing ensures your tabindex implementation creates a smooth, logical navigation experience:
The most valuable test is the simplest: try to complete all tasks on your website using only the keyboard. If you can't, neither can your keyboard-only users.
Implementing proper keyboard navigation delivers significant business benefits:
These benefits combine to create websites that are more inclusive, usable, and successful for both users and businesses.
Different types of websites can benefit from thoughtful tabindex implementation:
In each case, the key is understanding user workflows and ensuring that keyboard navigation supports efficient task completion.
Tabindex is more than just a technical attribute—it's a tool for creating inclusive digital experiences that work for everyone. When implemented thoughtfully, it creates clear, logical pathways that guide users through your content efficiently, regardless of how they choose to navigate.
The beauty of good keyboard navigation is that it benefits everyone. While it's essential for users who rely on keyboards due to disability or preference, it also makes websites more efficient and pleasant to use for anyone who appreciates well-structured, logically organized interfaces.
What makes tabindex particularly powerful is its role in bridging the gap between visual design and functional accessibility. It allows developers to create interfaces that look great while ensuring they work well for users with diverse needs and interaction preferences.
As our digital world becomes increasingly complex, the principles behind good tabindex implementation—logical organization, clear navigation paths, and inclusive design—become even more important. By mastering keyboard navigation, you're not just improving accessibility; you're creating better user experiences for everyone.
Greadme's easy-to-use tools can help you identify keyboard navigation issues on your website and provide simple instructions to create logical, accessible tab order—even if you're not technically minded.
Check Your Website's Keyboard Navigation Today