Have you ever pressed Tab to move through a form and found that the focus skips around unpredictably? Or maybe you have custom buttons that you cannot reach with the keyboard at all? The tabindex attribute controls which elements are focusable and in what order. It is essential for keyboard accessibility.
Tabindex Values Explained
The tabindex attribute accepts three types of values. 0 puts the element in the natural tab order. Positive values (1, 2, 3...) create a custom tab order and are processed first. -1 makes an element focusable only with JavaScript, not with the Tab key.
<!-- Natural tab order (follows DOM order) -->
<button tabindex="0">First</button>
<button tabindex="0">Second</button>
<!-- Custom order (positive values go first) -->
<input tabindex="3" placeholder="Third">
<input tabindex="1" placeholder="First">
<input tabindex="2" placeholder="Second">
<!-- Focusable only via JavaScript -->
<div tabindex="-1" id="focusableDiv">Focus me with JS</div>
Making Custom Elements Focusable
If you build a custom button using a <div>, it is not focusable by default. Adding tabindex="0" makes it reachable with the Tab key. You should also listen for the Enter and Space keys to trigger the same action as a click.
<div role="button" tabindex="0" id="customBtn">
Custom Button
</div>
<script>
const btn = document.getElementById('customBtn');
btn.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
btn.click();
}
});
</script>
The Accessibility Problem with Positive Values
Positive tabindex values are almost always a bad idea. They override the natural document order, which confuses users who expect tab order to follow the visual layout. If you need elements in a different tab order, rearrange the HTML instead of using positive tabindex values.
<!-- Avoid this: positive tabindex values -->
<input tabindex="3" placeholder="Third">
<input tabindex="1" placeholder="First">
<input tabindex="2" placeholder="Second">
<!-- Do this instead: put them in the right order in HTML -->
<input placeholder="First">
<input placeholder="Second">
<input placeholder="Third">
Focus Trapping and Management
For modal dialogs and complex widgets, you often need to trap focus within a section. Using tabindex="-1" on elements that should be focusable via JavaScript but not via Tab, combined with element.focus(), gives you fine-grained control over focus movement.
<div role="dialog" aria-modal="true">
<button tabindex="-1" id="closeBtn">Close</button>
<input type="text" placeholder="Enter name">
</div>
<script>
// When dialog opens, focus the first input
dialog.addEventListener('open', () => {
dialog.querySelector('input').focus();
});
</script>