The web has an ever-growing user base, and more activities than ever are centered around web applications. It’s important for developers and product managers to build interfaces that are applicable to not only a lot of use cases, but a wide range of abilities as well. The World Wide Web Consortium (W3C) created a set of specifications to show how web apps can be made accessible to individuals who may face challenges when using them. This includes people with physical, visual, speech, auditory, and intellectual impairments.
JavaScript is arguably the most popular language used to build web apps, and its two most popular frameworks are React and Vue. Let’s take a look at how we can make web apps built with either framework more accessible to users with limitations.
Accessible Rich Internet Applications (ARIA) attributes are huge part of accessibility in web apps. You can use them to specify attributes that define the way an element is translated into the accessibility tree.
To demonstrate how ARIA attributes can be used to improve accessibility in React apps, let’s say we have an e-commerce app and we want to make the checkout process easy.
render() { return ( <div> <h3>"Click below to use Prime's lifetime access at $10.99 per month"</h3> <button onClick={this.makePayment}>Pay Here</button> </div> ); } } render(<Payment />, document.getElementById("root"));
Here’s the problem: if a screen reader is being used on this web app, it might detect the button but not the text in the <h3>
tag. As a result, a visually impaired user who doesn’t detect this might unknowingly sign up for a service where they’ll be deducted every other month. We can use an ARIA attribute to make this more accessible.
render() { return ( <div> <h3> Click below to use Prime's lifetime access at $10.99 per month </h3> <button onClick={this.makePayment} aria-label="Click below to use Prime's lifetime access at $10.99 per month" > Pay Here </button> </div> ); }
In the code sample above, aria-label
tells the app’s users what exactly the button pays for. But what if the text in the <h3>
tag is really long? We wouldn’t want to fit in an entire paragraph in aria-label
. Let’s modify our return
statement to include another ARIA attribute:
render() { return ( <div> <h3 id="lifetimeAccess"> Click below to use Prime's lifetime access at $10.99 per month </h3> <button onClick={this.makePayment} aria-labelledby="lifetimeAccess" > Pay Here </button> </div> ); }
With the aria-labelledby
attribute, a screen reader can detect that the element with the id
of lifetime access is the button’s label.
With Vue, this is almost the same thing except for changes in syntax:
<template> <div> <h3 :id="`lifetimeAccess`"> Click below to use Prime's lifetime access at $10.99 per month </h3> <button @click="makePayment()" :aria-labelledby="`lifetimeAccess`" > Pay Here </button> </div> </template>
It’s important to give users options for how to handle focus when accessing your app. Keyboard focus is a good option because it allows people who have limited mobility in their wrists to access your app easily. Vue implements keyboard focus through the use of custom directives.
<template> <div id="app"> <div v-if="flow == 'search'"> <input type="text" placeholder="Enter your query" v-model="search" v-focus> <button>Search</button> </div> </div> </template> <script> import Vue from "vue"; Vue.directive("focus", { inserted: function(el) { el.focus(); }, update: function(el) { Vue.nextTick(function() { el.focus(); }); } }); export default { name: "App", data() { return { flow: "search", search: null }; } }; </script>
In the code sample above, v-focus
is registered globally as a custom directive. It is then inserted into the DOM and wrapped in a nextTick
. This will hold the focus event until the DOM is updated and the input is displayed.
As shown in the short clip above, the focused element is the one currently receiving input. React accomplishes the same thing with refs
. You can use refs to access DOM nodes or React elements created in the render
method.
Here we’ll create a ref for the component to which we want to add an element and then update the focus using the componentDidMount
lifecycle method:
import React, { Component } from "react"; import { render } from "react-dom"; class App extends Component { constructor(props) { super(props); this.focusDiv = React.createRef(); } componentDidMount() { this.focusDiv.current.focus(); } render() { return ( <div className="app"> <input tabIndex="-1" ref={this.focusDiv} placeholder="Enter your query" /> <button>Search</button> </div> ); } } render(<App />, document.getElementById("root"));
The tabIndex
value is set to -1
to allow you to set programmatic focus on an element that is not natively focusable. When configuring keyboard focus, do not add CSS styles that remove the outline or border of elements, since these could affect the outline that appears when an element is in focus.
Screen readers have certain limitations with navigating routes in single-page apps built with React or Vue. During navigation, the routing software of these frameworks handles some of the navigation actions from the browser to prevent constant reloading of the host HTML page.
Screen readers depend on the browser to feed them updates on navigation, but since this functionality is being handled by frameworks, what follows is a totally silent page transition for visually challenged users. Other examples are error situations and content and state changes in our application that could be very clear visually but go undetected by screen readers.
react-aria-live
is a React library that uses ARIA live regions to announce route changes in an application. Suppose we want a visually impaired user to know that the Order
page in an e-commerce app has loaded:
import React, { Component } from "react"; import { LiveAnnouncer, LiveMessage } from "react-aria-live"; class App extends Component { state = { message: "" }; componentDidMount() { document.title = "Orders Page"; setTimeout(() => { this.setState({ message: "The Orders page has loaded" }); }, 3000); } render() { return ( <LiveAnnouncer> <h1 tabIndex="-1"> Confirm your orders here</h1> <LiveMessage message={this.state.message} aria-live="polite" /> ); } </LiveAnnouncer> ); } } export default App;
In the code sample above, LiveAnnouncer
wraps the entire app and renders a visually hidden message area that can broadcast aria-live
messages. The LiveMessage
component does not have to exist in the same component as LiveAnnouncer
; as long as it exists inside a component tree wrapped by LiveAnnouncer
, it is used to convey the message using either an assertive
or polite
tone.
Vue informs screen readers of route changes with vue-announcer
, a library similar to react-aria-live
. Here you may also have to manually configure messages. Let’s replicate the same Orders
page, only this time using Vue:
<template> <div id="app"> <h1 tabindex="-1">Confirm your orders here</h1> </div> </template> <script> export default { name: "App", head: { title: { inner: "Orders Page" } }, methods: { mounted() { setTimeout(() => { let message = `The Orders page has loaded`; this.$announcer.set(message); }, 3000); } } }; </script>
In the code sample above, this.$announcer
notifies the user by sending an auditory message three seconds after the page has loaded.
A huge first step toward achieving accessibility is acknowledging that there are people out there who do not use your apps and devices in a conventional manner. Building apps that address their needs can help increase user retention and demonstrate your commitment to inclusiveness.
Debugging Vue.js applications can be difficult, especially when there are dozens, if not hundreds of mutations during a user session. If you’re interested in monitoring and tracking Vue mutations for all of your users in production, try LogRocket.
LogRocket is like a DVR for web and mobile apps, recording literally everything that happens in your Vue apps, including network requests, JavaScript errors, performance problems, and much more. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred.
The LogRocket Vuex plugin logs Vuex mutations to the LogRocket console, giving you context around what led to an error and what state the application was in when an issue occurred.
Modernize how you debug your Vue apps — start monitoring for free.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowHandle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.
Design React Native UIs that look great on any device by using adaptive layouts, responsive scaling, and platform-specific tools.
Angular’s two-way data binding has evolved with signals, offering improved performance, simpler syntax, and better type inference.