Improving Accessibility

a11y
Improving
Mark Mark Southard

ConvertKit exists to help all creators earn a living. What follows describes the efforts we are making to improve ConvertKit for creators with disabilities. While our app is not perfect, we are actively working to make ConvertKit more accessible for all creators.

What is web accessibility?

Before discussing the steps we are taking to improve accessibility within ConvertKit, it is important to understand just what web accessibility is. Web accessibility (or a11y) relates to how people are able to interact with the internet. Depending on your source, 15-20% of the world's population has a disability. These disabilities can have a major impact on a person's ability to use the internet; she may be limited in navigating web pages or reading text on the screen. The World Wide Web Consortium (W3C) defines web accessibility as follows:

Web accessibility means that websites, tools, and technologies are designed and developed so that people with disabilities can use them. More specifically, people can: perceive, understand, navigate, and interact with the Web and contribute to the Web

In an effort to make the web more accessible, the W3C has started the Web Accessibility Initiative to define the guidelines for accessibility solutions. These guidelines tell us, as developers, what we need to do in order to ensure that all people can successfully navigate and interact with the websites and applications that we create.

As developers, what can we do?

First and foremost, ensure that you are writing semantic HTML. The <header> tag should be used for site wide information, search, and navigation. The <footer> tag should be used for copyright information, disclaimers, etc. The <main> tag should be used for the main content region. The <nav> tag should be used for navigation. Heading tags <h1> through <h6> should be used for headings only. A lot of developers have gotten a bad habit of using heading tags for styling elements. Instead of using heading tags to add styling, use the appropriate HTML tag and style it with CSS. By writing semantic HTML, we're creating a better document structure, which enables screen readers to more easily navigate through a web page.

Beyond writing semantic HTML, there are a lot of things that we need to do to make our websites and applications accessible:

  • Add alt attributes on img tags
  • Add labels to form fields
  • Ensure proper contrast between foreground and background colors
  • Add textual hints to icon buttons
  • Use Accessible Rich Internet Applications (ARIA) attributes as needed
  • And much more

While the above was not an exhaustive list, you may be overwhelmed by some of the items. You may be thinking that the efforts to improve accessibility are not worth it. But, remember web accessibility helps everyone; one of the great benefits of improving a11y for people with permanent impairments is that you also improve things for people with temporary impairments. For example, avoiding very small text helps people with a permanent visual impairment, but it also helps people who have misplaced their glasses. Making your site navigable by keyboard helps those who don't usually use a mouse, and those who are having temporary difficulty using a mouse due to an injury.

Convinced it's time to improve accessiblity on your site, but still feeling a little overwhelmed? Don't worry, I have a two step process you can follow to make your website more accessible.

The 2 steps to making your website more accessible

1. Take stock; perform an accessibility audit

As a first step, you should perform an accessibility audit. If the word "audit" scares you, don't worry, performing an accessibility audit couldn't be easier. Chrome comes equipped with Lighthouse which can perform an accessibility audit. To access Lighthouse, open Chrome's dev tools (View > Developer > Developer Tools), and click on the Audits tab. Run a baseline test on desktop with no throttling for just accessibility. Once you've run your audit, you'll get a score. When I first ran this test for ConvertKit, it wasn't good. Most pages scored between 60% and 75%. Regrettably, the inspiration for this post came long after initial improvements were made within ConvertKit. Thus, I'll be improving the accessibility of the very blog that you are reading.

Ititial a11y score of 81

Our first audit scores 81, with three line items we'll need to address. Note that this is starting off in a very good place. The HTML structure of the page is good, and for the most part, tags have been used for the appropriate use case. If you've been writing semantic HTML, you could find your website scoring as well or better.

2. Start making improvements

Our second step is to start making improvements by addressing the flagged items. Looking at the list, I see some quick wins: the <html> tag is missing a language setting and some <img> tags are missing alt attributes. If you ever get flagged on something that you do not understand, you can click to expand the section to get more information (with a link to even more information).

Expanded audit section

Lets tackle those easy wins I highlighted, and add the lang attribute to the <html> tag and the alt attribute to each <img> tag. After running a fresh audit, our page is now scoring 97. That's a pretty big payoff for what was two minutes of work.

Continuing down the list from our original audit, I see Links do not have a discernable name. Like many other sites, our footer has links to our social profiles. Because we have used icons in place of text, screen readers cannot access these icons. I need to add an aria label to describe them (aria-label="Connect with ConvertKit on Facebook"). How did I know this was the solution? The audit told me how to fix the issue. And with that, our blog is scoring 100 for accessibility (happy dance).

Taking audits to the next level

Google Lighthouse does a great at providing a baseline audit of a page on load, but what about interactivity? What about the issues that are hidden because they exist in a modal that is not currently displayed? Enter axe. Axe is an amazing tool that has a browser extension for Chrome, Firefox and Android. Axe allows you to run an accessibility audits after interacting with a page. Now you can perform real actions (e.g., open a modal) and audit the screen for the elements currently displayed. Axe is a great tool that I highly recommend, but performing manual audits can quickly become cumbersome. With this blog, it's fairly simple to maintain a high level of accessibility. We'll only be adding new articles which should not introduce any regressions to our current level of accessibility. For the ConvertKit application, this isn't the case. Each and everyday we write code that has the potential to introduce an accessibility error. Manual audits are a reactive measure, and I want to be proactive in preventing accessibility regression.

Introducing a11y linting

After manually performing audits on several pages within the app and improving them one at a time (most pages are now scoring 85 or higher on Lighthouse), I decided it was time to automate. While the accessibility was improved, there were no safeguards to prevent regression. Our code is written with React and we use eslint to identify errors and maintain a high level of code. Thus, it was natural to introduce eslint-plugin-jsx-a11y to our codebase1. Setting this up was as simple as adding the plugin via yarn add eslint-plugin-jsx-a11y --dev. If you are not currently using eslint, you will need to install it first. Once installed, I added jsx-a11y to our plugins and set the rules via "plugin:jsx-a11y/recommended" in the extends section. Having already spent time improving our application's accessibility, I was shocked when running lint for the first time.

a11y linting errors

With 255 total problems, we've got a lot of work to do! Our development process does not allow us to ship any linting errors. Clearly this is good for maintaining our codebase, but it also means that I wouldn't be able to get the updated linter into production until those errors were fixed. Thus, I set the errors to warn instead and will fix them during my next a11y bug smash. Once all errors are fixed, we'll turn the rules back on so that new errors cannot be introduced into production. This will be our first line of defense against accessibility errors.

Unfortunately, the linter cannot identify some accessibility issues (e.g., poor contrast). Thus, it looks like we'll need to continue to perform occassional manual audits to ensure that we've not introduced a regression. Or do we?

Our plans for maintaining high levels of a11y

While I'm confident that our linting rules will go a long way in maintaining a high level of accessibility, there is still a possibility of regressions occurring. Thus, we're planning on introducing accessibility testing via Cypress. We're still in the early stages of implementation, but I am very excited for what is possible with Cypress. If you're unfamiliar with Cypress, it is an end-to-end testing framework for Javascript. Once you've installed Cypress, you can begin writing end-to-end tests that mimic user behavior. For example, you could write a test that visits the login screen to your application and logs in. This might look like:

  describe('Initial login test', function() {
    it('user can login', function() {
      cy.visit('/login')
      cy.get('.username').type('user')
      cy.get('.password').type('password'{enter})
      cy.url().should('include', '/dashboard')
    })
  })

With this test, we're ensuring that users can log into our app. If you're already using Cypress, you may already have a suite of tests on which you can build. You'll need to add and configure cypress-axe for your project. Once we have cypress-axe installed and configured, it is incredibly easy to automate our accessibility testing. For our pseudo test above, we would introduce accessibility testing as follows:

  describe('A11y login test', function() {
    it('user can login', function() {
      cy.visit('/login')
      cy.injectAxe()
      cy.checkA11y()
      cy.get('.username').type('user')
      cy.get('.password').type('password'{enter})
      cy.url().should('include', '/dashboard')
    })
  })

Voilà! With two lines of code, we've introduced accessibility testing on page load. Furthermore, we're free to invoke ck.checkA11y() whenever we want within the test. This means that we can have Cypress perform different actions, and then fire cy.checkA11y() to run an accessibility test. We'll now be able to automate the accessibility testing of elements that are hidden on page load. And this is what has me so excited; we're getting the best of both worlds: automation and full accessibility audits.

Conclusion

I hope that you are as excited for accessibility as I am. Making your website more accessible benefits every user. Right now, there are amazing tools available that make it incredibly easy to reduce accessibility errors. I hope that you recognize the importance of making the web as accessible as possible, and will dedicate the time needed to improve your website or application.

1. If you are not using React, you will want to explore the [Axe core library](https://github.com/dequelabs/axe-core) that powers the linting rules. There are a lot of other tools available for different languages and frameworks.

Tools for maintaining accessibility