DEV Community

Cover image for Updating your existing apps for accessibility
Brian Neville-O'Neill
Brian Neville-O'Neill

Posted on • Originally published at blog.logrocket.com on

Updating your existing apps for accessibility

Written by Raphael Ugwu✏️

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.

LogRocket Free Trial Banner

Improving markup with ARIA Attributes

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"));
Enter fullscreen mode Exit fullscreen mode

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>
    );
  }
Enter fullscreen mode Exit fullscreen mode

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>
    );
  }
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

Managing Focus

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>
Enter fullscreen mode Exit fullscreen mode

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"));
Enter fullscreen mode Exit fullscreen mode

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.

Accessibility in route transitions

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;
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

In the code sample above, this.$announcer notifies the user by sending an auditory message three seconds after the page has loaded.

Summary

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.


Editor's note: Seeing something wrong with this post? You can find the correct version here.

Plug: LogRocket, a DVR for web apps

 
LogRocket Dashboard Free Trial Banner
 
LogRocket is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
 
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.
 
Try it for free.


The post Updating your existing apps for accessibility appeared first on LogRocket Blog.

Top comments (0)