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.
- Education
- 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>©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">©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:
- use in-browser tools
- 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.