Semantic HTML and keyboard access
Why the right element is most of accessibility, how tab order really works, and the fixes that come free with native controls.
Use the element that already does the job
A native element arrives with behaviour, keyboard support, focus handling and a role that assistive technology understands. A div with a click handler has none of that, and every fix you add afterwards is a partial reimplementation of what the browser already had.
<!-- does not focus, does not respond to Enter or Space, no role -->
<div class="btn" onclick="save()">Save</div>
<!-- focusable, keyboard-operable, announced as a button, disabled state is free -->
<button type="button" onclick="save()">Save</button>
<!-- a link navigates; a button acts. Do not swap them -->
<a href="/orders">View orders</a>
<!-- placeholder is not a label -->
<input type="email" placeholder="Email address">
<!-- explicit label, associated by id -->
<label for="email">Email address</label>
<input id="email" name="email" type="email" autocomplete="email" required>| Instead of | Use | You get free |
|---|---|---|
div + click | button | Focus, Enter/Space, role, disabled |
div + onclick | a href | Right-click, middle-click, status bar, screen-reader link list |
div wrapper | nav, main, header, footer | Landmarks for quick navigation |
div + role=list | ul / li | Item count announced, correct semantics |
| label styled as text | label for or wrapping label | Click-to-focus, correct announcement |
Keyboard access and tab order
The default tab order follows the document order. Adding a positive tabindex removes an element from that flow and reorders everything, which is why it is almost always a mistake - if the order feels wrong, move the markup instead.
<!-- avoid: positive tabindex reorders the whole page -->
<input tabindex="1">
<!-- include something that is not focusable by default -->
<div tabindex="0" role="region" aria-label="Terminal output">...</div>
<!-- keep it in the tree, but not in the tab order -->
<button tabindex="-1">Close</button>
<!-- focus target for skip links needs tabindex="-1" to accept focus -->
<h2 id="section-2" tabindex="-1">Section 2</h2>- Press Tab through the page and confirm the focus indicator is always visible and never clipped by overflow.
- Everything reachable by mouse must be reachable by keyboard: menus, carousels, date pickers, custom selects.
- Escape must close dialogs and menus; arrow keys move within a group, Tab moves between groups.
- Do not disable the outline with
outline: noneunless you replace it with something clearly visible. - Touch targets should be at least 24 by 24 CSS pixels, with 44 by 44 as a comfortable target.
/* a focus ring that survives on both light and dark backgrounds */
:focus-visible {
outline: 3px solid #1a5fff;
outline-offset: 2px;
border-radius: 2px;
}
/* never remove the indicator without a replacement */
/* button:focus { outline: none; } <- do not do this */Structure that assistive tech can navigate
A screen reader user often jumps by heading, landmark or link list rather than reading linearly. Consistent headings and landmarks turn a 2000-word page into something navigable in seconds.
- One
h1describing the page, then a logicalh2/h3outline with no skipped levels. - Wrap the page in
header,nav,main,footer; label multiple navs witharia-label. - Give tables a
captionand usethwithscopefor headers. - Wrap a group of related inputs in
fieldsetwith alegend. - Keep the page
langcorrect, and mark inline language changes.
FAQ
Can I use a div if I add role and keydown handling?
What is the difference between focus and focus-visible?
:focus matches whenever an element has focus, including mouse clicks. :focus-visible matches when the browser judges the focus should be shown, which is almost always for keyboard navigation - that is the one to style.Related
WCAG in plain language Focus management, ARIA and testing
Last refreshed 2026-09-18.