Build Design Systems With Penpot Components
Penpot's new component system for building scalable design systems, emphasizing designer-developer collaboration.

Andrew Welch
·
Insights · #frontend #accessibility #best-practices
Making the web better one site at a time, with a focus on performance, usability & SEO
Making your website accessible means making it available to the largest number of people possible. It’s already a Herculean task to get people to your website to begin with, so we certainly don’t want to turn a % of people away due to accessibility issues.
I find that many developers don’t really understand accessibility, or shuffle it off to the “it’d be nice” list on their tasks. With a little education and proper tooling, it doesn’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 countries, website accessibility is also mandated by the law. In the USA, for instance, there is the Americans with Disabilities Act (ADA), which the DOJ has made clear extends to the Internet.
Companies such as Target (see NFB V. Target) and Netflix (see NAD V. Netflix) have been the subject of lawsuits based on this; but it’s also just good business to allow as many people as possible access to your company’s product or service.
If you plan on working on a website for US Federal or State governments (which includes educational institutions), the Website Accessibility Under Title II of the ADA document outlines the accessibility mandates for websites. Even public companies may very well need to comply with the ADA; check out the Does your website need to be ADA compliant? article for details.
I’m just going to assume that you want to make your website accessible for the practical reasons mentioned above that go beyond legal compliance. Like many of the topics that I’ve discussed before, this is all about making the web better.
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 accessibility to our clients, similar to how we talked about selling performance in the A Pretty Website Isn’t Enough article. We should build accessibility into our proposals, and integrate it into our design & development workflow. Educate clients on why it is important, and they’ll quickly get on board.
The Personas for Accessible UX article is a super-useful resource for understanding the type of disabilities people may have, and how that impacts their ability to access your website. Take the time to read through the personas listed there, and come back when you’re done.
Really, it’s worth it. I’ll wait.
Link
Major Use Cases
So here’s a non-exhaustive synopsis of the disabilities that people may encounter with your website (cribbed from here):
In general, it’s estimated that 12-20% of people have a disability of some kind.
So let’s do something about it!
Link
Pa11y as Tooling for Accessibility
Actually making websites accessible isn’t that hard; the hard part is knowing what you should be doing. While there are some web-based tools out there, I’ve found that the npm package Pa11y to be the most up to date and effective tool for me.
Pa11y is just a CLI layer on top of HTML_CodeSniffer, which enforces the three conformance levels of the Web Content Accessibility Guidelines (WCAG) 2.0, and the web-related components of the U.S.“Section 508” legislation.
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 accessibility; if you want to do a deep-dive on accessibility, check out the Getting Started with Web Accessibility & Using Aria resources. But if you use semantic HTML5 elements, use a proper heading hierarchy, and use alt attributes for images, you’re halfway home.
For the rest of it, pa11y gives you a very easy to understand list of what’s broken, and how to fix it. It’s very similar to using the WC3 Validator to check your HTML for correctness, so it’s not hard to get going on a bullet-pointed list of things to fix.
You will need to have Node & NPM (or Yarn) installed in order to take advantage of it, but hopefully you’re already up to speed using these tools. If you’re not, definitely consider getting on the bandwagon, because frontend development is moving inexorably towards them.
So the first thing we’ll do is install a package called npx; this lets you run node modules without having to install them (either globally or as part of a package), either via npm or yarn:
We’re installing the npx package globally, so we’re using sudo to make it happen; but you can also consider checking out how to install packages globally without sudo if you choose to, or perhaps don’t have sudo access.
Of course, if you like you can add pa11y as a dependency in your project as well, if you prefer to do it that way. You could even install pa11y globally, the same way we did with npx. But I wanted to introduce using npx as a general way to execute Node modules without having to install them.
npx also has a specific benefit in this case: it’ll make sure we’re always running the latest pa11y with the latest rulesets, without having to keep it up to date.
Link
Fixing Accessibility Problems
So now that we have npx installed, we can run pa11y and gets its evaluation of the accessibility of our website! We’ll use the blog page of this very website as our guinea pig for accessibility 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 comprehensive check, you can show the warnings too, by omitting it from the command.
Out of the box, pa11y supports 4 accessibility standards: Section508, WCAG2A, WCAG2AA, WCAG2AAA. The default is WCAG2AA, but you can specify any standard you want via the –standard command line argument.
All of the WCAG2* standards are the same international standard, but with different levels of strictness (A being the least strict, AAA being the most strict). Additional information on WCAG levels can be found in Understanding Levels of Conformance.
The Section508 standard is for US Federal & State government institutions, should you be working on websites for them.
But we’re just going to use the default WCAG2AA standard, which is middle of the road in terms of strictness (and indeed is the standard that WCAG suggests to use), and is used by default if we don’t specify a –standard.
Here’s what the output 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:[email protected]"><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 actually does is pretty nifty: it fires up a “headless” browser, renders your webpage in it courtesy of PhantomJS, and then analyzes it for accessibility issues. This is pretty similar to how Critical CSS is generated, as per the Implementing Critical CSS on your website article.
In any event, that’s quite a number 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 displayed inline in the global site header, so there’s some text for screen readers to parse. Link targets for screen readers should say where the link will take them, or what clicking on the link will do, not describe what the thing inside the link is (a logo, in this case).
Just imagine someone reading the webpage aloud to you, if they said “link nystudio107 logo” you’d have no idea what clicking on it does. If instead they said “link homepage” then you’d get it.
Another way that this issue can also be resolved is by putting text inside of the <a> tag that is invisible to the eye, but screen readers will pick up. We can do this with a <span class=”sr-only”> using the CSS from Twitter 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 readers will have something 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 saying that the contrast ratio between our menu links and the header background color isn’t high enough. It suggests changing it from #58595b to #3C3D3F to increase the contrast ratio. This is to assist with readabilty for people who have vision problems.
Since these are the nystudio107 branding colors, the problem could have been mitigated by applying the tips from the article Tips on Designing for Web Accessibility during 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 saying that our <input> element used for site search lacks a aria-label attribute for screen readers. This can be fixed by adding them to our vue2-autocomplete 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:[email protected]"><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>
<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 causes screen readers to skip the element, and the title attribute gives sighted people a mouseover tooltip. Then the <span class=”sr-only”> provides the text for screen readers to parse, using the CSS from Twitter 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 accessible 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, unfortunately, we can’t fix… because it’s coming from the Smallchat library we use to allow site visitors to contact us via Slack. However what we can do is contact the authors of Smallchat, and point out the accessibility issues, so that they can fix them. Then everyone benefits.
Then you just pa11y the way you use any other auditing tool: you get a list of things to fix, you address them, and then you re-run the test. Lather, rinse, repeat until all of the issues you plan to address are taken care of.
One really nice thing from a best practices point of view is that if we’ve addressed SEO best practices as per the Modern SEO: Snake Oil vs. Substance article, we’ve already taken care of a number of accessibility issues. Both SEO and accessibility want alt tags for images, for instance. Nice.
It makes sense if you think about it; search engines are essentially screen readers. But it’s great to see the convergence of techniques that fall under the best practices umbrella.
That’s it! It wasn’t so bad at all to address these things, and we probably made a big difference to some people visiting our site.
Accessibility isn’t something that’s terribly difficult to address, especially if you integrate it as part of your design & development process, and use tools like pa11y to help you along the way.
As with anything, it’s much easier to build projects with accessibility in mind than it is to retroactively add it. So make it part of your design & development process.
It’s also worth noting that Pa11y also has some other cool projects based on it, such as Pa11y Dashboard and Pa11y Webservice for more automated monitoring of your website’s accessibility. If you prefer GUI tools, check out Chrome Accessibility Developer Tools and HTML_Codesniffer.
Go make some inclusive websites!
AI-driven updates, curated by humans and hand-edited for the Prototypr community