Topic 03

Buttons, Keyboard and Focus

AccessibilityJavaScript

Many people never touch a mouse. Some use only a keyboard. Some use a switch, a single large button that steps through the page. Some use a screen reader, which reads the page aloud and on a computer is usually driven from the keyboard. A real button element works for all of them without a line of extra code. A div with a click listener, styled to look like a button, works for almost none of them, and this topic shows exactly why.

Picture two doors on a wall: one real, with a handle, and one painted on so well that you cannot tell them apart from across the room. Both look like doors. Only one opens, and only one opens for everyone who tries it. A div styled as a button is the painted door: it looks the part and does nothing for the people who cannot use a mouse.

What Does a button Give You?

To see the difference, put one of each on a practice page: add both to the main of index.html, with a listener for each at the end of js/main.js. Remove them when you finish.

index.html
<div class="fake-button">Save</div>
<button type="button" class="real-button">Save</button>
js/main.js
document.querySelector(".fake-button").addEventListener("click", () => {
  console.log("div clicked");
});

document.querySelector(".real-button").addEventListener("click", () => {
  console.log("button clicked");
});

With a mouse, both work: clicking either one logs its message. Now put the mouse aside and press Tab a few times. Focus, the outline that shows where keyboard input will go, moves from link to link and lands on the real button, but it never stops on the div. With the button focused, press Enter or Space, and the Console shows button clicked. There is no way to reach the div from the keyboard at all.

Run this test in Chrome. Safari on a Mac skips links and buttons when you press Tab, until you switch on the setting to press Tab to highlight each item, in Safari's Advanced settings. That is Safari's default, not a fault in your page.

The browser gives a button four things for free. It is focusable, so Tab reaches it. It is activated by Enter and Space, which fire the same click event a mouse does, so your one listener serves both. It is announced as a button by screen readers, which tell the listener that this thing can be pressed. And the disabled attribute switches it off in one word: no focus, no clicks, and screen readers say it is unavailable. A div has none of these. Rebuilding them by hand takes several attributes and extra listeners, and is easy to get subtly wrong; Chapter 12 shows what that involves and why it is a last resort.

Who can use it?
ControlMouseTab keyEnter or SpaceScreen reader
div with a click listeneryesno, never focusednoread as plain text
buttonyesyesyesannounced as a button

Links or Buttons?

The other common mix-up is between a link and a button. The rule is about what happens, not how it looks. A link goes somewhere: it changes the address, works with the Back button, and can be opened in a new tab. A button does something on the current page: opens a panel, filters a list, sends a form. CSS can make either look like the other, so choose the element by its job, then style it.

Buttons carry one trap of their own. A button element's default type is submit, so inside a form, a button with no type attribute sends the form when clicked. The page reloads or moves to another address, and whatever your listener did is lost. That is why every button in this book says type="button" unless it is meant to send a form, which Chapter 10 does on purpose.

Fragment
<form>
  <input type="text" name="search">
  <button>Clear</button>               <!-- sends the form -->
  <button type="button">Clear</button> <!-- only runs your listener -->
</form>

In What Order Does Focus Move?

Tab moves focus forward and Shift+Tab moves it back, through the interactive elements, such as links, buttons and form fields, in the order they appear in the HTML. So the order of your HTML matters even when CSS rearranges things on the screen. Flexbox and grid can place the third item first visually, but Tab still visits it third, and a keyboard user watches the focus jump around the page. When the visual order and the reading order disagree, fix the HTML order rather than hiding the problem with CSS.

The tabindex attribute changes focus behaviour. With the value zero, it adds an element that is not normally focusable into the Tab order at its place in the HTML, which custom controls occasionally need. Positive values move an element ahead of everything else on the page, and almost always create a confusing order, so avoid them.

A Panel That Opens, Closes, and Answers Escape

Here is a small menu panel built the accessible way. The button says whether the panel is open with aria-expanded, from the last chapter; the panel uses the hidden attribute, which hides it without any CSS. Two functions keep the two in step, so they can never disagree. Like the buttons above, this is practice to remove afterwards.

index.html
<button type="button" class="menu-toggle" aria-expanded="false">Menu</button>
<div class="menu-panel" hidden>
  <a href="projects.html">Projects</a>
  <a href="contact.html">Contact</a>
</div>
js/main.js
const menuToggle = document.querySelector(".menu-toggle");
const menuPanel = document.querySelector(".menu-panel");

function openMenu() {
  menuPanel.hidden = false;
  menuToggle.setAttribute("aria-expanded", "true");
}

function closeMenu() {
  menuPanel.hidden = true;
  menuToggle.setAttribute("aria-expanded", "false");
}

menuToggle.addEventListener("click", () => {
  if (menuPanel.hidden) {
    openMenu();
  } else {
    closeMenu();
  }
});

Click Menu, or Tab to it and press Enter, and the two links appear; a screen reader now announces the button as expanded. Press it again and the links disappear and the button is announced as collapsed. One caution about hidden: a CSS rule that gives the panel a display value of its own overrides it, and the panel stays visible. If hidden seems to do nothing, look for such a rule in the Styles pane.

Keyboard users expect Escape to close whatever just opened. The keydown event and its key property make that one listener. It goes on the whole document, so it hears Escape wherever focus is, thanks to bubbling.

js/main.js
document.addEventListener("keydown", (event) => {
  if (event.key === "Escape" && !menuPanel.hidden) {
    closeMenu();
    menuToggle.focus();
  }
});

Open the menu, Tab into its links, and press Escape: the panel closes and focus returns to the Menu button. That last line matters. Without it, focus would vanish along with the hidden link: nothing on the page would show an outline, and a keyboard user would not know where the next Tab will go.

Can You See Where Focus Is?

All of this depends on seeing the focus. Chapter 3 introduced :focus-visible, which matches an element focused from the keyboard. Now test it properly: open each of the three pages, put the mouse away, and Tab through every link and button. Every one should show a clear outline. If one does not, a rule somewhere has removed its outline; find it in the Styles pane and restyle it rather than deleting it.

css/style.css
a:focus-visible,
button:focus-visible {
  outline: 3px solid var(--color-accent);
  outline-offset: 2px;
}

This rule draws a three-pixel outline in the site's accent colour, with a small gap around each focused link or button. Add it to the stylesheet. If you kept a focus rule from Chapter 3, replace it with this one, so the outline follows the site's own colour tokens.

Common Confusions
  • "Styling a div as a button is fine." It looks like one and works only with a mouse. Keyboard and screen-reader users cannot reach it or tell what it is.
  • "Buttons look wrong, so I avoid them." Every part of a button's look can be restyled in CSS. Start from a real button and change how it looks, never the other way round.
  • "Keyboard users are rare." They include many screen-reader users, people with motor disabilities who cannot hold a mouse steady, and power users who move faster without one.
Why It Matters
  • The site works for keyboard users by construction, not by later repair.
  • Choosing button or a by what happens prevents most interactive accessibility bugs before they exist.

Knowledge Check

Which of these does a real button provide that a clickable div does not?

  • It responds when clicked with a mouse
  • It can show a word such as Save inside it
  • It works with Tab, Enter and Space
  • It can be styled to look like a link

A control opens a panel on the current page. Which element fits?

  • A link, since it is the most clickable element
  • A button, since it acts on the current page
  • A div, since it can be styled any way you like
  • Whichever element looks better on the page

In what order does the Tab key move through a page's links and buttons?

  • The order they appear on the screen
  • The largest controls first, then smaller ones
  • Alphabetical order of each control's text
  • The order they appear in the HTML file

A button with no type attribute sits inside a form. What happens when it is clicked?

  • It sends the form, as a submit button
  • It runs only its click listener
  • It is disabled until given a type
  • It shows a warning in the Console

You got correct