Operable · Level A · 2.4.3
Focus Order
When a page can be navigated by pressing Tab in sequence, that sequence has to stay logical enough to preserve meaning and operability. It doesn't have to match the visual layout exactly — but it can't be arbitrary either.
- Low vision
- Cognitive
- Motor
How to recognize it
Tab through the page from the top and watch where focus lands each time, without touching the mouse. Three patterns fail this: focus jumping from a form's first field to an unrelated part of the page before coming back to the second field; a modal dialog that opens without focus moving into it, so Tab keeps walking through the page hidden behind it; and dynamically injected content — an autocomplete list, an expanded accordion panel — that's placed at the end of the DOM even though it visually appears in the middle of the page, so focus order visually teleports. None of this requires the tab order to match left-to-right, top-to-bottom reading order exactly — the actual bar is lower than that: does the sequence stay logical enough that you're not left disoriented about what you just interacted with?
How to fix it
The most common cause is DOM order silently drifting away from visual order — CSS (flexbox/grid order, absolute positioning) can make something appear first without it being first in the markup, and Tab always follows DOM order, not the visual one. Fix the DOM order itself rather than patching the visual result with CSS. For dialogs and other content that appears dynamically, move focus into it programmatically the moment it becomes visible, and return focus to whatever triggered it when it closes. A modal also needs role="dialog" (or role="alertdialog") and aria-modal="true" on its container — that's a 4.1.2 requirement, not a 2.4.3 one, but the two go hand in hand in practice: moving focus into a container that has no role tells a screen reader user where they landed but not that they're now inside a dialog. Test this the same way a keyboard user would: by actually tabbing through, not by reading the code.
Example
Avoid
<form>
<input name="email" style="order: 2">
<input name="name" style="order: 1">
</form><div id="modal" hidden>
<h2>Confirm delete</h2>
</div>
function openModal() {
modal.hidden = false;
// no role, and focus never moves — Tab keeps walking through the page behind it
}<div id="page-content">...</div>
<div id="accordion-panel-2" hidden>Revealed in the middle of the page, but rendered at the very end of the DOM</div>Prefer
<form>
<input name="name">
<input name="email">
</form><div id="modal" role="dialog" aria-modal="true" aria-labelledby="modal-title" hidden>
<h2 id="modal-title">Confirm delete</h2>
</div>
function openModal() {
modal.hidden = false;
modal.querySelector('h2').focus();
}<div id="page-content">
...
<div id="accordion-panel-2" hidden>Rendered right where it visually appears, in DOM order</div>
</div>