Welcome to Vanilla Breeze
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
Controls whether and in what order an element can receive keyboard focus. Essential for custom widgets, focus management, and accessible single-page applications.
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.
| Value | Behavior | When 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>
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:
Instead of adjusting tabindex values, reorder elements in the DOM to match the desired focus sequence.
These elements are focusable by default and do not need tabindex:
<button><a href="..."> (links with an href)<input>, <select>, <textarea><details> (via its <summary>)contenteditableAdding 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.
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>
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>
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>
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>
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.
<button> is always better than a <div tabindex="0" role="button">. Native elements provide keyboard interaction, focus styling, and screen reader announcements for free.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.tabindex="0", add keyboard handlers. Focusable elements must respond to Enter and (for buttons) Space. Click handlers alone are not enough.:focus-visible outlines. Never remove them without providing an alternative.hidden — remove elements from rendering and focus order entirelypopover — overlay content with built-in focus management<button> — the preferred element for interactive controls<dialog> — modal dialogs with automatic focus trappingtabindex="-1" on its target