Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save Harry-Harrison/d986711c95f7c9ff3c359e64c675c9d1 to your computer and use it in GitHub Desktop.
Save Harry-Harrison/d986711c95f7c9ff3c359e64c675c9d1 to your computer and use it in GitHub Desktop.
Building For Accessibility

Building for Accessibility

Series Eight aims for websites we develop to meet the accessibility standard WCAG 2.1 at a minimum of AA level. Most sites won't be certified to WCAG standards unless required to, but we should aim for at least AA where possible.

This document covers a few things to watch out for and common issues you will need to deal with whilst developing a website. It is accompanied by the Testing for Accessibility document (in-progress) which explains how we can test the accessibility of our sites using automated tools and manual processes.

To see a table of contents of this document, see this guide.


We will be hosting a workshop on accessibility building and testing for developers, in addition to one for designers. When complete the recording will also be available here.

Updates and Changes

If you have any additions, good references, changes or fixes then please start a Discussion, file an Issue, or make a Pull Request. No matter the size of your change, all are welcome. This will be a living document.

Introductuion to Accessibility

Accessibility of the web is extremely important for millions of people, providing a big moral and financial incentive to ensure the sites we produce for our clients are fully accessible to all users.

If you are sighted, without disability and in perfectly good health it can be quite difficult to conceive of how an accessible web can affect people. Consider different perspectives from your own:

  • A blind person using a screen reader to have the website described to them;
  • An older person with reduced vision who has to increase the font size to read on screens;
  • Someone who struggles with fine motor movement relying on keyboard navigation instead of a mouse;
  • A person with deuteranopia — severe red-green colour blindness — who can't tell the difference between red and green;
  • Someone who has had an accident resulting in both arms in a cast using voice dictation software to control their computer/phone;
  • A smartphone user in bright sun struggling to see the screen thanks to the glare;
  • A user with Attention deficit hyperactivity disorder, unable to focus on the site due to moving elements;
  • A short-sighted user who normally uses glasses but has just broken them and needs to order a new pair. (This is a personal anecdote, you'd be amazed how many optician websites are hard to use with poor vision)

Consider some of these viewpoints and the circumstances they are in — whether temporary, situational or permanent. It can be quite easy to assume that all of the customers of our clients are in the perfect circumstances and environments whenever they use our websites, but this is far from the truth. If you're interested in more examples like these check out Empathy Prompts.

As a developer however, we are aware that this will be a lot of information to take in — and especially if you are unfamiliar with it — may be hard. The most important thing however is that while we develop websites we try our best for great accessibility, just as we would be for performance and code consistency.

We have a great development team with a diverse set of skills, many of whom have experience with accessibility. Feel free to ask individuals or the #dev-team channel within Slack if you need any advice, someone to look at your code, or help with accessibility testing. We would all be more than happy to help.


  • WCAG — Web Content Accessibility Guidelines. Common standard of web accessibility used internationally since 1999. Maintained by W3C, the current latest standard is 2.2 and shall be referenced throughout this document. Many countries base their local guidelines on WCAG including the ADA (American Disabilities Act).
  • Screen Reader — Although there is a lot of different accessibility tooling, screen readers are one of the most common tools and are a good benchmark for us. Screen readers are aimed at visually-impaired users that use text-to-speech to 'read' the content out loud. When I refer to screen readers, I really mean all accessibility tooling.
  • Semantic Elements — Elements that have instrinsic meaning like main, button or h1. These are opposed to elements without intrinsic meaning like div and span.
  • Certification — A process of extremely detailed and extensive testing of a site's accessibility to confirm it is 100% compliant with WCAG etc guidelines. Certification is not required nor desired for most sites as it is very time consuming, but may be needed for public sector organisations. This would likely be handled by the most experienced accessibility dev on the team or an external consultant.

Page Structure and Semantics

Structuring your HTML in an organised way using semantic elements makes a big difference to the accessibility of the document. Browsers and accessibility tooling—including screen readers—rely on a well-structured document in order to be meaningful. HTML5 added a load of new elements that help us do this without additional code.

Document Structure

  • Each page should have just one main element for the 'content' for that page. This has an id and tabindex="-1" so it can be focused.
  • Repeatable content at the top of the page is within a header element. This may include the logo, navigation, contact buttons.
  • Standalone pieces of content that make sense alone like a blog post card use an article element.
  • Repeatable content at the bottom of the page like call to action or colophon are in a footer element.
Explanation and example of Document Outline

A good example of a well-structured document is the following, using semantic elements. This provides screen readers with an outline of how the document is built and allows for skipping to the part they're interested in.

                <li><a href="/">Home</a></li>
                <li><a href="/">About</a></li>

        <h1>About Us</h1>

        Copyright 2021

This would translate into roughly the following structure for a screen reader:

header - "Banner"
		unordered list - "2 items"
			link - Home
			link - About
main — "Main Content"
	h1 - "About Us"

By using the outline, screen reader users can get an idea of the structure of the document and skip to the part of the page that is most relevant to them, without having to listen to the entire page.

Further Reading on Page Structure and Semantics

Skip Link

A 'Skip Link' should be one of the first elements in the body of your page. A skip link allows for less technical screen-reader and keyboard users to 'skip' over repetitive content like the navigation to the content of the page.

To get a very rough idea for how helpful this could be to a user, open a site and read every item aloud in the header and any areas prefacing the main content of a page. For each list the reader will announce "Unordered list, X items" too. Although some advanced screen-reader users can increase the speed of the naration, this can mean it can take a matter of minutes per-page to get to the actual content for every page visited.

Here is a basic skip link implementation for reference:

    <a class="skip-link" href="#main">Skip to content</a>
    <main id="main">
.skip-link {
  // Core functionality
  position: absolute;
  left: 50%;
  z-index: 11;
  transform: translateX(-50%);
  &:not(:focus) {
    @apply sr-only;
  // Optional presentation
  background: #000;
  color: #fff;
  font-weight: bold;
  border-radius: .5rem;
  padding: .75rem .5rem;

Further Reading on Skip Links


  • Navigation links are within a nav element.
  • Where possible use ul elements for lists of navigation links.
  • With multiple navs on a single page add unique aria-label attributes to make them distinct.
  • Do not use role="menu" or role="menuitem" for navigation.
  • The current page or link has aria-current="page" to make the current location in the site clear.
  • Submenus appear immediately after their parents in the DOM, for focus or clickable menus.

For collapsible menus (eg, burger menu or clickable 'dropdown'):

  • Use aria-expanded to communicate whether the menu is open or closed.
  • aria-controls is used to link the trigger element and menu container.
  • button element is used for the trigger.
  • The Esc key closes the menu and re-focuses the trigger.

Further Reading on Navigation


  • Use h1h6 tags to mark up headings and communicate page structure.
  • Include only one h1, describing the main topic of the page.
  • Heading levels descend from h1 down to h6 and are always used in sequence. Never skip a heading level, as this will break the document structure.
  • Every unique section of content should have a heading, otherwise it will be grouped under the heading of a previous section. If the design doesn't have one you can use a .sr-only class to hide the title visually.
  • Headings should precede any related content like images in the DOM. If design requires use flex/grid order property to re-order.
Example of headings in the document outline

As shown in the example below, headings also contribute to the document structure:

	<h1>About Us</h1>
	<p>Intro Content</p>
	<h2>Our Values</h2>
	<h3>Work Together</h3>
	<h3>Follow Through</h3>
	<h2>Our Mission</h2>
	<p>Lorem Ipsum</p>
main — "Main Content"
	h1 — heading level 1 "About Us"
		h2 — heading level 2 "Our Values"
			h3 — heading level 3 "Work Together"
			h3 — heading level 3 "Follow Through"
		h2 — heading level 2 "Our Mission"

Heading elements as illustrated above contribute to the document outline. What's important to note is that every time you go 'down' a heading level (eg h2 to h3) it sits underneath the previous one. This is why there are multiple heading levels within HTML, so content can be in a hierarchy with multiple titles.

This is something that is fairly instinctive to us when we view the design and see a big header with a slightly smaller one below. Crucially though, this is a strict structure based on the levels of heading we use and will have ramifications if we don't consider it when developing.

Further Reading on Headings

Interactive elements

I know that <button> elements can sometimes be a pain to style, and it's so easy to add an click handler to a <div> and be done with it right? NO—DON'T DO IT!

There are only a handful of elements in HTML that the browser considers interactive so if you need something triggerable you should use one of these elements. By doing so you'll be getting keyboard focus, activating on Enter and Space and screen reader support for free rather than having to implement these yourself.

This is all interactive HTML elements and their purposes. When implementing consider which is most appropriate for your use.

Element Purpose
button Interactive on-page actions that aren't better suited to a different element.
a Navigations to a new page or to shift focus to a new portion of the same page.
summary The trigger for details/summary disclosure widgets.
input, select, textarea Forms and user input.

All interactive elements should also have accessible text using text, .sr-only or aria-label. This is particularly important for icon buttons or links.

Further Reading on Interactive Elements


The destination of links should be clear from the link text alone - without any context. Screen readers have access to a 'link mode' that gives a list of all links on a page without surrounding text. This would make a "Learn More" link useless, so consider putting the link on a title or provide more context in accessible text using .sr-only or aria-label.

Links should not open in a new tab or window. If required, it should be clear to both screen-reader and sighted users — for example using a visual icon with alt text or including "(opens in new tab)" in the link text.

Links should not wrap large amounts of content. When encountering a link, most screen readers will announce all of the link text and then announce it is a link. Rather than wrapping an entire block with an a element to make it all clickable, implement the box link pattern:

<!-- element that should be clickable is position relative -->
<article class="relative">
	<!-- here we link just the title to be concise and that's where users expect it -->
		<a class="box-link" href="/example/">Example Box Link</a>
	<img src="..." alt="">
	<p>blah blah blah...</p>
/* add a pseudo element that is position absolutely to the bounds of the 'wrapper' */
.box-link::before {
	content: '';
	position: absolute;
	top: 0;
	right: 0;
	bottom: 0;
	left: 0;

Further Reading on Links


  • All form fields have an associated label. This should be a constantly-visible label for both screen-reader and sighted users. Where not possible, consider using a 'floating label' pattern or as a last resort uses .sr-only to provide the label for only screen-reader users.
  • label elements are associated with form elements using the for attribute. Dictation software doesn't support 'implicit' labelling, where the form element is nested within the label.
  • Use native browser validation where possible including required attribute.

Error Messages:

  • Error messages should be clear and descriptive.
  • Dynamically-added error messages should be focused on being added and have aria-live="polite" so they are announced to screen-readers.
  • Error messages that are linked to a specific form element use aria-describedby on the form element.

Further Reading on Forms


Often lists are over-used, when implementing a ul consider whether it really adds to the experience for a screen reader. A screen reader will announce something like this:

"Unordered list, 2 items. Start of List. First item: 'Lorem ipsum'. Second item: 'Dolor sit amet'. End of List.

Now scale that up to a sites navigation that may have multiple levels and you can imagine how helpful the Skip Link paradigm can be.

Useful: A bulleted list; Navigation links; List of products/blog posts.
Less useful: A carousel without a common theme; Set of links with no relation.

When using a ul element and list-style-type: none, add the role="list" attribute to the ul. This is to work around a Safari 'optimisation' where list-style-type: none also removes the list semantics.

Further Reading on Lists

Mouse-less/Keyboard User Navigation

  • Every interactive element should be focusable.
  • Focused elements are visible on screen and accessibly.
  • Every interactive element has a focus outline (via :focus, :focus-visible, :focus-within).
  • If you can open a modal/dialog, the content should be focused immediately. If it's a dialog that requires interaction, focus should be trapped within it. When closing the dialog, focus should return to where it was before opening.
  • Don't implement infinite scrolling.
  • Focus order should match the visual order going top to bottom, left to right. If focus indicators are obvious enough there can be some slight differences.
  • Consider creating your own focus outline matching the site's style that is more visible than browser defaults.

Further Reading on Keyboard Navigation



All img elements MUST have an alt attribute — without this screen-reader may announces the URL of the image. For entirely decorative images you can leave this attribute blank (alt=""), but it must always be present.

Writing good alternative text is quite difficult, but it is extremely important to do. We should also always provide a way to add alt text within the CMS and educate our clients on how to write good alt text.

If you have the ability to add an instruction to an alt text field, the following may suit your needs.

Users using screen readers will be read the alt text to better understand an on-page image.
It should describe what is in the image, not that this is an image.

Guides on writing alt text

For svg elements inline within HTML add the role="img" attribute to declare it as an image. If the image is decorative you can then add aria-hidden="true", or add alternative text within aria-label.

Further Reading on Images

Video and Audio

  • Auto-playing content should be muted, some browsers wont play content unless this is the case.
  • Video should not auto-play if prefers-reduced-motion is reduce.
  • Video and audio content should always be pausable.
  • Where possible closed captions or transcripts are available. If not possible the key content should be available in a text format.

Further Reading on Video and Audio

Colour Contrast

It is important that there is enough contrast between colours used and that colour is never solely used to convey any information. There are many different colour-related vestibular disorders but by ensuring there is enough difference in contrast and that we use other methods to convey information we don't need to test for every single one.

Designers are aware of colour contrast requirements and should consider it in their designs. If you find any instances of low contrast, or a client asks for colour changes that are inaccessible, let the design team know and they can make necessary tweaks.

WCAG 2.2 AA compliance requires:

  • Normal Text — Text smaller than 24px should have a contrast ratio of 4.5:1 against the background.
  • Large Text — Text that is 24px or larger should have a contrast ratio of 3:1 against the background.
  • Icons — Icons should have a contrast ratio of 3:1.
  • Colour shouldn't be used as the only indicator of information, consider also adding an outline, underline or movement.

You can check colour contrasts within the Chrome and Firefox devtools but you can also use tools like Tota11y to check in-browser or tools like colourcontrast.css that allow you to input foreground/background colours.

Further Reading on Colour Contrast

Dynamic Components

When building or including custom JavaScript components like accordions, carousels or tabs, it is your reponsibility as a developer to ensure the implementation remains accessible. Using native elements like button helps, but you will often need to convey state and link together elements to make intentions clear - particularly to visually impaired users.

Making custom components accessible can be pretty tricky, so when building a custom component consider having a look around for guidance/already accessible implementations. I suggest looking up implementations within the Series Eight developer docs, referencing sources like Inclusive Components or searching online.

It's also worth testing when the component is complete rather than waiting until the site is almost finished.


WCAG provideds ARIA to help with making dynamic JS components easier to understand for screen-reader users. ARIA is implemented as attributes on HTML elements starting aria- and the role attribute. There are a few common 'rules' for using ARIA:

  1. Don't use ARIA unless you have to — Native HTML will be a lot easier and better supported: <input type="checkbox"> vs <div role="checkbox" aria-checked="true" tabindex="0">.
  2. Don't change native sematics unless you really, really have to — Changing element roles is generally a bad idea unless you test extensively. For a heading that's also a button <h2><button>heading button</button></h2> is a lot better than <h2 role="button">heading button</h2>.
  3. All interative ARIA controls must be usable with a keyboard — ARIA doesn't affect keyboard functionality but the users who rely on it will expect certain keyboard controls.
  4. Do not use role="presentation" or aria-hidden="true" on a focusable element — Users will get confused if their keyboard focus is on an element that doesn't exist to them.
  5. All interactive elements must have an accessible name — See Interactive Elements.

Further Reading on WAI-ARIA

Media Queries

There are a couple accessibility-related media queries within CSS and JS that we can use to detect the user's preferences and make appropriate changes to the site. These are exposed to the user via the preferences/options of their browser or OS. As more Browser vendors take notice and OS's evolve, more queries are being added.


This media query is extremely important, and is often set by users who feel nausea or pain to uncontrolled motion on screens. It's also beneficial to users with ADHD. Examples of this would be a lot of animation, animation-on-scroll effects or parallax. But can also extend to "Back to top" links that smooth scroll the user.

You can implement it within CSS and JavaScript like so:

@media (prefers-reduced-motion: reduce) {
  .element {
    animation: none;
let mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
if (mediaQuery && mediaQuery.matches) {
  // it's safe to run animations

This doesn't necessarily mean no motion, there are cases when an interface makes a lot more sense or is more usable with slight user-controlled movement. If you don't have cases like this you could consider adding an animation and transition 'nuke' to make it easier to manage:

@media (prefers-reduced-motion: reduce) {
  html:focus-within {
    scroll-behavior: auto;

  *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
    transition-duration: .01ms !important;

Further Reading on prefers-reduced-motion


This media query reflects the Users preference for a light or dark colour scheme. Due to the amount of effort required by design and development it's unlikely you will be implementing/using this unless fixing a specific color-scheme bug or as a client request.

For a designer this will mean making and maintaining an entirely new colour scheme for the site that is light or dark. For the developer you will need to maintain two sets of colours that can be changed on the client - likely using custom properties. You would also need to implement a 'dark mode toggle' that allows users to switch between these modes independently of the browser/OS settings.

For testing or your personal use, here is a quick CSS trick for a dark mode.

body {
  background: black;

body, img {
  filter: invert(1) hue-rotate(180deg);

Further Reading on prefers-color-scheme

Adaptable Content

Relative Units

Browsers and OS' have options for font-size, allowing the user to increase the normal size of text across the interface and in webpages. This is particularly commonly used by iOS users, and is different to zooming in on a webpage with a gesture. Many users with vision impairments like presbyopia (where many peoples vision starts to deteriorate around their 40s-50s) rely on an increased text size.

We can support these changes by using relative sizing units like rem, em and % rather than px. By using relative units the interface can scale with font-sizes easily and without further development.

We should hence default to using relative units and only use absolute units like px where required and carefully considered. A couple examples where using absolute units makes sense:

  • Decorative elements — It doesn't necessarily make sense for a border-radius for example to increase with the font-size;
  • Borders — A 1px border we may want to always be 1 pixel wide no matter the font size.

It is generally a good idea to use relative units wherever possible, this applies to other properties like line height and letter spacing. Using the unitless line height and using em for letter spacing allows for it to scale with font size. That's useful for both simplifying the number of declarations developers need to make, and also for maintaining legibility when the user changes font size.

Further Reading on Relative Units

Viewport Sizing

You should build for a minimum viewport width of 320px. This is a commonly agreed minimum to accomodate for smaller devices like small smartphones which are far more frequent among lower-end devices and includes the first generation iPhone SE. This 320px is also accepted to be the minimum that device makers should use to ensure maximum compatibility with existing sites - this includes devices like the Apple Watch.

Further Reading on Viewport Sizing

Viewport Zooming

The user - regardless of device - should be able to zoom into the page to at least 200% whist remaining fully functional with all content visible.

  • <meta name="viewport"> element doesn't include user-scalable=no;
  • <meta name="viewport"> element doesn't specify maximum-scale with a value less than 2;
  • You have built your site resiliant to the text size and zoom level changing.

For most cases <meta name="viewport" content="width=device-width, initial-scale=1.0"> should be all you need

Further Reading on Viewport Zooming

Touch Targets

To ensure interactive elements (or "targets") are large anough to be triggered, try to ensure that interactive controls are at least 44 by 44 pixels in size. This assists touch screen users with their big fingers and mouse users who may not be as precise due to cirumstance, motor issues or simply missing.

Links within blocks of text are exempt from this however. In other cases, adding "invisible padding" around targets that are presented as just text may be beneficial.

Further Reading on Touch Targets

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment