Accessibility Guidelines

At Zaengle, our motto is "Be Nice, Do Good". An important aspect of living up to that standard in our code is ensuring that the sites and apps we build are fully accessible to all users. The last thing we want to do as developers is code in such a way that the end result of our code is a site/app that provides a bad experience (or even impossible experience) for some of our users.

It's important to keep in mind that "normal" users (i.e., those without disabilities), are a small subset of all users. Therefore, it's essential that we keep all user "types" in mind when write code. Building sites/apps that are accessible to users with disabilities will inevitably lead to products that provide a better experience for all users, which is certainly a worthy goal. So, as developers at Zaengle, let's all strive for that goal and make the Internet a more accessible place, one site at a time.

We can do so by following the guidelines below. It's important to note that while, yes, these are things that can be changed after the fact, we should be focusing on accessibility as we build -- it shouldn't be an afterthought.

Common Mistakes to Avoid

There are several accessibility anti-patterns. The following are some common mistakes that should be avoided:

  • Do not use a <div> (or other element, like a <span>) in place of a <button>.

    • Related to this point, the "logout" nav item should not be an <a> element. It should be a <button> element that's styled to look like the other nav items.
  • Do not skip heading levels simply to use the browser's built-in styling for headings. Use headings in order and style them as needed with custom CSS.

  • Do not remove focus indicators entirely. Either leave the browser defaults or provide custom styles. Tailwind provides useful classes for this (e.g., focus:outline-none focus:shadow-outline).

  • Avoid a tabindex greater than 0. If an element needs to come sooner in the tab order, move it to an earlier spot in the DOM.

Buttons

  • Buttons should always be a <button> element with a type (typically either submit or button).

  • Icon buttons, or any buttons that don't have text content, must be given an aria-label property that contains information about the button (e.g., aria-label="Edit file"). It can also be useful to provide an aria-label to buttons that are repeated. For example, a list of items, each item of which has a Remove button, can be given an aria-label property of Remove file <file_name>. This will give a screen reader user more useful context about the button.

  • As mentioned above, do not remove focus indicators from buttons.

Examples of Accessible Code

<button
  type="button"
  aria-label="Download file"
>
  <svg
    xmlns="http://www.w3.org/2000/svg"
    fill="none"
    viewBox="0 0 24 24"
    stroke="currentColor"
  >
    <path
      stroke-linecap="round"
      stroke-linejoin="round"
      stroke-width="2"
      d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M9 19l3 3m0 0l3-3m-3 3V10"
    />
  </svg>
</button>
<div v-for="file in files">
  <span v-text="file.name">
  <button
    aria-label="`Remove ${file.name}`"
    v-text="Remove"
  >
</div>

Form Elements

  • Every form element must have an associated <label>. This can be done by placing the form element inside of the <label> element or by using the <label>'s for attribute to refer to the form element's id attribute.

  • Even if the label shouldn't be visible to sighted users, it still must be present in the DOM and be properly associated with the form element. In such cases, the label can be hidden with CSS (Tailwind provides the sr-only class for just such a case). However, labels provide useful click targets for sighted users, so in most cases, it's best to have the label be visible to sighted users as well.

  • As mentioned above, do not remove focus indicators from form elements.

Examples of Accessible Code

<!-- The input is inside of the label -->
<label>
  <input type="checkbox">Receive promotional offers?</input>
</label>
<!-- The label's for value matches the input's id value -->
<input
  id="promo"
  type="checkbox"
>
<label for="promo">Receive promotional offers?</label>
<!-- The label is properly associated with the input, but hidden from sighted users -->
<label
  for="search"
  class="sr-only"
>
  Search users
</label>
<input
  id="search"
  type="text"
  placeholder="Search users"
>

Images

  • All images must have an alt attribute.

  • Decorative images (i.e., those that do not add any information to the page's content) should be given an empty alt (alt="").

    • The Zaengle logo on this site is a perfect example of a decorative image. Vuepress mistakenly gives the logo an alt of the title provided in the config (i.e., alt="Developer Guidelines"), which will result in a screen reader announcing "developer guidelines" twice in a row.

Example of Accessible Code

<!-- This is the accessible version of this site's logo -->
<img
  src="/logo.svg"
  alt=""
  class="logo"
>
  • Links should contain meaningful text (i.e., something more descriptive than "click here" or "read more").

  • It's common to use a logo as a home link in a site's navigation. In such cases, the link will need an aria-label in order to provide accessible text to a screen reader.

  • Like with buttons, if there are multiple list items that all contain the same link (e.g., imagine the list of Zaengsters on the Zaengle site had a "Learn More" link for each Zaengster), each link should be given an aria-label to provide more useful context to a screen reader user (e.g., aria-label="Learn more about Philip Zaengle").

  • As mentioned above, do not remove focus indicators from links.

Examples of Accessible Code

<!-- This link has text that will give a screen reader user a good idea of the link's purpose. -->
<p>
  We have lots of great products.
  <a href="/products">View what we have in stock right now.</a>
</p>
<!-- The aria-label tells a screen reader user where the link leads. -->
<a
  href="/"
  aria-label="Home"
>
  <img
    src="/logo.svg"
    alt=""
    class="logo"
  >
</a>

CSS

  • All text contrast must meet the WCAG AA standard, which requires a ratio of at least 4.5:1 for normal text and 3:1 for large text.

    • Large text is classified as 18.66+ px for bold text and 24+ px for non-bold text.
  • Do not use CSS to reorder HTML elements; it will result in an illogical tab order. If content has to be rearranged, change the order of the elements in the HTML.

  • Do not rely on color alone to convey information.

    • An active link should be denoted by more than just a different color (e.g., add a bottom border and provide the aria-current="true" attribute).
    • Provide error messages for form fields, rather than just outlining the form element in red.
  • Use relative units (em or rem) for text size.

    • Text that's sized in px will not update when the user updates his/her text size preferences in the browser.

User Zooming

  • When configuring the viewport meta tag, never set maximum-scale=1 or user-scalable=no. Both of these settings prevent the user from zooming.

Example of Accessible Code

<meta name="viewport" content="width=device-width, initial-scale=1">

Tooling

  • The eslint-plugin-vue-a11y package can be installed and added to the ESLint config in order to provide accessibility-related linting rules.

  • Google Chrome and Brave Browser both come equipped with Lighthouse, which can be used to run accessibility checks. There's also a Lighthouse extension available for Firefox.

    • Lighthouse won't find all issues, so manual testing is still required. But it's good at finding issues with color contrast, missing accessible names, non-semantic HTML, etc.
  • There are many contrast checkers available online. A reliable one is the WebAIM Contrast Checker.

  • You can check screen reader output on a Mac by enabling VoiceOver with Command + fn + F5.

ESLint Config with eslint-plugin-vue-a11y

module.exports = {
  extends: ['@nuxtjs', 'plugin:nuxt/recommended', 'plugin:vue-a11y/base'],
  plugins: ['vue-a11y'],
}