Modern HTML5 in Production · Semantics and Accessibility
Buttons, Links and Keyboard Access
Why keyboard users couldn't add items to the basket, and how choosing button vs link, keeping focus visible and avoiding clickable divs fixes it.
Scenario
Example app: a design-system "Card" makes the whole product tile clickable with <div onclick>, and the "Add to basket" control is a styled <span>. To match the design, the global CSS removes focus outlines with *:focus { outline: none; }.
What you see in production
- Keyboard users (including many people with motor impairments and power users) can't reach the add-to-basket control with Tab.
- Users who can reach other controls can't see where focus is.
- Screen readers announce "clickable" or nothing at all, instead of "button".
The naive code
<style>*:focus { outline: none; }</style>
<div class="card" onclick="openProduct(42)">
<h3>Ceramic Mug</h3>
<span class="btn" onclick="addToBasket(42)">Add to basket</span>
</div>
Reproduce it
Open the preview and use only the keyboard: Tab to move, Enter or Space to activate. The first row uses the naive markup; the second uses a link and a button.
<style>
.naive *:focus { outline: none; }
.card { border: 1px solid #ccc; border-radius: 8px; padding: 8px 12px; margin: 8px 0; }
.btn { display: inline-block; padding: 4px 10px; background: #4f46e5; color: white; border-radius: 6px; cursor: pointer; border: 0; font: inherit; }
.fixed :focus-visible { outline: 3px solid #f59e0b; outline-offset: 2px; }
</style>
<p id="log">Last action: none</p>
<div class="naive">
<div class="card" onclick="log('open product (div)')">
<strong>Naive card</strong>
<span class="btn" onclick="event.stopPropagation(); log('added (span)')">Add to basket</span>
</div>
</div>
<div class="fixed">
<div class="card">
<a href="#product-42" onclick="log('open product (link)')"><strong>Fixed card</strong></a>
<button class="btn" type="button" onclick="log('added (button)')">Add to basket</button>
</div>
</div>
<script>function log(msg) { document.getElementById("log").textContent = "Last action: " + msg; }</script>
Root cause
Only certain elements are focusable and keyboard-operable by default: links with href, buttons, form controls and elements with tabindex. A <div> or <span> with a click handler is invisible to the keyboard and has no role. Removing the focus outline without a replacement hides where focus is, which fails WCAG's focus-visible criterion.
The fix
- Links navigate (to another page or place):
<a href>. Buttons act (add, open, submit, toggle):<button type="button">. See the APG button pattern. - Never remove focus styles without a replacement; use
:focus-visibleto show a clear outline for keyboard users only. - For whole-card links, put a real link on the title and extend its click area with CSS (a
::afteroverlay) instead ofdiv onclick. - Keep focus order logical: follow the DOM order and avoid positive
tabindexvalues.
Trade-offs
- Native buttons come with default styles to reset, which is a small CSS cost for a lot of free behaviour.
role="button" tabindex="0"plus key handlers can mimic a button, but it's more code and easy to get wrong.
Detect it in production
- Do a keyboard-only pass on every key flow (sign-up, checkout) as part of QA.
- Lint for
onclickon non-interactive elements (for examplejsx-a11y/click-events-have-key-events,no-static-element-interactions).
Key points
- Links navigate, buttons act; clickable divs and spans aren't keyboard-accessible.
- Never hide focus; use
:focus-visible. - Test every key flow with the keyboard alone.
Practice questions
When should you use a link and when a button?
Answer
A link (a with href) navigates to another page or location; a button performs an action on the current page (submit, open, add, toggle). Using the right one gives correct keyboard behaviour and announcements.
Why is `div onclick` an accessibility problem?
Answer
A div isn't focusable, can't be activated with Enter or Space, and has no role, so keyboard and screen-reader users can't use it. Use a button or link instead.
How should focus styles be handled?
Answer
Never remove them without a visible replacement. Use :focus-visible to show a clear outline when users navigate with the keyboard, which satisfies WCAG's focus-visible requirement without showing rings on mouse clicks.
Open in the app to mark lessons complete, take notes and track your progress.