Andrew Welch · Insights · #frontend #accessibility #best-practices

Published , updated · 5 min read ·


Please consider 🎗 sponsoring me 🎗 to keep writing articles like this.

Making Websites Better through Accessibility

Mak­ing your web­site acces­si­ble isn’t just about legal com­pli­ance, it also means not turn­ing away cus­tomers. Because who wants to do that?

Mak­ing your web­site acces­si­ble means mak­ing it avail­able to the largest num­ber of peo­ple pos­si­ble. It’s already a Her­culean task to get peo­ple to your web­site to begin with, so we cer­tain­ly don’t want to turn a % of peo­ple away due to acces­si­bil­i­ty issues.

I find that many devel­op­ers don’t real­ly under­stand acces­si­bil­i­ty, or shuf­fle it off to the it’d be nice” list on their tasks. With a lit­tle edu­ca­tion and prop­er tool­ing, it does­n’t have to be that way.

I view website accessibility as customer acquisition, as well as building an inclusive website that serves the most number of people as possible

In many coun­tries, web­site acces­si­bil­i­ty is also man­dat­ed by the law. In the USA, for instance, there is the Amer­i­cans with Dis­abil­i­ties Act (ADA), which the DOJ has made clear extends to the Internet.

Com­pa­nies such as Tar­get (see NFB V. Tar­get) and Net­flix (see NAD V. Net­flix) have been the sub­ject of law­suits based on this; but it’s also just good busi­ness to allow as many peo­ple as pos­si­ble access to your com­pa­ny’s prod­uct or service.

If you plan on work­ing on a web­site for US Fed­er­al or State gov­ern­ments (which includes edu­ca­tion­al insti­tu­tions), the Web­site Acces­si­bil­i­ty Under Title II of the ADA doc­u­ment out­lines the acces­si­bil­i­ty man­dates for web­sites. Even pub­lic com­pa­nies may very well need to com­ply with the ADA; check out the Does your web­site need to be ADA com­pli­ant? arti­cle for details.

I’m just going to assume that you want to make your web­site acces­si­ble for the prac­ti­cal rea­sons men­tioned above that go beyond legal com­pli­ance. Like many of the top­ics that I’ve dis­cussed before, this is all about mak­ing the web bet­ter.

Both performance & accessibility are not about padding proposals; they are about making websites that are more effective for both our clients, and their customers.

This is also how we should sell acces­si­bil­i­ty to our clients, sim­i­lar to how we talked about sell­ing per­for­mance in the A Pret­ty Web­site Isn’t Enough arti­cle. We should build acces­si­bil­i­ty into our pro­pos­als, and inte­grate it into our design & devel­op­ment work­flow. Edu­cate clients on why it is impor­tant, and they’ll quick­ly get on board.

The Per­sonas for Acces­si­ble UX arti­cle is a super-use­ful resource for under­stand­ing the type of dis­abil­i­ties peo­ple may have, and how that impacts their abil­i­ty to access your web­site. Take the time to read through the per­sonas list­ed there, and come back when you’re done.

Real­ly, it’s worth it. I’ll wait.

Link Major Use Cases

So here’s a non-exhaus­tive syn­op­sis of the dis­abil­i­ties that peo­ple may encounter with your web­site (cribbed from here):

  • Vision (blind­ness, low vision, col­or blindness)
    • Over 6,500,000 adults, most of them between 18 and 64, and over 650,000 chil­dren in the US report­ed issues relat­ed to low vision or blind­ness in 2011.
    • About 1 in 10 men are col­or blind.
  • Hear­ing (deaf­ness)
    • About 36 mil­lion adults in the US have some form of hear­ing loss.
    • About 2 in 1000 chil­dren in the US are born deaf.
  • Motor skills (trou­ble with using a mouse)
  • Cog­ni­tive and learn­ing issues (autism, dyslex­ia, ADD, etc.)
    • About 1 in 88 chil­dren in the US are on the autism spectrum.
    • Prob­a­bly about 20% of peo­ple have some form of dyslexia.
    • 11% of chil­dren 4 – 17 years of age, and about 4% of adults, in the US have been diag­nosed with ADD.

In gen­er­al, it’s esti­mat­ed that 12 – 20% of peo­ple have a dis­abil­i­ty of some kind.

So let’s do some­thing about it!

Link Pa11y as Tooling for Accessibility

Actu­al­ly mak­ing web­sites acces­si­ble isn’t that hard; the hard part is know­ing what you should be doing. While there are some web-based tools out there, I’ve found that the npm pack­age Pa11y to be the most up to date and effec­tive tool for me.

Pa11y is just a CLI lay­er on top of HTML_​CodeSniffer, which enforces the three con­for­mance lev­els of the Web Con­tent Acces­si­bil­i­ty Guide­lines (WCAG) 2.0, and the web-relat­ed com­po­nents of the U.S. Sec­tion 508” leg­is­la­tion.

So let’s talk about how to use it.

The abbreviation #a11y is often used for “accessibility”, where the number 11 refers to the number of letters omitted from “accessibility”

I’ll state right upfront that I’m no expert on acces­si­bil­i­ty; if you want to do a deep-dive on acces­si­bil­i­ty, check out the Get­ting Start­ed with Web Acces­si­bil­i­ty & Using Aria resources. But if you use seman­tic HTML5 ele­ments, use a prop­er head­ing hier­ar­chy, and use alt attrib­ut­es for images, you’re halfway home.

For the rest of it, pa11y gives you a very easy to under­stand list of what’s bro­ken, and how to fix it. It’s very sim­i­lar to using the WC3 Val­ida­tor to check your HTML for cor­rect­ness, so it’s not hard to get going on a bul­let-point­ed list of things to fix.

You will need to have Node & NPM (or Yarn) installed in order to take advan­tage of it, but hope­ful­ly you’re already up to speed using these tools. If you’re not, def­i­nite­ly con­sid­er get­ting on the band­wag­on, because fron­tend devel­op­ment is mov­ing inex­orably towards them.

So the first thing we’ll do is install a pack­age called npx; this lets you run node mod­ules with­out hav­ing to install them (either glob­al­ly or as part of a pack­age), either via npm or yarn:

sudo npm install -g npx
sudo yarn global add npx

We’re installing the npx pack­age glob­al­ly, so we’re using sudo to make it hap­pen; but you can also con­sid­er check­ing out how to install pack­ages glob­al­ly with­out sudo if you choose to, or per­haps don’t have sudo access.

Of course, if you like you can add pa11y as a depen­den­cy in your project as well, if you pre­fer to do it that way. You could even install pa11y glob­al­ly, the same way we did with npx. But I want­ed to intro­duce using npx as a gen­er­al way to exe­cute Node mod­ules with­out hav­ing to install them.

npx also has a spe­cif­ic ben­e­fit in this case: it’ll make sure we’re always run­ning the lat­est pa11y with the lat­est rule­sets, with­out hav­ing to keep it up to date.

Link Fixing Accessibility Problems

So now that we have npx installed, we can run pa11y and gets its eval­u­a­tion of the acces­si­bil­i­ty of our web­site! We’ll use the blog page of this very web­site as our guinea pig for acces­si­bil­i­ty issues:

To run pa11y, we can just do:

npx pa11y --standard "WCAG2AA" --ignore "notice;warning" https://nystudio107.com/blog

We’re telling it that we don’t want to see any notices or warnings, but rather just errors. If you want to do a more com­pre­hen­sive check, you can show the warnings too, by omit­ting it from the command.

Out of the box, pa11y sup­ports 4 acces­si­bil­i­ty stan­dards: Section508, WCAG2A, WCAG2AA, WCAG2AAA. The default is WCAG2AA, but you can spec­i­fy any stan­dard you want via the --standard com­mand line argument.

All of the WCAG2* stan­dards are the same inter­na­tion­al stan­dard, but with dif­fer­ent lev­els of strict­ness (A being the least strict, AAA being the most strict). Addi­tion­al infor­ma­tion on WCAG lev­els can be found in Under­stand­ing Lev­els of Con­for­mance.

The Section508 stan­dard is for US Fed­er­al & State gov­ern­ment insti­tu­tions, should you be work­ing on web­sites for them.

But we’re just going to use the default WCAG2AA stan­dard, which is mid­dle of the road in terms of strict­ness (and indeed is the stan­dard that WCAG sug­gests to use), and is used by default if we don’t spec­i­fy a --standard.

Here’s what the out­put looks like:

vagrant@homestead:~$ npx pa11y --ignore "notice;warning" https://nystudio107.com/blog
Welcome to Pa11y

 > PhantomJS browser created
 > Testing the page "https://nystudio107.com/blog"

Results for https://nystudio107.com/blog:

 • Error: Anchor element found with a valid href attribute, but no link content has been supplied.
   ├── WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.NoContent
   ├── #nav-menu > div > div:nth-child(1) > div > div > a
   └── <a href="/"><svg xmlns="http://www.w3.org/2...</a>

 • Error: This element has insufficient contrast at this conformance level. Expected a contrast ratio of at least 4.5:1, but text in this element has a contrast ratio of 2.93:1. Recommendation: change background to #3c3d3f.
   ├── WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail
   ├── #nav-menu > div > div:nth-child(3) > ul > li:nth-child(2) > a
   └── <a href="/blog" class="nav-link">Blog</a>

 • Error: This text input element does not have a name available to an accessibility API. Valid names are: label element, title attribute.
   ├── WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.InputText.Name
   ├── #searchbox
   └── <input type="text" id="searchbox" placeholder="search" autocomplete="off" class="autocomplete-input">

 • Error: This form field should be labelled in some way. Use the label element (either with a "for" attribute or wrapped around the form field), or "title", "aria-label" or "aria-labelledby" attributes as appropriate.
   ├── WCAG2AA.Principle1.Guideline1_3.1_3_1.F68
   ├── #searchbox
   └── <input type="text" id="searchbox" placeholder="search" autocomplete="off" class="autocomplete-input">

 • Error: Anchor element found with a valid href attribute, but no link content has been supplied.
   ├── WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.NoContent
   ├── #nys-footer > div > div > div > div:nth-child(1) > div > a
   └── <a href="/"> <svg class="scaling-svg sub-lo...</a>

 • Error: Anchor element found with a valid href attribute, but no link content has been supplied.
   ├── WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.NoContent
   ├── #nys-footer > div > div > div > div:nth-child(2) > a:nth-child(1)
   └── <a class="social" href="mailto:info@nystudio107.com"><i class="icon-mail-alt"></i></a>

 • Error: Anchor element found with a valid href attribute, but no link content has been supplied.
   ├── WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.NoContent
   ├── #nys-footer > div > div > div > div:nth-child(2) > a:nth-child(2)
   └── <a rel="nofollow" class="social" href="https://www.facebook.com/newyorkstudio107"><i class="icon-facebook"></i></a>

 • Error: Anchor element found with a valid href attribute, but no link content has been supplied.
   ├── WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.NoContent
   ├── #nys-footer > div > div > div > div:nth-child(2) > a:nth-child(3)
   └── <a rel="nofollow" class="social" href="https://twitter.com/nystudio107"><i class="icon-twitter"></i></a>

 • Error: Anchor element found with a valid href attribute, but no link content has been supplied.
   ├── WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.NoContent
   ├── #nys-footer > div > div > div > div:nth-child(2) > a:nth-child(4)
   └── <a rel="nofollow" class="social" href="https://github.com/nystudio107"><i class="icon-github-circled">...</a>

 • Error: Iframe element requires a non-empty title attribute that identifies the frame.
   ├── WCAG2AA.Principle2.Guideline2_4.2_4_1.H64.1
   ├── #Smallchat > iframe
   └── <iframe data-reactroot="" style="z-index: 999999999; position: fixed; right: 0px; bottom: 0px; border: 0px; background-image: none; transition: width 200ms cubic-bezier(0.25, 0.25, 0.5, 1), height 200ms cubic-bezier(0.25, 0.25, 0.5, 1); -webkit-trans...

10 Errors
0 Warnings
0 Notices

What it actu­al­ly does is pret­ty nifty: it fires up a head­less” brows­er, ren­ders your web­page in it cour­tesy of Phan­tomJS, and then ana­lyzes it for acces­si­bil­i­ty issues. This is pret­ty sim­i­lar to how Crit­i­cal CSS is gen­er­at­ed, as per the Imple­ment­ing Crit­i­cal CSS on your web­site article.

In any event, that’s quite a num­ber of errors, so let’s take them one by one!

 • Error: Anchor element found with a valid href attribute, but no link content has been supplied.
   ├── WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.NoContent
   ├── #nav-menu > div > div:nth-child(1) > div > div > a
   └── <a href="/"><svg xmlns="http://www.w3.org/2...</a>

This can be fixed by adding a <title>homepage</title> as a child of the <svg> that is dis­played inline in the glob­al site head­er, so there’s some text for screen read­ers to parse. Link tar­gets for screen read­ers should say where the link will take them, or what click­ing on the link will do, not describe what the thing inside the link is (a logo, in this case).

Just imag­ine some­one read­ing the web­page aloud to you, if they said link nystudio107 logo” you’d have no idea what click­ing on it does. If instead they said link home­page” then you’d get it.

Anoth­er way that this issue can also be resolved is by putting text inside of the <a> tag that is invis­i­ble to the eye, but screen read­ers will pick up. We can do this with a <span class="sr-only"> using the CSS from Twit­ter Bootstrap:

.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0,0,0,0);
    border: 0;
}

That way, screen read­ers will have some­thing to read!

 • Error: This element has insufficient contrast at this conformance level. Expected a contrast ratio of at least 4.5:1, but text in this element has a contrast ratio of 2.93:1. Recommendation: change background to #3c3d3f.
   ├── WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail
   ├── #nav-menu > div > div:nth-child(3) > ul > li:nth-child(2) > a
   └── <a href="/blog" class="nav-link">Blog</a>

This is say­ing that the con­trast ratio between our menu links and the head­er back­ground col­or isn’t high enough. It sug­gests chang­ing it from #58595b to #3C3D3F to increase the con­trast ratio. This is to assist with read­abilty for peo­ple who have vision problems.

Since these are the nystudio107 brand­ing col­ors, the prob­lem could have been mit­i­gat­ed by apply­ing the tips from the arti­cle Tips on Design­ing for Web Acces­si­bil­i­ty dur­ing the design process.

 • Error: This text input element does not have a name available to an accessibility API. Valid names are: label element, title attribute.
   ├── WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.InputText.Name
   ├── #searchbox
   └── <input type="text" id="searchbox" placeholder="search" autocomplete="off" class="autocomplete-input">

 • Error: This form field should be labelled in some way. Use the label element (either with a "for" attribute or wrapped around the form field), or "title", "aria-label" or "aria-labelledby" attributes as appropriate.
   ├── WCAG2AA.Principle1.Guideline1_3.1_3_1.F68
   ├── #searchbox
   └── <input type="text" id="searchbox" placeholder="search" autocomplete="off" class="autocomplete-input">

This is say­ing that our <input> ele­ment used for site search lacks a aria-label attribute for screen read­ers. This can be fixed by adding them to our vue2-auto­com­plete Vue component.

 • Error: Anchor element found with a valid href attribute, but no link content has been supplied.
   ├── WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.NoContent
   ├── #nys-footer > div > div > div > div:nth-child(2) > a:nth-child(1)
   └── <a class="social" href="mailto:info@nystudio107.com"><i class="icon-mail-alt"></i></a>

 • Error: Anchor element found with a valid href attribute, but no link content has been supplied.
   ├── WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.NoContent
   ├── #nys-footer > div > div > div > div:nth-child(2) > a:nth-child(2)
   └── <a rel="nofollow" class="social" href="https://www.facebook.com/newyorkstudio107"><i class="icon-facebook"></i></a>

 • Error: Anchor element found with a valid href attribute, but no link content has been supplied.
   ├── WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.NoContent
   ├── #nys-footer > div > div > div > div:nth-child(2) > a:nth-child(3)
   └── <a rel="nofollow" class="social" href="https://twitter.com/nystudio107"><i class="icon-twitter"></i></a>

 • Error: Anchor element found with a valid href attribute, but no link content has been supplied.
   ├── WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.NoContent
   ├── #nys-footer > div > div > div > div:nth-child(2) > a:nth-child(4)
   └── <a rel="nofollow" class="social" href="https://github.com/nystudio107"><i class="icon-github-circled">...</a>

These all refer to the social icons at the bot­tom of the screen, which are a cus­tom icon font cour­tesy of Fontel­lo (gen­er­at­ed via our Gulp task). Con­trary to pop­u­lar belief, icon fonts can work with accessibility.

We just need to change the links to be like this:

<a rel="nofollow" class="social" href="https://www.facebook.com/newyorkstudio107">
    <i class="icon-facebook" aria-hidden="true" title="Facebook"></i>
    <span class="sr-only">Facebook</span>
</a>

The aria-hidden attribute caus­es screen read­ers to skip the ele­ment, and the title attribute gives sight­ed peo­ple a mouseover tooltip. Then the <span class="sr-only"> pro­vides the text for screen read­ers to parse, using the CSS from Twit­ter Bootstrap:

.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0,0,0,0);
    border: 0;
}

That’s all there is to it, we now have nice acces­si­ble social icons.

 • Error: Iframe element requires a non-empty title attribute that identifies the frame.
   ├── WCAG2AA.Principle2.Guideline2_4.2_4_1.H64.1
   ├── #Smallchat > iframe
   └── <iframe data-reactroot="" style="z-index: 999999999; position: fixed; right: 0px; bottom: 0px; border: 0px; background-image: none; transition: width 200ms cubic-bezier(0.25, 0.25, 0.5, 1), height 200ms cubic-bezier(0.25, 0.25, 0.5, 1); -webkit-trans...

This one, unfor­tu­nate­ly, we can’t fix… because it’s com­ing from the Small­chat library we use to allow site vis­i­tors to con­tact us via Slack. How­ev­er what we can do is con­tact the authors of Small­chat, and point out the acces­si­bil­i­ty issues, so that they can fix them. Then every­one benefits.

Link Automating Accessibility Testing

The cool thing about pa11y being a Node mod­ule is that we can inte­grate it into our fron­tend work­flow, and auto­mate test­ing. Take for exam­ple this a11y Gulp task:

// Process data in an array synchronously, moving onto the n+1 item only after the nth item callback
function doSynchronousLoop(data, processData, done) {
    if (data.length > 0) {
        const loop = (data, i, processData, done) => {
            processData(data[i], i, () => {
                if (++i < data.length) {
                    loop(data, i, processData, done);
                } else {
                    done();
                }
            });
        };
        loop(data, 0, processData, done);
    } else {
        done();
    }
}

// Run pa11y accessibility tests on each template
function processAccessibility(element, i, callback) {
    const accessibilitySrc = pkg.urls.critical + element.url;
    const cliReporter = require('./node_modules/pa11y/reporter/cli.js');
    const options = {
        log: cliReporter,
        ignore:
                [
                    'notice',
                    'warning'
                ],
        };
    const test = $.pa11y(options);

    $.fancyLog("-> Checking Accessibility for URL: " + $.chalk.cyan(accessibilitySrc));
    test.run(accessibilitySrc, (error, results) => {
        cliReporter.results(results, accessibilitySrc);
        callback();
    });
}

// accessibility task
gulp.task("a11y", (callback) => {
    doSynchronousLoop(pkg.globs.critical, processAccessibility, () => {
        // all done
        callback();
    });
});

This pig­gy­backs off of the package.json set­up we out­lined in the A Bet­ter package.json for the Fron­tend arti­cle, and uses the urls we already have in the pkg.globs.critical object from the Imple­ment­ing Crit­i­cal CSS on your web­site article.

We already have a URL to each major tem­plate our site uses for our Crit­i­cal CSS gen­er­a­tion, we can use them again to run our pa11y checks on every tem­plate on our website!

For this to work, you’ll need to do npm install pa11y -D or yarn add pa11y -D to add pa11y to our pro­jec­t’s package.json, but then we’re good to go!

So now we can just do gulp a11y and it’ll test our entire web­site for acces­si­bil­i­ty issues. Nice!

Link Wrapping Up

Just use pa11y the way you use any oth­er audit­ing tool: you get a list of things to fix, you address them, and then you re-run the test. Lath­er, rinse, repeat until all of the issues you plan to address are tak­en care of.

One real­ly nice thing from a best prac­tices point of view is that if we’ve addressed SEO best prac­tices as per the Mod­ern SEO: Snake Oil vs. Sub­stance arti­cle, we’ve already tak­en care of a num­ber of acces­si­bil­i­ty issues. Both SEO and acces­si­bil­i­ty want alt tags for images, for instance. Nice.

It makes sense if you think about it; search engines are essen­tial­ly screen read­ers. But it’s great to see the con­ver­gence of tech­niques that fall under the best prac­tices umbrella.

That’s it! It was­n’t so bad at all to address these things, and we prob­a­bly made a big dif­fer­ence to some peo­ple vis­it­ing our site.

Acces­si­bil­i­ty isn’t some­thing that’s ter­ri­bly dif­fi­cult to address, espe­cial­ly if you inte­grate it as part of your design & devel­op­ment process, and use tools like pa11y to help you along the way.

As with any­thing, it’s much eas­i­er to build projects with acces­si­bil­i­ty in mind than it is to retroac­tive­ly add it. So make it part of your design & devel­op­ment process.

It’s also worth not­ing that Pa11y also has some oth­er cool projects based on it, such as Pa11y Dash­board and Pa11y Web­ser­vice for more auto­mat­ed mon­i­tor­ing of your web­site’s acces­si­bil­i­ty. If you pre­fer GUI tools, check out Chrome Acces­si­bil­i­ty Devel­op­er Tools and HTML_​Codesniffer.

Go make some inclu­sive websites!