tabindex

Controls whether and in what order an element can receive keyboard focus. Essential for custom widgets, focus management, and accessible single-page applications.

Overview

The tabindex attribute controls whether an element can receive keyboard focus and where it sits in the sequential tab order. It is a global attribute — it can be placed on any HTML element.

Interactive elements like <button>, <a href>, <input>, <select>, and <textarea> are already focusable without tabindex. You only need this attribute when working with non-interactive elements or managing focus programmatically.

Values

ValueBehaviorWhen to Use
0 Element is focusable and included in the natural tab order (based on DOM position) Custom interactive widgets built from non-interactive elements
-1 Element is focusable via JavaScript (element.focus()) but excluded from the tab order Focus management targets: modals, headings after route changes, error summaries
1+ (positive) Element is focusable and placed before all tabindex="0" elements in tab order Avoid. Breaks the natural document order and creates maintenance nightmares.
<!-- Make a non-interactive element focusable in normal tab order --> <div tabindex="0" role="button" onclick="handleClick()"> Custom Button </div> <!-- Focusable programmatically, but NOT via Tab key --> <div id="modal-container" tabindex="-1"> <p>This panel can receive focus via JavaScript, but the user can't Tab to it.</p> </div>

Why Avoid Positive Values

Positive tabindex values (1, 2, 3, etc.) force elements to the front of the tab order, regardless of their position in the document. This creates several problems:

  • Unpredictable navigation: Focus jumps around the page instead of following the visual flow
  • Maintenance burden: Every new focusable element requires re-numbering to maintain order
  • Conflicts at scale: Third-party widgets and your own code compete for tab positions
  • Confusing for all users: Sighted keyboard users and screen reader users alike expect Tab to follow visual order

Instead of adjusting tabindex values, reorder elements in the DOM to match the desired focus sequence.

Native Focusability

These elements are focusable by default and do not need tabindex:

Adding tabindex="0" to these elements is harmless but redundant. Adding tabindex="-1" to them removes them from the tab order while keeping them programmatically focusable — useful for temporarily disabling keyboard access without using disabled.

Common Patterns

Skip Links

A skip link lets keyboard users bypass navigation and jump to the main content. The target needs tabindex="-1" so the skip link can move focus to it.

<!-- Skip link: first focusable element on the page --> <a href="#main-content" class="visually-hidden">Skip to main content</a> <!-- ... site header and navigation ... --> <!-- Target: tabindex="-1" so the skip link can move focus here --> <main id="main-content" tabindex="-1"> <h1>Page Title</h1> <p>Content starts here.</p> </main>

Focus After Action

When an action removes the currently focused element (deleting a list item, closing a panel), focus must be moved somewhere logical. Use tabindex="-1" on the target and call .focus().

<!-- Move focus after a delete action --> <ul id="task-list"> <li> <span>Task A</span> <button onclick="deleteTask(this)">Delete</button> </li> <li> <span>Task B</span> <button onclick="deleteTask(this)">Delete</button> </li> </ul> <script> function deleteTask(button) { const item = button.closest('li'); const next = item.nextElementSibling || item.previousElementSibling; item.remove(); // Move focus to the next item's button, or a fallback if (next) { next.querySelector('button').focus(); } else { document.getElementById('task-list').focus(); } } </script>

SPA Route Changes

In single-page applications, after a client-side navigation the browser does not move focus. Add tabindex="-1" to the new page heading and focus it after the route change so screen reader users know the page has changed.

<!-- After a client-side route change, move focus to the new page heading --> <main> <h1 id="page-heading" tabindex="-1">Dashboard</h1> <p>Welcome back.</p> </main> <script> // After SPA navigation completes: function onRouteChange() { const heading = document.getElementById('page-heading'); heading.focus(); } </script>

Custom Widgets

When building custom interactive components from non-interactive elements, tabindex="0" makes the element reachable via Tab. Always pair it with the appropriate ARIA role and keyboard event handlers.

<!-- Custom toolbar with roving tabindex --> <div role="toolbar" aria-label="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>

Roving Tabindex

For composite widgets like toolbars, tab lists, and menus, the roving tabindex pattern allows only one child to be in the tab order at a time. Arrow keys move focus between children.

<div role="toolbar" aria-label="Text formatting"> <button tabindex="0">Bold</button> <button tabindex="-1">Italic</button> <button tabindex="-1">Underline</button> </div> <script> const toolbar = document.querySelector('[role="toolbar"]'); const buttons = toolbar.querySelectorAll('button'); toolbar.addEventListener('keydown', (e) => { const current = document.activeElement; let next; if (e.key === 'ArrowRight') { next = current.nextElementSibling || buttons[0]; } else if (e.key === 'ArrowLeft') { next = current.previousElementSibling || buttons[buttons.length - 1]; } if (next) { buttons.forEach(b => b.tabIndex = -1); next.tabIndex = 0; next.focus(); } }); </script>

This pattern keeps the Tab key for moving between widgets while arrow keys navigate within a widget — matching the behavior users expect from native controls.

Accessibility

  • Prefer native interactive elements. A <button> is always better than a <div tabindex="0" role="button">. Native elements provide keyboard interaction, focus styling, and screen reader announcements for free.
  • If you add tabindex="0", add a role. Screen readers need an ARIA role to announce what the element is. A focusable <div> without a role is announced as "group" — meaningless to users.
  • If you add tabindex="0", add keyboard handlers. Focusable elements must respond to Enter and (for buttons) Space. Click handlers alone are not enough.
  • Manage focus intentionally. When content changes (modals open, items are deleted, routes change), always move focus to a logical location.
  • Visible focus styles are required. VB provides default :focus-visible outlines. Never remove them without providing an alternative.
  • Test with keyboard only. Put the mouse away and Tab through the page. If you can't reach or operate every interactive element, something is missing.

See Also

  • hidden — remove elements from rendering and focus order entirely
  • Accessibility Guide — overview of VB's accessibility patterns
  • popover — overlay content with built-in focus management
  • <button> — the preferred element for interactive controls
  • <dialog> — modal dialogs with automatic focus trapping