How to code an accessible Shopify website

12 March 2020 by A11y is Everything

Last week we talked about what accessible web development is, now we need to talk about how to achieve it and specially in Shopify.

Use right HTML element for the job

The tendency for web developers to use the wrong element for the job is really prevalent in modern web development because it’s much easier to add a simple element with no associated styling and add an onClick event than add a button and have to deal with removing all the default styling like padding, background colours, borders, typeface/font-size and text-alignment.

But the button element comes with all sorts of built-in accessibility features, that while you can recreate with HTML and JavaScript, you’d be crazy to.

The same is true to the mighty select element. Designers and clients often hate the select element because it is tough to style. In 2020 we can style it in its active state however we want but we still cannot style its active state i.e. when you’ve clicked into it and the dropdown options are displayed.

Let’s take a look at all the things a select element should be able to do:

  • Type a character to jump to the first option that starts with this character
  • Type the character again to jump to the next option that starts with this character (and so forth)
  • Use the up and down arrow keys to navigate through the options
  • Use the enter key, spacebar, or Alt + Down to expand the list of options
  • Use Alt + Up to collapse the list of options
  • Collapse the options by using Tab to jump to the next focusable element
  • Use Esc to collapse the list of options

And that’s just keyboard-based behaviour. The select element also can:

  • Display as dark or light depending upon its underlying background
  • ’pop-out’ of a modal window
  • Appear above or below in its active state depending on the user’s scroll position

To recreate a select element properly from divs and spans is a huge piece of work and the likelihood is you will miss some behaviour.

Headings

Headings i.e. h1, h2, h3 etc are super important for accessiblity and SEO.

Screenreaders users can often navigate a website by its headings so having a good heading structure is paramount.

It’s hard for website editors to appreciate that a h1 does not have to be BIG and BOLD and h6 does not have to be tiny and small. The semantics of the HTML and the styling (provided by CSS) should be separate. A good way to achieve this is by having a set of font utility classes like so:

.u-font-xxl {
  font-size: 5rem;
}
.u-font-xl {
  font-size: 4rem;
}
.u-font-l {
  font-size: 3rem;
}
.u-font-m {
  font-size: 2rem;
}
.u-font-s {
  font-size: 1.6rem;
}
.u-font-xs {
  font-size: 1.4rem;
  text-transform: uppercase;
}

Then to code your HTML like so:

<div class="c-hero">
  <div class="u-font-xs">Tiny copy above big heading</div>
  <div class="u-font-xxl">Big hero heading, not a h1 as it does not need to be</div>
</div>
<div class="c-body-section">
  <h1 class="u-font-m">This heading visually does not need to be BIG</h1>
  <p>Here is some body copy.</p>
  <h2 class="u-font-s">This heading can be smaller</h2>
  <p>Here is some body copy.</p>
</div>

Notice how in the HTML, that not everything with a class of u-font-etc has to be a heading. Just because it visually looks like a heading, it doesn’t mean it should be so in the code.

Wityh Shopify, the client has access to a rich-text editor when editing page copy or product descriptions. They can add classes to <h2> or <h3> tags but it's not straightforward. The trick here is two-fold.

  1. Education
  2. A system that defines heading's typographic styles in blocks of copy created/maintained by a client

Example HTML

<h1 class="u-font-xl">{{ page.title }}</h1>
<div class="rte">
  {{ page.content }}
</div>

Where {{ page.content }}'s rendered HTML looks something like:

  <p>Body copy sits here.</p>
  <h2>A sub-heading</h2>
  <p>More body copy sits here.</p>

With CSS that looks like this:

.u-font-xl {
  font-size: 3rem;
}
.rte h2 {
  font-size: 2rem;
}
.rte h3 {
  font-size: 1.6rem;
}

As Shopify comes with Sass (or SCSS) capabilities you can do this shorthand in your CSS code:

@mixin font-xl {
  font-size: 5rem;
}
@mixin font-l {
  font-size: 4rem;
}
@mixin font-m {
  font-size: 3rem;
}
.u-font-xl,
.rte h1 {
  @include font-xl;
}
.u-font-l,
.rte h2 {
  @include font-l;
}
.u-font-m,
.rte h3 {
  @include font-m;
}

With the above code, any elements with a class of u-font-m or any <h3> tags inside an element with a class of rte will be styled the same.

This is perfect. As long as we educate the client that they should:

  • SHOULD NOT USE a <h1> in their product description or page body copy
  • SHOULD USE the correct heading structure e.g. `treta heading tag like a tree structure

Then we should be in a good position for accessibility and SEO too.

Showing and hiding elements correctly with ARIA attributes

Showing and hiding elements on the page is fraught with difficulties from an accessibility perspective.

If something is set to display:none or given the hidden attribute e.g.

<div hidden>This is hidden</div>
<div style="display:none;">So is this</div>

Then it is both visually hidden and hidden from screen readers too. It is also hidden from a keyboard-user’s tab flow.

That’s great BUT, if something is set to display:none then it cannot be animated. So if you imagine a hamburger menu, which is hidden and needs to fade/slide/bounce/swipe into view when a button is tapped. That element cannot be set to display:none, instead it needs to be hidden from screenreaders with ARIA. Like so:

<div class="hidden-element" aria-hidden="true">
  This is hidden from screenreaders.
  <a href="#">but this can still be tabbed to</a>.
</div>

In the code above, we have an issue. Screenreaders do not know about the hidden div but keyboard users might tab to it... but it would be hidden from view - which could be confusing.

So we have to hide it from the tab flow when it’s visually hidden. We do this by setting its tabindex attribute’s value to -1 via JavaScript e.g.

var hiddenElement = document.querySelector('.hidden-element');
var interactiveElements = hiddenElement.querySelectorAll('a, button');
var i = 0;
for (i = 0; i < interactiveElements.length; i++) {
  interactiveElements[i].setAttribute('tabindex', -1);
}

That code should work in IE11 thanks to the use of the old school for loop.

Note: our interactiveElements variable has been simplified to just links and buttons; in the real world, you may want to check for other interactive elements like input, select or anything with a tabindex e.g. document.querySelectorAll('a, button, input, select, textarea, [tabindex]').

Another aspect to consider is how do we inform a screenreader that the element we are toggling on/off visually has been toggled on or off? Once again, take the example of the hamburger menu: ideally, we’d have a button which we click/tap to show/hide the menu e.g.

<button type="button">Menu</button>
<nav aria-hidden="true">
  <ul>
    <li><a href="#">Home</a></li>
    <li><a href="#">About us</a></li>
    <li><a href="#">Contact us</a></li>
  </ul>
</nav>

And ideally, we’d have the button inside the nav element, but often the design does not allow for this.

To make it so that the screenreader is aware that the two elements, button and nav, are related you need to use ARIA attributes like so:

<button aria-controls="nav" aria-expanded="false" id="button" type="button">Menu</button>
<nav aria-hidden="true" aria-labelledby="button" id="nav">
  <ul>
    <li><a href="#" tabindex="-1">Home</a></li>
    <li><a href="#" tabindex="-1">About us</a></li>
    <li><a href="#" tabindex="-1">Contact us</a></li>
  </ul>
</nav>

and then when the nav is active you’d have:

<button aria-controls="nav" aria-expanded="true" id="button" type="button">Menu</button>
<nav aria-hidden="false" aria-labelledby="button" id="nav">
  <ul>
    <li><a href="#">Home</a></li>
    <li><a href="#">About us</a></li>
    <li><a href="#" >Contact us</a></li>
  </ul>
</nav>

When the hamburger nav is active the code says the button is active with aria-expanded="true" and the nav itself is shown to screenreaders with aria-hidden="false" and the links have been made available to the keyboard users by removing tabindex="-1" from them.

Not too complicated, but a little more so than just doing:

$('#button').on('click', function() {
  $('#nav').toggle();
});

Like we all used to do in 2015!

But wait, we haven’t accounted for the rest of the page. Depending upon the design, we may have completely covered the rest of the screen with our nav element or we may have placed a darkened translucent layer over everything except the active hamburger nav which implies that content is unimportant and non-interactive.

What should we do then?

Well, we need to make sure anything on the site that is visually hidden cannot be interacted with via a keyboard or read out to screenreader so most likely we are looping through all the interactive elements on the page and adding the tabindex="-1" attribute/values to them and adding aria-hidden="true" to any containing elements.

So if you take your page structure when the nav is inactive, it could look like this:

<header>
  <div>Logo</div>
  <button aria-controls="nav" aria-expanded="false" id="button" type="button">Menu</button>
</header>
<nav aria-hidden="true" aria-labelledby="button" id="nav">
  <ul >
    <li><a href="#" tabindex="-1">Home</a></li>
    <li><a href="#" tabindex="-1">About us</a></li>
    <li><a href="#" tabindex="-1">Contact us</a></li>
  </ul>
</nav>
<main>
  <h1>Page content</h1>
  <p>Body copy with a <a href="#">Link</a> <button type="button">Button</button></p>
</main>
<footer>&copy;2020 Site name</footer>

Then when the hamburger nav is active you’d need to see something like this:

<header>
  <div aria-hidden="true">Logo</div>
  <button aria-controls="nav" aria-expanded="true" id="button" type="button">Menu</button>
</header>
<nav aria-hidden="false" aria-labelledby="button" id="nav">
  <ul >
    <li><a href="#">Home</a></li>
    <li><a href="#">About us</a></li>
    <li><a href="#" >Contact us</a></li>
  </ul>
</nav>
<main aria-hidden="true">
  <h1>Page content</h1>
  <p>Body copy with a <a href="#" tabindex="-1">Link</a> <button type="button" tabindex="-1">Button</button></p>
</main>
<footer aria-hidden="true">&copy;2020 Site name</footer>

You can see how, accessible functionality is adding to our work load quite a bit.

Every single interactive element needs to have the same level of thought as we have put into the hamburger nav example above.

Testing

You have two options to test:

  1. use in-browser tools
  2. get an expert to review the site

If your aim is to officially achieve WCAG 2.1 Level AA and you work for a larger organisation or public body then getting a third party or inhouse expert to review the site is a must.

AbilityNet in the UK do a good job as do digital accessibility center, both will find errors you won’t. However, if your aim is to simply improve your site’s accessibility then the in-browser tools such as:

Are great for alerting you to errors and warnings in your website’s code.

Read more blog articles

Let’s work together…

If you have an existing Shopify/ecommerce website or thinking of starting a new Shopify store, then send us your contact details, along with some details about your project and we will get back in touch shortly to discuss your requirements.

Sound good?

Email us