The 'is it accessible?' checklist

For each component or webpage you develop please run through this 3 stage checklist:

  1. Does this work with a keyboard?
  2. Does this work with a screen-reader?
  3. Does this work if I zoom-in at both 200% and 400%?

1. Does this component work with a keyboard?

Open up the webpage where your component is and try to TAB through it on your keyboard. Elements like <a>, <button>, <input>, <select>, <textarea> should receive focus.

From a visual perspective, is it obvious which element is in focus? What if you were colourblind, would it still be obvious? What if you had low vision, would the contrast be high enough to notice the focus ring?

Note

Mac users may have to turn on a feature in System Preferences > Keyboard > Shortcuts and toggle on the 'Use keyboard navigation to move focus between controls' checkbox

Key problems may be that the focus indicator is missing or isn't on brand. Sometimes you may have to play with display: inline-block/block to get the right look for the focus indicator.

Read more about :focus vs :focus-visible

Button vs a vs div

If the element is a link (to either to a new page or an another element on the page (e.g. <a href="#footer">go to footer</a>) then it should be an <a> tag.

If the element does something e.g.

then it should be a <button>.

Read more about Links Vs Buttons

Note

Any button inside a form will submit that form unless you set its type attribute to button.

It is therefore the safest thing to do to always write button type="button" unless it is a submit button and then write button type="submit" to be clear.

Users of keyboards may expect to be able to TAB to elements like links, buttons, and form controls but when they get to a component like a dropdown list or a calendar they may expect to be able to use their arrow keys to navigate it. For someone who uses a mouse, this can be difficult to comprehend but it is worth practicing using only your keyboard to navigate and see how hard/easy it is.

Managing focus

If the user clicks a button to show a new element, you either want their focus to switch to that new element automatically OR you want the next time the users tabs or presses the down arrow (depending on how they navigate) you want the relevant element to receive focus.

If the element in question is a dialog that covers the screen then we (usually) don't want the user to be able to get to information 'outside' of that dialog. We want them to be able to easily close it by using their ESC key or by clicking outside of the element or by clicking a correctly labelled 'close' button inside it.

We don't want them to be able to access content outside of the dialog e.g. if they are tabbing we want them to only be able to tab inside the dialog and we don't want a screen-reader to read out page elements not inside the dialog.

tabindex

To programmatically allow an element (that isn't a link, button, or form control) to receive focus, you can add tabindex="0" to it (or tabIndex={0} in JSX).

To take an element out of the tab flow you would use tabindex="-1" if you wanted to prevent a keyboard user tabbing to it.

<div class="card">
  <a href="#" aria-hidden="true" tabindex="-1">
    <img src="image.jpg" alt="" />
  </a>
  <a href="#">This link is focusable</a>
</div>

In the above example, the component is a card and has an image and a link beneath. The client/business has requested that the image is clickable as well as the text below it but if we do that then the keyboard user has to tab more times than necessary to work through the site and the screen-reader user is read out too many identical links.

The fix is to hide repetitive links from both types of users with aria-hidden="true" and tabindex="-1".

Note

Note: A new attribue is coming, inert which does both of these things, but currently it is not 100% supported and Typescript does not support it either. In the near future, we will use inert.

To programmatically add focus to a element that has just been toggled to be visible it may make sense to temporarily add tabindex="0" to that component and then remove it once the component has been toggled to be invisible e.g.

<button type="button" aria-controls="div1" aria-expanded="false">
  Expanded
</button>
<div hidden id="div1">I am not expanded and cannot be focused</div>

and

<button type="button" aria-controls="div1" aria-expanded="true">
  Expanded
</button>
<div id="div1" tabindex="0">I am visible and can be focused</div>

Remember: we don't ever try to change the user's tab order by entering positive numbers into the tabindex attribute. That will confuse a user.

We use `tabindex="0" which allows the element slip into its natural position in the tab index based on its position in the DOM.

2. Does this component work with a screen-reader?

Before testing with a screen-reader, you can fix issues by getting into a different mindset. If you have a <button> that when clicked/tapped shows another element, how would a screen-reader know that the new element was now visible after the action?

ARIA is a set of attributes which give clues to screen-readers about interactivity on the page. Overuse and underuse can both be a problem. However, if in doubt, semantic HTML is better than adding ARIA attributes.

Inaccessible toggle example

<div class="button" onClick="javascript:toggleDiv();">
  Toggle hidden content
</div>
<div>My hidden content <a href="#">link</a></div>

Accessible toggle example

<button
  aria-controls="my-div"
  aria-expanded="false"
  type="button"
  onClick="{toggleDiv}"
>
  Toggle hidden content
</button>
<div hidden id="my-div">My hidden content <a href="#">link</a></div>

In the above example. The <button> is announced as a button so the screen-reader user knows it does something. It also says the element it controls is not visible because of aria-expanded="false". The hidden attribute on the div works like display: none in CSS and it hides the div from the screen-reader meaning its <a> element cannot be tabbed to or be read out by the screen-reader.

Read more about Accessible Toggling

Accessible names

All interactive elements have accessible names. For a link it is usually its own innerText e.g.

<a href="#"
  >I am the accessible name and will be read aloud to a screen-reader</a
>

but, other values can used too… like aria-label

<a
  href="#"
  aria-label="I am the accessible name and will be read aloud to a screen-reader"
>
  Click me
</a>

Note: the above example would not be good for a someone using Voice Control to navigate the website.

and sometimes we may choose to connect an element with another e.g.

<h2 id="label1">Ticket type 1</button>
<button aria-labelledby="label1" type="button">Add</button>
<h2 id="label2">Ticket type 2</button>
<button aria-labelledby="label2" type="button">Add</button>

or

<h2 id="label1">Ticket type 1</button>
<button aria-describedby="label1" type="button">Add</button>

<h2 id="label2">Ticket type 2</button>
<button aria-describedby="label2" type="button">Add</button>

In example 1, a screen-reader will read out 'Ticket type 1' for the first button but in example 2 the screen-reader will read out, 'Add (pause) Ticket type 1'.

Sometimes we will have an image or an SVG icon on the page and then we have to consider what accessible name we give that element.

If the image/icon is purely decorative then some empty alt text and/or an aria-hidden="true" attribute should be used to 'hide' the element from a screen-reader.

<img alt="" src="/decorative-image.jpg" />

or

<svg aria-hidden="true"><path d="" /></svg>

might suffice but if the image or SVG icon are conveying information then they need to make sure a screen-reader informs the user of their purpose

<a href="download.pdf" download>
  <svg aria-label="Download PDF file about foo"><path d="" /></svg>
</a>

Notice how the accessible name does not mention that this is an icon/image e.g. <svg aria-label="Download icon" />? It is unnecessary to do that.

3. Does this component work if I zoom-in at both 200% and 400%?

Users may choose to zoom-in their browser. They may go as big as 400%. There is a guideline that the screen should work down to 320px wide without horizontal scrollbars. This is the equivalent of 1280px when zoomed-in to 400%.

To test, zoom in your browser. Is everything still operational? Is everything legible? Is body text in one column?

Read more about Understanding 'reflow'

4. Bonus - Does this component work with a Windows 11 contrast theme turned on?

Windows 11 users have an accessibility featured called Contrast themes. They have 4 pre-built themes and the ability to customise them too. These can be found in Settings > Accessibility > Contrast themes.

Want to work with us?

Let’s talk about your project