Tab order and document flow
Natural tab order is a free gift from HTML. Don't break it without a really good reason.
Press Tab on any web page. Something gets a focus ring. Press it again — focus moves to the next interactive thing. That sequence is called tab order, and getting it right is the foundation of every other lesson in this course.
The good news: HTML gives you a sensible tab order for free. Buttons, links, inputs, and other native controls are focusable by default, and they appear in the order they show up in the markup. The bad news: the moment you start moving things around with CSS, swapping <button> for <div>, or adding tabindex numbers, you can break that gift in surprising ways.
In this lesson you'll learn what "focusable" means, what each tabindex value does, and the one rule that prevents 90% of tab-order bugs.
What "natural" tab order is
A page has two layers of order:
- Document order — the order elements appear in the HTML source, top to bottom.
- Visual order — where they end up on screen after CSS is applied.
By default, tab order follows document order, not visual order. The browser walks down your HTML, stops at every focusable element, and that's the sequence. CSS positioning, flexbox order, and grid placement do not change this.
<article> <h2>Sign up</h2> <button>Cancel</button> <button>Submit</button> </article>
Even if CSS swaps the buttons visually so Submit appears on the left and Cancel on the right, Tab will still hit Cancel first — because it comes first in the source. That mismatch is one of the most common keyboard bugs on the web.
The fix is almost never tabindex. The fix is changing the source order to match the visual order. Then everyone, mouse users included, gets the right reading flow.
Native focusable elements include <a href>, <button>, form inputs (<input>, <select>, <textarea>), <summary>, and any element with a non-negative tabindex. A <div> with a click handler is not focusable — keyboard users can't reach it without help.
tabindex, decoded
tabindex is a global HTML attribute that takes a single number. There are three meaningful values, and they do very different things.
tabindex="0" puts an element into the natural tab order at its document-order position. Use this when you've built a custom interactive widget out of a <div> or <span> and you genuinely need it to be reachable by Tab. (Better still: use a real <button>.)
tabindex="-1" removes the element from the tab order, but lets your JavaScript still call .focus() on it. This is the workhorse of focus management: it lets you focus a heading, a dialog, or a list container without adding a new tab stop for keyboard users to wade through.
tabindex="1" (or any positive number) is a trap, which deserves its own section.
Why positive numbers go wrong
A positive tabindex means "jump this element to the front of the tab order, ahead of everything that doesn't have a positive value." If multiple elements have positive values, they're sorted ascending, then the rest of the page follows.
This sounds clever and is almost always a disaster. Reasons:
- It overrides document order in a way users can't see. A keyboard user expects Tab to flow with the page; a positive
tabindexmakes focus jump to a corner first, then back to the top. - It compounds. As soon as one part of the page has
tabindex="1", every other "important" thing wantstabindex="2",tabindex="3", and now you're maintaining a global ordered list across components. - It survives reorders. Shift the layout, and the numbers stay where they were, silently misaligning order from layout.
Hard rule: don't use positive tabindex. If something's tab order is wrong, fix the source order or the focus management — not the index number.
The Web Content Accessibility Guidelines bake this in: success criterion 2.4.3 ("Focus Order") wants tab order to preserve meaning and operability. Positive tabindex works against that.
Fixing broken order
When you find a tab-order bug, walk through this short checklist before reaching for tabindex:
- Is the element using the right tag? A clickable
<div>is the most common cause of skipped focus. Swap it for<button>or<a>. - Does the source order match the visual order? If CSS rearranged things, move them in the markup instead. Flexbox
orderand grid placement should be cosmetic, not structural. - Is there an invisible focusable element grabbing focus? Hidden elements that only use
display: noneare out of the tab order, but elements only hidden withvisibility: hiddenoropacity: 0may still be focusable. Useinert(oraria-hiddenplus removing them from focus) when an entire region should be skipped. - Is something custom that needs to be focusable? Now
tabindex="0"is appropriate — paired with a role and keyboard handlers. (Lesson 5 covers this.)
<!-- The whole sidebar is skipped on Tab while the modal is open --> <aside inert> <button>Sort</button> <button>Filter</button> </aside> <dialog open> <button>Confirm</button> <button>Cancel</button> </dialog>
The inert attribute is a relatively new tool: it removes a subtree from focus, click, and assistive-tech reach in one line. Lesson 4 leans on it heavily for modals.
tabindex="-1" do?Try it yourself
order. Tab focuses Cancel first. What's the best fix?tabindex attribute?