HTML and CSS course Β· Module 10: Accessibility (a11y)
Keyboard Navigation
In this lesson8
Put your mouse aside for a moment and try to buy a museum ticket using only the Tab key. If after a few presses you do not know where you are, or the "Buy" button does not react at all, you have just felt what the web is like for many people. In Ancient Egypt, the roads and corridors of temples were precisely planned so that every pilgrim could reach their destination. Similarly, websites must provide clear navigation paths for users who do not use a mouse - they navigate exclusively using the keyboard.
Why Is Keyboard Navigation Important?
Many people use websites only with a keyboard:
- People with limited motor skills
- Screen reader users
- Advanced users (programmers, power users)
- People with temporary injuries (e.g., a broken arm)
What is more, many assistive technologies, such as voice control or switches, work by simulating key presses. A page that works well from the keyboard therefore works with them too.
Basic Navigation Keys
| Key | Function |
|---|---|
Tab | Move to the next interactive element |
Shift + Tab | Return to the previous element |
Enter | Activate a link or button |
Space | Activate a button, check a checkbox |
Arrow keys | Navigate in menus, radio buttons, tablist |
Escape | Close a dialog, cancel |
Tab visits elements in the order in which they appear in the HTML code, not in the order in which you see them on the screen. That is why, on a typical page, focus lands in turn on the skip link, the navigation links, the interactive elements in the main content and finally the links in the footer.
Focus and tabindex
Natural Focus
Focus is like a torch in a dark corridor - it shows which element you are standing at right now. Some HTML elements are naturally focusable, which means you can Tab to them without any attribute:
1<!-- These elements are naturally focusable -->
2<a href="/link">Link</a>
3<button>Button</button>
4<input type="text">
5<select>...</select>
6<textarea>...</textarea>
7
8<!-- These elements are NOT naturally focusable -->
9<div>Cannot Tab to this</div>
10<span>This neither</span>
11<p>And not this</p>Links with an href attribute and buttons also react to keys straight away: a link to Enter, and a button to both Enter and Space. Plain text in a <div>, <span> or <p> is not interactive, so Tab skips it - and that is how it should be, because nobody wants to stop at every paragraph.
The tabindex Attribute
The tabindex attribute controls whether and when an element receives focus. It has three kinds of values - two useful ones and one you should avoid:
1<!-- tabindex="0" - element becomes focusable in natural order -->
2<div tabindex="0" role="button" onclick="handleClick()">
3 Click me
4</div>
5
6<!-- tabindex="-1" - focusable programmatically, but NOT via Tab -->
7<div tabindex="-1" id="error-message">
8 An error occurred!
9</div>
10
11<!-- tabindex="1+" - AVOID! Changes natural order -->
12<!-- This is an antipattern and causes navigation chaos -->
13<input tabindex="3" placeholder="Third">
14<input tabindex="1" placeholder="First">
15<input tabindex="2" placeholder="Second">tabindex="0" puts an element into the order according to its place in the code. tabindex="-1" does not add it to the Tab order, but it lets a script move focus to it, for example to an error message. Positive values push elements ahead of the rest of the page, so the order no longer matches what you see.
Rule: Use only tabindex="0" and tabindex="-1". Never use positive values!
Notice also that a <div> with role="button" gets focus but still does not react to Enter or Space - the key handling has to be written in JavaScript. That is why I recommend a native <button>, which does it for free.
Skip Links
Imagine a pilgrim who, every time they enter the temple, has to pass the same columns before reaching the sanctuary. Skip links allow keyboard users to skip repetitive navigation and go directly to the main content:
1<!-- Skip link - first element on the page -->
2<a href="#main-content" class="skip-link">
3 Skip to main content
4</a>
5
6<header>
7 <nav>
8 <!-- Long navigation that the user can skip -->
9 <a href="/">Home</a>
10 <a href="/exhibitions">Exhibitions</a>
11 <a href="/tickets">Tickets</a>
12 <a href="/contact">Contact</a>
13 </nav>
14</header>
15
16<main id="main-content">
17 <h1>Welcome to the Egyptian Museum</h1>
18 <!-- ... -->
19</main>The link works thanks to an anchor: href="#main-content" points to the element with id="main-content". The navigation itself does not change - the skip link only lets you bypass it. Now it has to be hidden from mouse users and shown when it receives focus:
1/* Skip link visually hidden, but visible on focus */
2.skip-link {
3 position: absolute;
4 top: -40px;
5 left: 0;
6 background: #000;
7 color: #fff;
8 padding: 8px 16px;
9 z-index: 100;
10 transition: top 0.3s;
11}
12
13.skip-link:focus {
14 top: 0;
15}The link waits above the edge of the page, and :focus brings it back into view after the first press of Tab. The exact value (-40px or -50px) does not matter, as long as the link hides completely. Do not use display: none, because an element hidden that way cannot receive focus.
Visible Focus
The outline is the only signal of where the torch is burning. Never remove the outline from focusable elements - compare two approaches:
1/* BAD - hiding focus */
2*:focus {
3 outline: none; /* DO NOT DO THIS! */
4}
5
6/* GOOD - custom but visible focus */
7a:focus-visible,
8button:focus-visible {
9 outline: 3px solid #d4af37;
10 outline-offset: 2px;
11 border-radius: 2px;
12}The :focus-visible pseudo-class is better than :focus because it shows the outline during keyboard navigation, not when you click a button with the mouse (text fields always show it, because you need to know where you are typing). The good version hides nothing - it only changes the outline's color and moves it 2px away.
Focus Trap in Dialogs
When you open a modal, focus must be "trapped" inside it - otherwise a keyboard user will wander around the page hidden beneath the window. HTML has a ready-made element for this:
1<dialog id="ticket-dialog" aria-labelledby="dialog-title">
2 <h2 id="dialog-title">Buy a Ticket</h2>
3 <form>
4 <label for="quantity">Number of tickets:</label>
5 <input type="number" id="quantity" min="1" max="10" value="1">
6 <button type="submit">Buy</button>
7 <button type="button" onclick="closeDialog()">Cancel</button>
8 </form>
9</dialog>A <dialog> element opened with the showModal() method manages focus automatically: it moves focus into the window and makes the rest of the page inert, so Tab cannot escape behind the window. Escape closes such a dialog, and focus returns to the element that was active before it opened.
Accessible Navigation
Finally, a navigation with additional ARIA roles. Take a look at the role and aria-current attributes:
1<nav aria-label="Main navigation">
2 <ul role="menubar">
3 <li role="none">
4 <a href="/" role="menuitem">Home</a>
5 </li>
6 <li role="none">
7 <a href="/exhibitions" role="menuitem"
8 aria-current="page">Exhibitions</a>
9 </li>
10 <li role="none">
11 <a href="/tickets" role="menuitem">Tickets</a>
12 </li>
13 </ul>
14</nav>The aria-current="page" attribute informs the screen reader which page the user is currently on. A word of warning: the menubar and menuitem roles promise an application menu operated with the arrow keys, which you would have to program yourself. For ordinary site navigation, a <nav> with a list of links is enough - and that is what I recommend.
Keyboard navigation is like clearly marked corridors in a pyramid - without them, even the most beautifully decorated chamber remains inaccessible to many visitors. In the next lesson you will use this knowledge in forms, where Tab leads you from field to field.
Practice
Put the mouse aside and go through the page in the editor below using only Tab. Check that the skip link appears first and that you can always see where the focus is:
Code for this lesson: index.html
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>Keyboard navigation</title>
7 <link rel="stylesheet" href="style.css">
8</head>
9<body>
10 <!-- Skip link -->
11 <a href="#main-content" class="skip-link">
12 Skip to main content
13 </a>
14
15 <header>
16 <h1>Egyptian Museum</h1>
17 <nav aria-label="Main navigation">
18 <ul>
19 <li><a href="#exhibitions" aria-current="page">Exhibitions</a></li>
20 <li><a href="#tickets">Tickets</a></li>
21 <li><a href="#contact">Contact</a></li>
22 </ul>
23 </nav>
24 </header>
25
26 <main id="main-content">
27 <h2>Test keyboard navigation</h2>
28 <p>Use the <kbd>Tab</kbd> key to move between elements.
29 Try <kbd>Shift+Tab</kbd> to go back.</p>
30
31 <section id="exhibitions">
32 <h3>Interactive elements</h3>
33
34 <a href="#link1">Link to exhibition 1</a>
35 <a href="#link2">Link to exhibition 2</a>
36
37 <button onclick="alert('Clicked!')">
38 Click me (Enter or Space)
39 </button>
40
41 <div class="custom-btn" tabindex="0" role="button"
42 onclick="alert('Div-button!')"
43 onkeydown="if(event.key==='Enter'||event.key===' '){event.preventDefault();alert('Div-button!')}">
44 A div with tabindex="0" (also focusable)
45 </div>
46 </section>
47
48 <section id="tickets">
49 <h3>A form with keyboard navigation</h3>
50 <form>
51 <label for="fname">First name:</label>
52 <input type="text" id="fname" name="fname">
53
54 <label for="lname">Last name:</label>
55 <input type="text" id="lname" name="lname">
56
57 <label for="ticket-type">Ticket type:</label>
58 <select id="ticket-type" name="ticket">
59 <option value="normal">Standard</option>
60 <option value="reduced">Reduced</option>
61 <option value="family">Family</option>
62 </select>
63
64 <button type="submit">Buy a ticket</button>
65 </form>
66 </section>
67 </main>
68
69 <footer>
70 <p>Try to get here with Tab - how many times do you have to press it?</p>
71 <p>Now go back to the top and use the skip link!</p>
72 </footer>
73</body>
74</html>Remember: Tab is a pilgrim walking the corridors of your pyramid - the skip link shortens their way, and visible focus is the torch that shows where they stand.
Spotted a mistake in this lesson?
Check yourself
Answer the questions from this lesson. Pick an answer to see right away whether it is correct.
1. Which tabindex attribute values are recommended for use?
Hands-on tasks in the game
- Click in order
Arrange a correct skip link in HTML:
- Code editor
Add a skip link at the beginning of the page, id="main-content" to main, and CSS styles for .skip-link (hidden, visible on focus). Also add focus-visible for a, button, and input.
- Vertical ordering
Arrange the typical Tab navigation order on a web page: