Like many other developers, you’re probably eager to dip your toe into the waters of using a framework like Vue, Angular, or React to build out a single page application. Or perhaps you’ve already given it a try. There are lots of advantages to SPAs (single-page applications), and I’m sure you’re already familiar with those. But what about the disadvantages?
One big disadvantage of SPAs is that accessibility is often lacking. Does that mean that the common frameworks developers use to build SPAs are inaccessible? Nope. The frameworks themselves generally handle accessibility just fine. What’s lacking is developer knowledge in how to go about building a SPA to be accessible.
To be fair, most online tutorials that step you through using these frameworks and building your first SPA don’t focus on accessibility at all, and worse, don’t teach accessibility best practices. As a developer, there is one simple thing you can start doing now that will make big improvements to the accessibility of your SPAs: Use semantic HTML.
That’s it. Easy, right?
Use Semantic HTML
Use buttons for interactivity
This is first because it’s probably the worst offender. Don’t add interactivity to elements like
<li>s. If the user should be able to click something to interact with it, it should be a
Why it’s important: There are many users who rely on the keyboard alone to navigate through web pages. These users include screen reader users as well as users with mobility issues that prevent them from being able to effectively use a mouse. Adding a click event listener to a
<div> works great for users who can click it with a mouse. But a keyboard-only user can’t even get the keyboard to focus your
<div>, let alone trigger the click event.
And before you ask, sure, yes, you could add a
tabindex of -1 to the
<div>, and then add a keydown event handler to it as well as the click event handler to make it work for keyboard users. Then you could be sure to add all the right ARIA roles to encourage most screen readers to correctly handle your clickable
<div>. But that’s a whole lot of extra work to do, and chances are you’re just not going to have the time or budget to do it every single time you make something interactive. Trust me, just use a
Be clear about when to use buttons and when to use links
Aside from form inputs, there are two main ways that users can interact with your single page app – buttons and links. Know when to use them appropriately.
A link (<a>) takes the user to a new page, location, or route. By default, these are shown in browsers as underlined blue text. If clicking it will load up a new page (or the equivalent of a new page in your SPA), it should be a link.
Any other kind of of interactivity should be handled by a button. A button might delete an item from a list, open a modal dialog, submit a form, open a menu, or sort a list. By default, most browsers display these as gray boxes with rounded corners and some kind of shadow or border style that communicates affordance to the user.
Why it’s important: The difference in how these are handled by browsers and assistive technologies helps communicate what the user can expect when the button or link is clicked. It’s helpful as a user to know if clicking a thing will take you away from the content you’re viewing now, or if it is going to do something else.
More information: Links vs buttons in modern web applications by Marcy Sutton
Wrap form elements around forms
If you’re going to include input elements, like text inputs, checkboxes, radio buttons, or select elements, wrap them in a
<form> element and treat them just as you would if you were adding a form to a static HTML page. Depending on the SPA you’re building, this might mean that your entire SPA is wrapped in one big
If you want to do something fancy when the form is submitted, listen for the submit event on the form rather than adding click handlers to the submit button.
Why it’s important: Using a
<form> element means that your form works as expected and users automatically understand how to interact with it based on their previous experience with forms on the web. Additionally, assistive technology can recognize form elements in your code and trigger a special forms mode that makes it easier for people to interact with and complete your forms. When you just have random form elements on a page, outside of the context of a containing
<form> element, the screen reader is probably not going to be triggered to enter forms mode.
Use labels for form elements
Every single form element should have a
<label> associated with it, either implicitly or explicitly. And what’s more – those labels should be visible all of the time. Don’t rely on
placeholder attributes or
title attributes that are only visible sometimes unless the context for the input is really, really, really clear.
Why it’s important: A properly associated
<label> helps screen readers correctly inform users about what kind of input is expected in a form field. Without a label, the screen reader has to make a guess, and often get it wrong, making forms without labels difficult or impossible for screen reader users. Labels also help users with motor skill challenges by providing a larger interactive area for small controls like checkboxes and radio button because they can click/tap either the input or the label.
Wrap fieldsets around groups of form inputs
If you’re providing a list of multiple choice options as either checkboxes or radio buttons, wrap them in a
<fieldset> and include a
<legend> that describes what the group of options is about. If you have multiple form fields that are related, such as separate fields for first and last name, these should also be wrapped in a
<fieldset> as related fields.
Why it’s important: Grouping related elements together both visually and programmatically makes the form easier to understand for all users, but especially for screen reader users and users with cognitive disabilities. The
<legend> for the
<fieldset> helps to identify what all the different options have in common and make it easier to make a choice.
Use native HTML controls whenever possible
If there’s a native HTML control available that will perform the basic functionality required, use it rather than building a custom control. If you want a simple toggle on/off, then use a checkbox. If you want to submit a form, use a button.
Why it’s important: You’ll make your own life easier if you just use native HTML controls as much as possible. You won’t have to worry about writing your own event handlers for alternative input devices like keyboards. You won’t have to mess around with proper focus management. You won’t have to find clever and creative ways to communicate labels and values of form elements to screen readers. Your app will be more robust, faster and cheaper to build, and easier to maintain.
Using a native HTML control does not mean that you are stuck using the default appearance of those controls. It’s often possible to do some pretty extensive customizations to the visual appearance of controls while maintaining their native accessibility. See Inclusive Components by Heydon Pickering for some ideas.
Use headings and lists correctly
Guess what? The most basic stuff that you probably learned on the first day you ever wrote HTML still applies. If something’s a heading, mark it up as one and use the correct heading level. Don’t just apply some CSS styling to make it big and bold. Same goes for lists of items – go ahead and wrap those in an unordered list or an ordered list as appropriate.
Why it’s important: Assistive technology provides lots of handy ways for users to navigate around an HTML page by headings and lists. Headings can provide an outline of the content that’s available and lists help to communicate that a group of things goes together. Getting these basics right helps users navigate around your SPA and helps to boost SEO as well.
Nice article, Natalie. 👌 Every Web Dev who doesn’t implement accessibility should read this.
Here’s a little stuff I created some time ago and revisited just recently to practice accessibility and make it accessible after doing the study on Accessibility on https://developers.google.com/web/fundamentals/accessibility
The link: power-f-god.github.io/stat-131-refresher
I believe the app is now accessible as I did an audit with Chrome Dev Tools’ Lighthouse which gave me an accessibility score of about 100%. Kindly, if you have the time, you may check it out and I’d love to have your critiques/observations/suggestions too if any. 🙂
So, I learnt to always make my web pages and apps accessible and to build with accessibility in mind always.
And thanks for the lovely article. 🙂