The tabindex Attribute
The tabindex Attribute
Section titled “The tabindex Attribute”The tabindex attribute controls whether an element can receive keyboard focus and defines its position in the sequential keyboard navigation order (the “tab order”). It’s crucial for keyboard accessibility and focus management in web applications.
Syntax
Section titled “Syntax”<element tabindex="value">Content</element>Values
Section titled “Values”| Value | Description | Use Case |
|---|---|---|
Negative (e.g., -1) | Focusable but not in tab order | Programmatic focus only |
0 | Focusable and in natural tab order | Make custom elements focusable |
Positive (e.g., 1, 2) | Focusable and in explicit tab order | Generally avoid |
Examples
Section titled “Examples”Basic Tab Order
Section titled “Basic Tab Order”Making Custom Elements Focusable
Section titled “Making Custom Elements Focusable”Focus Management with tabindex=“-1”
Section titled “Focus Management with tabindex=“-1””Why Avoid Positive tabindex Values
Section titled “Why Avoid Positive tabindex Values”Understanding tabindex Values
Section titled “Understanding tabindex Values”tabindex=“-1” (Programmatic Focus Only)
Section titled “tabindex=“-1” (Programmatic Focus Only)”Purpose: Make an element focusable via JavaScript, but skip it in tab order.
Use cases:
- Modal dialogs that need focus when opened
- Error messages for screen reader announcements
- Skip link targets
- Panels in tab interfaces
<!-- Focus moved programmatically --><div id="modal" tabindex="-1"> <h2>Modal Title</h2> <p>Content...</p></div>
<script> document.getElementById('modal').focus();</script>Important: Elements with tabindex="-1" can still be clicked with a mouse.
tabindex=“0” (Natural Tab Order)
Section titled “tabindex=“0” (Natural Tab Order)”Purpose: Add non-interactive elements to the tab order.
Use cases:
- Custom interactive components (widgets, custom buttons)
- Containers that need focus for keyboard interaction
- Skip navigation links
- Accessibility landmarks that need focus
<!-- Make a div behave like a button --><div tabindex="0" role="button" onclick="handleClick()"> Custom Button</div>
<!-- Focusable region --><div tabindex="0" role="region" aria-label="Interactive map"> <!-- Complex interactive content --></div>Note: Always add tabindex="0" to the correct position in the DOM to maintain logical tab order.
Positive Values (AVOID)
Section titled “Positive Values (AVOID)”Why to avoid:
- Unpredictable order: Disrupts natural document flow
- Maintenance nightmare: Hard to track which elements have which values
- Accessibility issues: Confuses keyboard users
- Breaks expectations: Users expect tab to follow visual order
<!-- DON'T DO THIS --><button tabindex="1">First</button><input tabindex="3" type="text"><button tabindex="2">Second</button>
<!-- DO THIS INSTEAD --><button>First</button><button>Second</button><input type="text">Best Practices
Section titled “Best Practices”1. Prefer Natural Tab Order
Section titled “1. Prefer Natural Tab Order”Let the DOM order define tab order:
<!-- Good: Visual and tab order match --><form> <input type="text" name="first"> <input type="text" name="second"> <button>Submit</button></form>2. Use Semantic HTML First
Section titled “2. Use Semantic HTML First”Interactive elements are naturally focusable:
<!-- No tabindex needed --><button>Click me</button><a href="/page">Link</a><input type="text"><select>...</select><textarea>...</textarea>3. Add ARIA Roles with tabindex
Section titled “3. Add ARIA Roles with tabindex”When making custom elements focusable, add appropriate ARIA roles:
<!-- Complete accessible custom button --><div tabindex="0" role="button" aria-pressed="false" onclick="toggle()" onkeydown="if(event.key==='Enter') toggle()"> Toggle</div>4. Manage Focus Programmatically
Section titled “4. Manage Focus Programmatically”Use tabindex="-1" for focus management patterns:
<div id="content" tabindex="-1"> <h1>Page Title</h1> <!-- Content --></div>
<script> // Skip to content functionality document.getElementById('skipLink').addEventListener('click', (e) => { e.preventDefault(); document.getElementById('content').focus(); });</script>5. Test with Keyboard Only
Section titled “5. Test with Keyboard Only”Always test navigation using only the keyboard:
- Tab: Move forward through focusable elements
- Shift+Tab: Move backward
- Enter: Activate buttons/links
- Space: Activate buttons, check checkboxes
- Arrow keys: Navigate within components
Common Patterns
Section titled “Common Patterns”Skip Navigation Link
Section titled “Skip Navigation Link”<a href="#main" class="skip-link">Skip to main content</a>
<main id="main" tabindex="-1"> <!-- Main content --></main>
<style> .skip-link { position: absolute; top: -40px; left: 0; background: #000; color: #fff; padding: 8px; text-decoration: none; } .skip-link:focus { top: 0; }</style>Modal Dialog Focus Trap
Section titled “Modal Dialog Focus Trap”const modal = document.getElementById('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', (e) => { if (e.key === 'Tab') { if (e.shiftKey && document.activeElement === firstFocusable) { e.preventDefault(); lastFocusable.focus(); } else if (!e.shiftKey && document.activeElement === lastFocusable) { e.preventDefault(); firstFocusable.focus(); } }});Custom Tab Panel
Section titled “Custom Tab Panel”<div role="tablist"> <button role="tab" aria-selected="true" aria-controls="panel1" tabindex="0"> Tab 1 </button> <button role="tab" aria-selected="false" aria-controls="panel2" tabindex="-1"> Tab 2 </button> <button role="tab" aria-selected="false" aria-controls="panel3" tabindex="-1"> Tab 3 </button></div>
<div role="tabpanel" id="panel1" tabindex="0"> Panel 1 content</div>Accessibility Considerations
Section titled “Accessibility Considerations”Focus Visibility
Section titled “Focus Visibility”Always ensure focused elements are clearly visible:
/* Good focus indicator */:focus { outline: 3px solid #3b82f6; outline-offset: 2px;}
/* Don't remove outlines without replacement */:focus { outline: none; /* Only if you provide an alternative */ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.5);}Screen Reader Considerations
Section titled “Screen Reader Considerations”- tabindex=“0” adds elements to tab order and makes them programmatically focusable
- tabindex=“-1” makes elements focusable but announces “not in tab order” to some screen readers
- Always pair with appropriate ARIA roles and labels
<!-- Complete accessible custom element --><div tabindex="0" role="button" aria-label="Close dialog" onclick="close()"> ×</div>Focus Order Matches Visual Order
Section titled “Focus Order Matches Visual Order”Ensure tab order matches visual layout:
<!-- Bad: Confusing for keyboard users --><div style="display: flex; flex-direction: column-reverse;"> <button>Visually Second</button> <button>Visually First</button></div>
<!-- Good: Tab order matches visual order --><div style="display: flex; flex-direction: column-reverse;"> <button style="order: 2">Visually Second</button> <button style="order: 1">Visually First</button></div>Common Pitfalls
Section titled “Common Pitfalls”Browser Support
Section titled “Browser Support”Universal support across all modern and legacy browsers:
| Browser | Support |
|---|---|
| Chrome | All versions |
| Firefox | All versions |
| Safari | All versions |
| Edge | All versions |
| IE | 5.5+ |
Related Attributes
Section titled “Related Attributes”autofocus- Automatically focus an element on page loadaccesskey- Keyboard shortcut to focus an elementcontenteditable- Makes elements editable and focusable