Use Semantic HTML

Improving Keyboard Accessibility for Web Apps — Part 2

Feranmi Akinlade
Published in
6 min readJun 30, 2020


Overview: This is the second article in a five-part series on optimizing keyboard accessibility for web apps. The series is based on learnings from our recent work on improving keyboard accessibility for our web app at BuyCoins.
This article focuses on the use of appropriate HTML tags to take advantage of native browser support for keyboard navigation. The
preceding article discusses why keyboard accessibility is important and how it normally works in the browser.

In the previous article, we looked at how keyboard navigation normally works in browsers. To recap, the user points to elements on a page sequentially, usually with the tab key. The element currently being pointed to is said to have focus and any keys pressed will trigger an event on that element, calling any associated event handlers. It’s up to the browser to decide what element receives focus next when a user hits the tab key. Let’s take a closer look at how the browser makes this decision.

The need for semantic meaning

No doubt it would be impractical to have to step through each span, div, and section element to get to a specific button because these elements aren’t interactive by nature. Stepping through elements that can’t be interacted with is pointless, so browsers provide a better experience by jumping between interactive elements — i.e anchor links, buttons, input fields, checkboxes, etc.

Different HTML tags exist for specific purposes, and this is what semantic HTML or semantic markup is all about — using the right HTML tag in all parts of your web page based on their specified purpose. If you’re not familiar with the various semantic HTML tags, this article on is a great place to start.

Perhaps the most important semantic elements, at least where keyboard navigation is concerned, are the button and anchor link elements. These are the primary elements that provide interactive functionality on the web, in addition to other form controls. Browsers recognize these elements and understand that they’re supposed to be interactive, and as such certain behaviours are associated with them out of the box.

One such behaviour is that they receive keyboard focus by default. So a user tabbing through a web app will land on a button element naturally, but if you created that button with something like <div role=”button”>, it will, by default, not receive focus when tabbing.

<!-- Good ✅ -->
<button onclick="doSomething()">
Click Me!
<!-- Bad ❌ -->
<div role=”button” onclick="doSomething()">
Click Me!

Reinventing the wheel

Now it is possible to add this “focusable” behaviour to a div element using the tabindex attribute, but even then it will not be clickable with the keyboard. The click event will fire when it’s clicked with a mouse, but not when the enter key is pressed. You then need to add a key press listener for the enter key in addition to the click listener. But again, the spacebar key can also be used to activate native button elements. So to fully replicate the button behaviour, you have to listen for the spacebar key as well.

Do you really need to reinvent the wheel in this way? The native button element gives you all of this out of the box because it has implicit semantic meaning understood by the browser and assistive technologies. You also get an SEO boost since web crawlers can also better make sense of your page. All of this free of charge. It’s a no-brainer really. Unsemantic HTML is costly, and more often than not, it makes for a bad experience for a good number of users.

If you would like to see more on the impracticality of retro-fitting generic elements for interactivity versus using native interactive elements, check out this great article on Smashing Mag. Also this MDN article includes examples.

But, it doesn’t look cool! 😭

If you need to make an interactive element look a certain way, you don’t have to break semantics to achieve this. CSS is powerful enough to make any element appear the way you want it to. In less than 5 lines of CSS you can override any default styling a button has and go on to style it the way you want. The following CSS should be a sufficient override for most browser’s UA styles for buttons:

button {
border: none;
background-color: transparent;
font-size: inherit;

Don’t choose elements for their appearance. Choose elements for their behaviours and modify their appearance with CSS. This is usually much easier to do. And in the worst case, it is certainly more tenable than the reverse. I would rather have a button that doesn’t look pretty, than one certain users can’t even click!

Seriously, you shouldn’t ever be doing <div role=”button”>. Like, ever!

When you can’t change the element…

Wrap it. If you’re trying to add some interactive functionality to a component you don’t control, it may be a good idea to wrap that component in an interactive control. So say I want to use the Angular Material card component, but I also want users to be able to click on the card, perhaps to open an expanded view of the card’s content in a modal. I could add a click handler to the component like so:

<mat-card (click)=”someFunction()”>

But we now know why that’s problematic. I also don’t want to replace <mat-card> with <button>, because then I lose all the ready-made styling that comes with the component. So instead, I wrap the component in a button like so:

<button (click)=”someFunction()”>


With one line of CSS to make that button render as a block container, I would have the best of both worlds — my third-party component, and natively supported interactive behaviour. True, this may sometimes deviate from the expected content for button elements, which is non-interactive phrasing content. But it reliably provides the desired behaviour with very little effort.

When not to wrap it

In some cases, you want to do this the other way around with the interactive element being nested in another element or component. Take navigation links as an example. You usually want to group these into <ul> elements, which makes sense since it’s a list of links. But should the unordered list contain list items, or anchor links? The only semantically correct child a <ul> can have is an <li>, for obvious reasons. It is, afterall, a list and should only contain list items. So you may find yourself contemplating something like this:

<li onclick=” location.pathname = ‘/about-us’”>

Again, this is problematic because <li> is not an interactive element, and since all direct children of <ul> must be <li>, you can’t wrap the list item in an anchor link. The correct way to do this would be to have a list item containing a link. Like so:

<a href=”/about-us”>
About Us

Sidebar: To link or not to link

You should only ever use an anchor link element if it performs navigation. This means that every <a> tag should have a meaningful href attribute that actually takes the user somewhere. This includes cases — such as in a Single Page Application — where the default behaviour is overridden by JavaScript. Using the right semantic tag even in such cases affords your users additional functionality like opening the link in a new tab. The default behaviour may also work as a fallback when JavaScript fails. In every other case, you should use the button element.

We’ve seen how to choose the right elements based on their intended behaviour. But we also saw that sometimes we don’t have full control over what HTML element is used in parts of our web app. The next article in the series will discuss in more depth the different cases in which the element used is decided by a third-party, and how best to manage them without compromising keyboard accessibility.
The link will be updated once it is published.

Next: Choosing Third Party Widgets and Custom Controls

Previous: The How and Why of Keyboard Accessibility