PLAINFINCH learn by building

Tab order

You will make Tab move through a page in the order people read it, and remove stops on things nobody can use.

What decides the order Tab moves in?

By default, the order of the HTML. The browser walks the document from top to bottom and stops on every focusable element it meets: links with an href, buttons, form fields, <summary>, and anything with a tabindex of zero or more. Shift+Tab walks the same list backwards.

Most of the time, that is exactly what you want. A page written in a sensible order, header, then main content, then footer, gets a sensible Tab order with no extra work. WCAG asks for it in 2.4.3 Focus Order: when focus moves in a sequence, the order must keep the meaning and the way the page works. Its partner 1.3.2 Meaningful Sequence asks that the reading order, the order a screen reader follows, makes sense too. Both come from the same source order.

What do the different tabindex values do?

The tabindex attribute changes whether and when an element takes focus. Its three kinds of values behave very differently.

The everyday rule is short. Use 0 rarely, for custom controls only. Use -1 for things you focus with script. Never use a positive number.

  • tabindex="0" makes an element focusable and puts it in the normal Tab order, where it sits in the HTML. Use it for a custom control built from a generic element, after checking that no native element would do.
  • tabindex="-1" makes an element focusable by script, with element.focus(), and by clicking, while keeping it out of the Tab order. Use it for a heading you move focus to after a page update, or the target of a skip link.
  • A positive value, such as tabindex="1", pulls the element to the front of the Tab order, ahead of every element without one. Elements with positive values go first, lowest number first, then the rest follow in source order.

Why is a positive tabindex so harmful?

Look at the starting page in the **Keyboard** view with numbers on. The button has tabindex="1", the name field 2 and the email field 3. The author probably wanted the form to be quick to reach. The result is that the first Tab press jumps past the menu and the heading to Book my repair, before either field has been filled in. The next two presses go back up to the fields, and only then do the menu links come round.

Positive values also break as pages grow. Each new link or field without a tabindex goes to the back of the queue, so anyone adding to the page has to renumber everything. On a site with shared headers and footers, numbers collide between templates. Screen reader users hear content in source order but Tab through it in a different one, so focus seems to jump around the page at random.

The fix is almost always to delete the attribute. If the order is wrong without it, change the order of the HTML.

<!-- Tab hits the button first, then goes back up -->
<input id="name" tabindex="2">
<input id="email" tabindex="3">
<button tabindex="1">Book my repair</button>

<!-- Tab follows the page -->
<input id="name">
<input id="email">
<button>Book my repair</button>
FigureTab order on the Book a repair page. With tabindex 1, 2, 3 the order is Book my repair, Your name, Email, Home, Shop, then the tip. With them removed it is Home, Shop, Your name, Email, Book my repair.Tab order on the Book a repair page. With tabindex 1, 2, 3 the order is Book my repair, Your name, Email, Home, Shop, then the tip. With them removed it is Home, Shop, Your name, Email, Book my repair.
Positive numbers pull the button to press 1, before the fields it submits, and the tip becomes a pointless stop. Delete them and Tab follows the page.

When does tabindex="0" do more harm than good?

The tip on the starting page has tabindex="0", probably so keyboard users would notice it. The result is a Tab stop with nothing to do. Keyboard users who can see the page read the tip without focusing it. Screen reader users read it with their own reading keys, in browse mode, without Tab. Making it focusable only adds a press between them and the form.

AI tools and some component libraries add tabindex="0" to cards, list items and headings to make them “accessible”. Without a role and a key handler, a focusable <div> is still a box a screen reader can't describe and a keyboard can't activate. If something can be acted on, use a link or a button. If it can't, it doesn't belong in the Tab order.

How can CSS break the Tab order?

CSS can move things on screen without moving them in the HTML. The flexbox order property, flex-direction: row-reverse, grid placement with grid-row and grid-column, and absolute positioning all change where an element appears. Tab and screen readers ignore all of that and follow the source.

Picture the bike shop's header with the Book button placed first visually by order: -1, while it sits last in the HTML. A sighted keyboard user presses Tab, focus jumps to the far right of the screen, then back to the left. It feels broken, and for someone using magnification who sees only part of the screen it is genuinely disorienting.

Treat visual reordering as a warning sign. If the visual order matters, change the HTML so the source matches it. Use CSS reordering only for things that don't change meaning, like moving an image to the other side of its text. The CSS reading-flow property, which lets Tab follow the visual order of a flex or grid container, is arriving in browsers, and it is too new to rely on yet.

.actions { display: flex; }
.actions .book { order: -1; } /* looks first, tabs last */

When should you use tabindex="-1"?

Use it when script needs to move focus somewhere that isn't normally focusable. After a single page app loads new content, focus can move to the new page's <h1>. After a form fails, focus can move to an error summary. The target of a skip link is often given tabindex="-1" for the same reason, which the Skip links lesson covers.

Never put tabindex="-1" on a link or button people need to reach. It removes it from the Tab order while leaving it clickable, so mouse users never notice the problem. The Break It page has exactly this bug. A related mistake is hiding a menu with opacity: 0 or moving it off screen while its links stay focusable, which leaves invisible Tab stops. Use the hidden attribute or display: none to take hidden content out of the Tab order.

How do you check the Tab order?

Press Tab through the page in a real browser and compare the path to the way you would read it. It should move top to bottom and left to right in English, through the menu, then the content, then the footer. Watch for jumps, for stops you can't see, and for stops on things you can't act on.

The **Keyboard** view lists every stop in order with its role and name, and flags any positive tabindex. Show numbers puts each number on the page, which makes a backwards jump obvious at a glance. The engine follows the browser's rules for tabindex; it cannot tell when CSS has moved things visually, so check flex and grid layouts by eye in the Page view.

Field notes

  • By default, Tab follows the order of the HTML.
  • A positive tabindex jumps an element ahead of everything else.
  • tabindex="0" adds a stop, so use it only on things people can act on.
  • tabindex="-1" lets script move focus without adding a Tab stop.

Easy to get wrong

A positive tabindex sends Tab to that element before everything else on the page. Order the HTML the way you want people to move through it.

Text that can't be acted on doesn't need focus. Screen reader users read it in browse mode, and sighted keyboard users can see it.

A value of -1 removes the link from the Tab order. Mouse users can still click it, so the bug hides until a keyboard user tries.

CSS order changes where something appears, while Tab and screen readers follow the source. The two orders drift apart.

Questions people ask

Is tabindex="1" ever acceptable?

In practice, no. Every case it seems to solve is better solved by ordering the HTML, or by moving focus with script and tabindex="-1". Checkers flag every positive value because the side effects reach the whole page.

Does a screen reader follow the Tab order?

In browse mode, a screen reader reads in source order using its own keys, and Tab moves only between focusable elements. Both follow the HTML, so source order affects both.

What about radio buttons, which Tab seems to skip?

A group of radio buttons with the same name is one Tab stop. Tab lands on the checked option, or the first if none is checked (Shift+Tab lands on the last), and the arrow keys move within the group. The Keyboard view treats a group with a checked option as one stop. With nothing checked it lists every radio, so count that case by hand in a real browser.

Do disabled buttons appear in the Tab order?

A <button disabled> is skipped by Tab and can't be activated. If people need to discover the button and learn why it is unavailable, aria-disabled="true" keeps it focusable; the Buttons and links lesson covers the trade-off.

Keep going