Managing focus
Focus is the spot on the page that receives your keystrokes right now. It is the keyboard's version of the mouse pointer. If you hide it, jump it around, or lock it in a box with no door, the page turns into a maze.
In one minute
- Always show where focus is. Never write
outline: nonewithout a replacement (WCAG 2.4.7, and 2.4.11 in WCAG 2.2). - Tab order follows the source order. Fix the HTML order, not the tab order.
- When a dialog opens, move focus into it. When it closes, send focus back to the button that opened it.
- Trap focus only inside a modal, and always give an exit (Escape and a Close button).
tabindex="0"adds something to the tab order,-1makes it focusable by script only. Never use 1 or higher.
Picture a spotlight on a stage. Only the actor in the light gets to speak. Pressing Tab moves the light to the next actor. If the light is invisible, nobody knows who is on. If it jumps to the back row for no reason, the audience is lost. If you close a door and the light was in that room, it must come back to where it started.
1. Focus must be visible
Who uses the keyboard? People who cannot use a mouse (motor disability, tremor, repetitive strain), blind screen reader users, and power users who simply prefer it. For all of them the focus indicator (the ring around the active control) is the only way to know where they are. A ring is visible when it contrasts at least 3 : 1 with its surroundings (WCAG 1.4.11). WCAG 2.2 also says the focused control must not be hidden behind a sticky header or cookie banner (2.4.11).
Lab: with and without the ring
Step 1. Choose a version, click in the box, then press Tab through the buttons.
Focus is on: nothing yet (only this line tells you; a keyboard user has no such line)
Why :focus-visible and not :focus? It shows the ring for keyboard users, and usually hides it for mouse clicks on buttons, so designers have no excuse to remove it.
A good ring is 2 px or thicker, solid, and offset a little from the control.
2. Tab order follows the source order
By default Tab visits links, buttons, and form fields in the order they appear in the HTML, not the order they appear on screen.
CSS can move things around on screen (flex-direction: row-reverse, order, grid placement) without moving them in the HTML.
Then sighted keyboard users watch the focus ring jump left, right, left. This fails WCAG 2.4.3 (Focus Order) and 1.3.2 (Meaningful Sequence).
Lab: who is first?
Step 2. The HTML order is always A, B, C. Pick a layout, then press Tab through the three buttons and read the log.
Nothing yet.
tabindex="1" or higher jumps those items ahead of everything else on the page, even the skip link and the menu.
It is fragile: add one new button and the whole order is wrong. Change the HTML order instead.
3. Move focus when the context changes, and give it back
When a dialog opens, a screen reader user hears nothing unless focus moves there, because the new content may be far from where they were. When it closes, focus must return to the button that opened it. If not, focus falls back to the top of the page and they must start over. Also: do not move focus on hover, on typing, or on a timer. Move it only when the user asked for a new place.
Lab: open a note
Step 3. Pick a version. Open the note with the keyboard (Enter), then close it with Escape or Close, and watch where focus lands.
Field note
Focus moved here. Escape or Close returns it.
Focus is on: nothing yet
The native <dialog> element with showModal() does most of this for you: it moves focus in, blocks the page behind, closes on Escape, and returns focus to the opener.
Prefer it to hand-made dialogs. After inserting new content such as a form error summary, you can also move focus to the heading with tabindex="-1" and .focus().
4. Contain focus in a modal, with a way out
A modal blocks the rest of the page. While it is open, the page behind must not be reachable, or a keyboard user tabs "behind" the modal and loses it. That is called containing focus. Done wrong it becomes a keyboard trap (WCAG 2.1.2): focus goes in and cannot come out. So: contain only while the modal is open, and always offer Escape plus a visible Close button.
Lab: leaky versus contained
Step 4. Pick a version, open the modal, and press Tab several times. Press Escape to leave.
Subscribe
Focus is on: nothing yet
Modern code makes the page behind inert. The inert attribute removes it from the tab order and from the accessibility tree at once.
The lab uses it. Without it, you must write the wrap-around key handling yourself, and every screen reader browse mode can still wander off.
5. Skip links and tabindex
A skip link is the first focusable control on the page. It jumps past the header and menu to the main content, so keyboard users do not press Tab forty times on every page (WCAG 2.4.1).
This course page has one: press Tab once after the page loads. The target (<main id="main" tabindex="-1">) needs tabindex="-1" so some browsers can actually move focus there.
Lab: what does tabindex do?
Step 5. Choose a value for the box below. Press Tab to see if it is reached. Press the "Call .focus()" button to see if a script can reach it.
Use tabindex="0" rarely, only when you build a custom widget that must be reached (and then also give it a role and key handling; see the ARIA lessons).
Use tabindex="-1" for things you move focus to by code: headings, error summaries, dialogs.
Common mistakes
| You see | Why it is a problem | Fix |
|---|---|---|
*:focus { outline: none } | Keyboard users cannot see where they are. | Style :focus-visible clearly. |
| CSS reorders items on screen | Focus jumps around the page. | Reorder the HTML. |
tabindex="5" | Breaks the natural order, fragile. | Only 0 or -1. |
| Dialog opens, focus stays on the button | Screen reader users do not know it opened. | Move focus into it. |
| Dialog closes, focus is lost | User restarts from the top of the page. | Return focus to the opener. |
| Modal with no Escape or Close | A keyboard trap (2.1.2). | Always offer both. |
| Sticky header covers the focused link | Focus is hidden (2.4.11). | scroll-padding-top. |
- Open the note in lab 3 with the keyboard, then close it. Focus should land back on "Open note".
- On any website you use, press Tab repeatedly. Can you always see the ring? Does the order make sense?
- Press Shift+Tab to go backwards through lab 4's modal.
Remember
- Visible focus is not optional. Style
:focus-visible, do not remove it. - Source order is tab order. Do not use positive
tabindex. - Opening moves focus in; closing returns focus to the opener.
- Contain focus only in a modal, with Escape and Close as exits. Prefer
<dialog>andinert. - Skip link first;
tabindex="-1"for script targets.