Problem Solved

HTML · tabindex

Home › Courses › HTML › Problems › tabindex
📄 Concept · 3 min read

The tabindex attribute.
Two values you need. One you don't.

tabindex="1" looks like a quick fix for "make this div focusable" or "move focus here first." It's not. It breaks the natural tab order, confuses keyboard users, and hides the real problem — usually that you're using the wrong element. This page covers the three valid values, what each actually does, and why the only two you need are 0 and -1.

Short answer: Use tabindex="0" to make a non-interactive element focusable via Tab (rare — prefer a real <button>). Use tabindex="-1" to focus an element programmatically without affecting the tab order (modals, error messages). Never use positive values (1, 2, ...) — they override the natural order and always break accessibility. Native interactive elements are already focusable; they need no tabindex.

✅ The three values

What each actually does.

tabindex="0"

Focusable via Tab, in the natural order. For non-interactive elements you've given an ARIA role. Rare — real buttons are better.

tabindex="-1"

Focusable programmatically via element.focus(), but not via Tab. For modals, error messages, skip links.

tabindex="1" or higher

Forced custom tab order. Takes the element out of the natural order. Almost always an accessibility bug.

No tabindex

Native interactive elements (<button>, <a href>, <input>) are already focusable. No attribute needed.

✅ Before and after

The same form, with and without positive tabindex.

bad-tabindex.html
<form>
<label for="name">Name</label>
<input id="name" type="text" tabindex="3">

<label for="email">Email</label>
<input id="email" type="email" tabindex="1">

<label for="password">Password</label>
<input id="password" type="password" tabindex="2">

<button type="submit">Submit</button>
</form>
Tab order with positive tabindex:
1. Email (tabindex="1")
2. Password (tabindex="2")
3. Name (tabindex="3")
4. Submit (natural — no tabindex)

But visual order is:
1. Name
2. Email
3. Password
4. Submit

❌ Tab jumps: Name → Email → Password → Name → Email...
❌ Sighted keyboard users are confused.
❌ Screen reader users hear fields in wrong order.
❌ Autofill flows to the wrong field.
good-tabindex.html
<form>
<label for="name">Name</label>
<input id="name" type="text">

<label for="email">Email</label>
<input id="email" type="email">

<label for="password">Password</label>
<input id="password" type="password">

<button type="submit">Submit</button>
</form>
Tab order (natural):
1. Name
2. Email
3. Password
4. Submit

✅ Tab order matches visual order.
✅ Keyboard users navigate predictably.
✅ Screen readers announce fields in order.
✅ No tabindex needed — native elements.

Rule: if the DOM order matches visual order,
you don't need tabindex. If it doesn't,
fix the DOM order, not the tab order.

✅ The two valid uses

Both are for focus management, not navigation.

valid-tabindex.html
<!-- tabindex="-1" on a modal dialog -->
<div role="dialog" aria-modal="true"
aria-labelledby="dialog-title"
tabindex="-1">
<h2 id="dialog-title">Confirm delete</h2>
<button>Cancel</button>
<button>Delete</button>
</div>

<!-- tabindex="-1" on an error message -->
<div role="alert" tabindex="-1" id="form-error">
Please fix the errors below.
</div>

<!-- tabindex="0" on a custom div button (avoid!) -->
<div role="button" tabindex="0"
onclick="..." onkeydown="...">
Custom Button
</div>

<!-- Better: use a real button -->
<button>Real Button</button>
tabindex="-1" uses:

Modal dialog: dialog.focus() when opened.
Tab still cycles inside the dialog,
but the container itself isn't in the
tab order. Screen reader announces it.

Error message: formError.focus() after failed
submit. Screen reader announces the
alert. Tab skips the alert otherwise.

tabindex="0" uses:

Custom button: Only when a real button won't fit.
Must handle: click, Enter, Space,
focus styles, disabled state.
Real button does all of this for free.

Rule: use the native element. Add tabindex
only when a native element won't work.

Don't use positive tabindex to fix visual ordering. If the DOM order doesn't match what you want to be the tab order, the fix is the DOM order — not tabindex="1". Reorder the elements in the markup. Use CSS to position them visually if you need a different visual layout. When CSS order on flex/grid moves things, the tab order stays in DOM order — that's a real accessibility problem, but the fix is to keep DOM order and visual order aligned, not to patch it with tabindex. Positive tabindex is a symptom, not a fix.

⚠️ Common mistakes

The tabindex mistakes that cost real time.

1. tabindex="1" or higher

Breaks natural order. Fix: remove it. Reorder the DOM if needed.

2. tabindex="0" on a button or link

Redundant — they're already focusable. Fix: remove it.

3. tabindex="-1" on a link

Removes it from tab order. Fix: only use -1 for programmatic focus targets.

4. Using tabindex to make a div clickable

You also need keyboard handling, focus styles, ARIA role. Fix: use <button>.

5. No focus visible on tabindex="0"

Keyboard users can't see where focus is. Fix: add a visible :focus style.

6. tabindex on hidden elements

display: none is not focusable regardless of tabindex. Fix: fix the visibility issue.

7. Removing outline without replacement

outline: none on :focus — keyboard users get lost. Fix: replace with a visible focus style.

8. tabindex on <body>

A legacy pattern that breaks focus. Fix: never put tabindex on body.

❓ Frequently asked

What does tabindex do?

tabindex controls whether an element is focusable and where it appears in the Tab key order. Three valid values: tabindex='0' makes any element focusable and adds it to the natural tab order; tabindex='-1' makes an element focusable programmatically (via element.focus()) but not via Tab; tabindex with a positive number (1, 2, 3...) forces a custom tab order, which almost always breaks things. Native interactive elements (button, a, input) are already focusable — you do not need tabindex on them.

Why is tabindex='1' bad?

tabindex with a positive value removes the element from the natural tab order and creates a custom order. Users pressing Tab jump to the tabindex='1' element first, then tabindex='2', regardless of visual position. Screen reader users, keyboard-only users, and switch access users all expect the natural order: the page flows top to bottom, left to right. Positive tabindex values break this expectation and create confusion. The WebAIM Million study finds tabindex='1' on a page in 20% of top-100 sites — every one is an accessibility bug.

When should I use tabindex='0'?

Use tabindex='0' when you have made a non-interactive element into an interactive one with ARIA — for example, a div with role='button' that responds to click and Enter. Adding tabindex='0' makes it focusable via Tab. But the better fix is to use a real button element, which is already focusable and handles Enter, Space, and focus styles. Use tabindex='0' only when a native element truly doesn't fit.

When should I use tabindex='-1'?

Use tabindex='-1' when you need to focus an element programmatically but not via Tab. Common cases: a modal dialog that receives focus when it opens (call dialog.focus() in the open handler), an error message that is scrolled into view and focused after a failed validation, a section that is the target of an anchor link where you want to announce it to screen readers. tabindex='-1' is safe — it doesn't affect the tab order, it just allows programmatic focus.

Do I need tabindex on buttons and links?

No. button, a with href, input, select, textarea, and other native interactive elements are focusable by default. Adding tabindex='0' to a button is redundant. Adding tabindex='-1' to a button removes it from the tab order — that is almost never what you want. If a button is not focusable, the problem is elsewhere (display: none, visibility: hidden, disabled, or inside an inert container). Fix the cause, not with tabindex.

How do I test keyboard navigation?

Press Tab to move forward, Shift+Tab to move backward, Enter to activate buttons, Space to activate buttons and check checkboxes, Escape to close dialogs. Watch where focus lands — it should follow the visual order of the page, top to bottom, left to right. Focus should be visible — a visible outline or focus ring. If you can't tab to a visible control, or if tab order jumps around unexpectedly, tabindex or CSS is wrong. Test with a screen reader too — NVDA, JAWS, or VoiceOver.

🔗 Related

← All HTML problems
Problem solved · HTML