Introduction
In this article, we will be discussing the topics of Semantic Markup, ARIA, performance, solving common issues for users and finally we will be implementing some of the topics discussed in the previous design article.
Semantic Markup
"Semantics is the study of the meanings of words and phrases in a language. Semantic elements = elements with a meaning." - W3Schools HTML5 Semantic Elements
Semantics give us a way to provide meaning to the content we produce, a simple example would be a blog post layout.
Perhaps you would write something like the following:
...
<div class="container">
<h1>My awesome post</h1>
<p>Quis possimus soluta officia accusantium unde perferendis.</p>
<p>Lorem, ipsum dolor sit amet consectetur adipisicing elit.</p>
</div>
...
The issue here is that visually you would have the appearance you are looking for but what about a user with vision issues? Well their screen reader would tell them that the h1
and p
tags were in a "group" but this means nothing, a group can be anything.
A better way is to use a semantic grouping element. In this case, the best suited element for the job is the article
tag.
...
<article class="container">
<h1>My awesome post</h1>
<p>Quis possimus soluta officia accusantium unde perferendis.</p>
<p>Lorem, ipsum dolor sit amet consectetur adipisicing elit.</p>
</article>
...
Visually nothing will change as an article is merely a block level element stylistically. Semantically however this will tell the user accessing the information via assistive technology they are within an article of self contained information which helps provide context and understanding.
The following are counted as semantic grouping elements:
Tag | Description |
---|---|
article | Defines an article (item) of self describing content |
aside | Defines content aside from the page content |
details | Defines additional details that the user can view or hide |
figcaption | Defines a caption for a figure element |
figure | Specifies self-contained content, like illustrations, diagrams, photos, code listings, etc. |
footer | Defines a footer for a document or section |
header | Specifies a header for a document or section |
main | Specifies the main content of a document |
mark | Defines marked/highlighted text |
nav | Defines navigation links |
section | Defines a section in a document |
summary | Defines a visible heading for a details element |
time | Defines a date/time |
Semantics are very important and if you work on the frontend and know about semantics and their value but don't push them as a topic. Worse yet, if you willingly choose not to implement them. You really should take a step back to reconsider a few things. By doing so, you do your users, potential users and yourself an injustice over something that is so simple to implement. If you merely take the time to think, justify, structure and implement while accepting constraint where necessary, you will be able to provide the best possible experience for all users. That to me is a foundation of accessibility.
This would also be how you achieve a large chunk of the "Provide comparable experience" accessible design principle.
DOM structure
The structure of your markup is as important as the semantics. What the user visually sees should so be generally how the markup is ordered as this is how users using assistive technology will interpret the page.
Good quality markup using as little tags as possible while adhering to semantics and structure will always provide a better experience for users and developers alike.
Performance
A large topic in development in general is that of performance.
This word can mean many things to many people but in terms of accessibility, we can move it to mean merely "how fast is this and how can it be faster?".
This may not seem like a logical part of accessibility but users on slow connections, low end devices and low data budgets have a barrier to entry and thus performance becomes a topic of access in this regard.
There are some really nice articles on accessibility in conjunction with performance such as:
- Connection aware performance by Max Böck
- Accessibility and performance by Marcy Sutton
- Visualising web accessibility performance to raise awareness and understanding amongst product stakeholders by Caroline Lindström
These are merely a few of my favourites but theres so much more out there.
In short though, consider performance, especially of frontend content delivery and optimisation. Remember that one size solutions fit no one and nothing, each issue is unique 9 times out of 10 and should be treated as such. Higher performing frontends deliver higher retention rates, lower bounce rates and higher user satisfaction and thus, this is a very important topic and a good business case for the topic also.
Common issues
Animation
Animations are a great way to implement some playfulness or provide further context that a user action was successful or at the least registered, etc. This however is also a topic which can be problematic for users with certain health issues such as epilepsy for example.
Animation if done right is a huge enhancement to a sites experience and emotional resonance for many users but for certain users, they can cause serious issues in extreme cases. This is why we should allow users to control how animations are provided to them and respect user choices as we would or more aptly, should, in any other feature.
Enter prefers-reduced-motion
. This is an operating system level setting which we can plug into via the aforementioned css property, it allows a user to say that they prefer reduced amounts of animation and then exposes this setting and its chosen value to us to detect in our stylesheets.
Here is a video showing it in action:
As you can see, this is instantly changed when the setting is turned on, this is super powerful and we can implement it very easily. For example:
@keyframes moveRight {
from {
transform: translateX(0);
}
to {
transform: translateX(100%);
}
}
.some-element-to-animate {
animation: moveRight 1s;
}
@media (prefers-reduced-motion: reduce) {
.some-element-to-animate{
/* Stop the animation */
animation: none;
/* Provide alternative styles if you like */
...
}
}
We have 3 options for the @supports
feature queries, these are:
/* Applies styles when Reduced Motion is enabled */
@media screen and (prefers-reduced-motion: reduce) { }
/* Also applies styles when Reduced Motion is enabled */
@media screen and (prefers-reduced-motion) { }
/* Applies styles when the user has made no preference known */
@media screen and (prefers-reduced-motion: no-preference) { }
If you really want to be super specific, the example above could be changed to the following:
@keyframes moveRight {
from {
transform: translateX(0);
}
to {
transform: translateX(100%);
}
}
@media screen and (prefers-reduced-motion: no-preference) {
.some-element-to-animate {
animation: moveRight 1s;
}
}
@media (prefers-reduced-motion: reduce) {
.some-element-to-animate{
/* Stop the animation */
animation: none;
/* Provide alternative styles if you like */
...
}
}
This explicitly only runs the animation if no preference has been stated but until there is a key for something like prefers-reduced-motion: allow-animation
or something to that effect, I would just use the first implementations way instead of the double media query, simply because prefers-reduced-motion: no-preference
is not the same as "they probably want and/or are ok with animations" and if we are to assume that to be the case anyway, the cascade is a simpler option than a second media query to do the same thing effectively.
Please note: Sadly (https://caniuse.com/#search=prefers-reduced-motion)[prefers-reduced-motion has relatively little support currently] but is fully supported in all macOS and iOS devices and is under development for the next version of Firefox.
This is a perfect feature to implement as a progressive enhancement and only by using features will other browsers take heed of our interest in pushing specifications through. This is a great feature which will enhance alot of peoples experiences, so go forth and enhance all the things!
Moving forward from the design article
If you haven't read the design article, I highly recommend you read it as this will be following on from there, I will aim to give a refresher in each section over what we discussed but it is important to understand the foundational aspects of each area in this section, so if you haven't read the previous article, I highly recommend you to do so.
Font sizing
Refresher
"For many mainstream body text fonts, 14 and 18 point is roughly equivalent to 18.75px and 24px or 1.2 and 1.5 em or to 120% or 150% of the default size for body text (assuming that the body font is 100%), but authors would need to check this for the particular fonts in use." - Steve Faulkner
If we take the example of small text, which is not covered in the above quote but is normally in the user agent style sheet a value of between 80% and 85% of the containing elements font-size. Which if the normal body text has a font-size of 18.75px, we can extrapolate a small text base value of 16px.
Size | Pixel value |
---|---|
Small text | 16 |
Body text | 18.75 |
Large text | 24 |
Generating rems from pixel values
First of all, lets create a rem-calc
function similar to that of teh foundation framework. This will allow us to provide a pixel value and receive a rem value in return.
The function would look like so:
/**
* @function rem-calc
*
* @example - SCSS usage
*
* p {
* font-size: rem-calc(18.75);
* }
*
* @example - CSS output
*
* p {
* font-size: 1.171875rem;
* }
*
* @param {int} $size - desired font size in px
* @param {int} $base [16] - base font size in px
* @return string - rem value representation of the desired px input
*/
@function rem-calc($size, $base: 16) {
$remSize: $size / $base;
@return #{$remSize}rem;
}
Ok, so lets break this one down. First we have a default base font size of 16, this is due to the assumption of the body font size being set to 100%. To ensure this, we would add the following line somewhere in our scss:
body {
font-size: 100%;
}
Then it's as simple as calling the function anywhere we like, for example:
// body text
main {
font-size: rem-calc(18.75);
}
// small text
small {
font-size: rem-calc(16);
}
// large text
.text-large {
font-size: rem-calc(24);
}
And now we have everything setup to meet the minimum requirements outlined above, pretty sweet, huh?
We can even reverse the process can get px back by reversing the calculation like so:
/**
* @function px-calc
*
* @example - SCSS usage
*
* p {
* font-size: px-calc(1.171875);
* }
*
* @example - CSS output
*
* p {
* font-size: 18.75px;
* }
*
* @param {int} $size - desired font size from rem
* @param {int} $base [16] - base font size in px
* @return string - px value representation of the desired rem input
*/
@function px-calc($size, $base: 16) {
$pxSize: $size * $base;
@return #{$pxSize}px;
}
I don't personally know of any use case for this but the point is that you can do it.
Colour contrast
Refresher
"The visual presentation of text and images of text has a contrast ratio of at least 4.5:1, except for the following:
Large Text
Large-scale text and images of large-scale text have a contrast ratio of at least 3:1;
Incidental
Text or images of text that are part of an inactive user interface component, that are pure decoration, that are not visible to anyone, or that are part of a picture that contains significant other visual content, have no contrast requirement.
Logotypes
Text that is part of a logo or brand name has no contrast requirement." - WCAG Success Criterion 1.4.3 - Colour contrast
Using out definitions of normal and large font sizing that we detailed in the Font sizing section of this article, we can now apply the following contrast rules to attain an accessible contrast ratio.
WCAG 2.0 level | Size | Contrast Ratio |
---|---|---|
AAA | normal | 7:1 |
AAA | Large, normal bold | 4.5:1 |
AA | normal | 4.5:1 |
AA | Large, normal bold | 3:1 |
Generating the best contrast possible
For developers, colour theory, contrast ratios and much more, can be difficult to fathom at times, especially if your focus is not on the frontend.
Sometimes also, designers are hindered by company guidelines that disallow them to get the best possible ratios through the design process, however, we as developers can try and give the best ratio nevertheless and that is where one of the most useful mixins I have ever found comes into play.
So, we have a button, its got a background colour value of #f44242
and we need to provide an accessible, or at the least an "as accessible as possible" colour to contrast with it. How can we achieve this?
Well, this is the mixin that rescues any thought of that:
/**
* @mixin text-contrast
*
* @example - SCSS usage
*
* body {
* background-color: black;
* @include text-contrast(black);
* }
*
* @example - CSS output
*
* body {
* background-color: black;
* color: white;
* }
*
* @param {colour} $colourToContrast - the colour value to contrast against, this can be any valid css colour value whether a named value, an rgba() value, a hex value, etc
*/
@mixin text-contrast($colourToContrast) {
$color-brightness:
round((red($colourToContrast) * 299) + (green($colourToContrast) * 587) + (blue($colourToContrast) * 114) / 1000);
$light-color:
round((red(white) * 299) + (green(white) * 587) + (blue(white) * 114) / 1000);
@if abs($color-brightness) < ($light-color / 2){
color: white;
} @else {
color: black;
}
}
Now, this may seem like there is a bit of magician work going but actually it is an algorithm set out by the W3C being put into action.
"Color brightness is determined by the following formula:
((Red value X 299) + (Green value X 587) + (Blue value X 114)) / 1000
Note: This algorithm is taken from a formula for converting RGB values to YIQ values. This brightness value gives a perceived brightness for a color." - W3C Techniques For Accessibility Evaluation And Repair Tools
Lets say for argument sake we wanted to contrast text on a background of forestgreen
. In this case, the whole mixin can be broken down as so:
/*
*
* @example
*
* .green-section {
* background-color: forestgreen;
* @include text-contrast(forestgreen);
* }
*
* becomes:
*/
@mixin text-contrast(forestgreen) {
/*
* forestgreen RGB values:
*
* Red: 1
* Green: 68
* Blue: 33
*/
$color-brightness: round(
(red(forestgreen) * 299) +
(green(forestgreen) * 587) +
(blue(forestgreen) * 114) / 1000
); // 40219
/*
* white RGB values:
*
* Red: 255
* Green: 255
* Blue: 255
*/
$light-color: round(
(red(white) * 299) +
(green(white) * 587) +
(blue(white) * 114) / 1000
); // 225959
/*
* The middle ground between white and black:
* $light-color / 2 = 225959 / 2 = 112979.5
*
* The absolute value of $color-brightness:
* 40219
*
* The condition ends up as:
* 40219 < 112979.5
*
* The outcome is true and thus the resolution is:
* color: white
*/
@if abs($color-brightness) < ($light-color / 2) {
color: white;
} @else {
color: black;
}
}
To use the mixin, we could do something like the following:
$black: #333;
$cornflower: cornflowerblue;
body {
background-color: $black;
@include text-contrast($black);
}
header {
background-color: $cornflower;
@include text-contrast($cornflower);
}
Super simple and 99.999% of the time will meet a minimum of WCAG AA pass for colour contrast in my experience.
A big shout out to David halford and his blog post on contrasting text for the mixin, full credits to him, it has done me well over the years thusly.
Provide headers for data tables
Note: There is no refresher for this section as we will instead build an accessible data table based on the excellent article on creating accessible data tables by Haydon Pickering.
Lets write a table that shows data of all our potential customers in a sales pipeline.
This could be the beginning markup for our table:
<table>
<thead>
<tr>
<th>name</th>
<th>employees</th>
<th>founded</th>
</tr>
</thead>
<tbody>
<tr>
<td>ACME co</td>
<td>1296</td>
<td>1993</td>
</tr>
<tr>
<td>ACME co</td>
<td>1296</td>
<td>1993</td>
</tr>
</tbody>
</table>
Now, this is a perfectly valid table but if we want to make it as accessible as possible, we can make a few minor changes which will really boost a user using assistive technologies understanding of the data and dependant on styling, non assistive technology users also.
Lets make some changes:
<table>
<caption>
Companies in the current sales pipeline
</caption>
<thead>
<tr>
<th>name</th>
<th>employees</th>
<th>founded</th>
</tr>
</thead>
<tbody>
<tr>
<th>Google</th>
<td>1296</td>
<td>1993</td>
</tr>
<tr>
<th>Facebook</th>
<td>1296</td>
<td>1993</td>
</tr>
</tbody>
</table>
Ok, you may have some questions here, but take a look over the new markup and try to grasp what is going on here.
Good? Ok, lets break it down.
Firstly we have added a <caption></caption>
tag. This tag is specific to tables and provides a way to give more context to users on what data is contained within the table. It can also act as a heading for the table itself and can even take a heading tag as a direct child!
Secondly we added <th></th>
tags into our table body rows. Not many of you will have seen this getting done before I am sure but it is perfectly valid to do. There is one caveate however:
<th></th>
tags are normally associated with columns and thus we need to tell the browser that this specific <th></th>
is relevant only for the row it is held within.
That is where scope="row"
and scope="col"
come in. These tell the browser that we have two headings for the data in this row, the column heading and the row heading.
The table would now look like so if we add this in:
<table>
<caption>
Companies in the current sales pipeline
</caption>
<thead>
<tr>
<th scope="col">name</th>
<th scope="col">employees</th>
<th scope="col">founded</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Google</th>
<td>1296</td>
<td>1993</td>
</tr>
<tr>
<th scope="row">Facebook</th>
<td>1296</td>
<td>1993</td>
</tr>
</tbody>
</table>
For screenreader users, for example, if we were on the <tbody></tbody>
's second row, second cell, we may hear the following announcement on a screenreader:
"Employees, Facebook, 1296"
This helps us provide context and drives understanding, if we mix this with styling to show visibly the difference between the row/column headings and the table cells, we can drive a comparable experience and understanding among all users.
Do not rely on colour alone to convey meaning
Refresher
"The use of colour can enhance comprehension, but do not use colour alone to convey information. Be especially cautious of red/green colour combinations." - Quick Reference Web Accessibility Principles
Building an accessible barchart
Continuing the example from the design article, lets build an accessible barchart that doesn't only rely on colour to show the differences between each item.
In this, let's also put our text-contrast and rem-calc functions above to good use to show them in a real scenario.
First, lets build our the markup:
<dl class="bar-chart">
<dt class="bar-chart__legend">
Users favourite films
</dt>
<dd class="bar-chart__bar bar-chart__bar--40">
Batman begins - 40%
</dd>
<dd class="bar-chart__bar bar-chart__bar--30">
Pokemon: The movie - 30%
</dd>
<dd class="bar-chart__bar bar-chart__bar--60">
The Avengers - 60%
</dd>
<dd class="bar-chart__bar bar-chart__bar--90">
Titanic - 90%
</dd>
</dl>
// @requires the above rem-calc and text-contrast helpers
.bar-chart {
// @see: http://geoffgraham.me/randomize-sass/
$s-min: 20;
$s-max: 70;
$l-min: 30;
$l-max: 90;
@at-root #{&}__legend {
font-weight: bold;
margin-bottom: rem-calc(12);
font-size: rem-calc(24);
}
@at-root #{&}__bar {
padding: rem-calc(12);
margin: 0;
font-size: rem-calc(18.75);
@for $i from 1 through 100 {
&--#{$i} {
max-width: #{$i}#{"%"};
}
&:nth-child(#{$i}) {
$bg: hsl(
random(360),
$s-min + random($s-max + -$s-min),
$l-min + random($l-max + -$l-min)
);
@include text-contrast($bg);
}
}
}
}
With that, we have a functional, bar chart using our helpers from before and it "just works".
However, we are still only relying on colour to denote things, thus, lets hack in a quick example of how we can give our bars background patterns. In the nth-child()
block, add the following @if
block:
@if $i == 2 {
background: linear-gradient(
63deg,
lighten($bg, 10%) 23%,
transparent 23%
)
7px 0,
linear-gradient(63deg, transparent 74%, lighten($bg, 10%) 78%),
linear-gradient(
63deg,
transparent 34%,
lighten($bg, 10%) 38%,
lighten($bg, 10%) 58%,
transparent 62%
),
$bg;
background-size: rem-calc(16) rem-calc(48);
} @else if $i == 3 {
background: radial-gradient(
circle at 0% 50%,
rgba(96, 16, 48, 0) rem-calc(9),
darken($bg, 20%) rem-calc(10),
rgba(96, 16, 48, 0) rem-calc(11)
)
0px 10px,
radial-gradient(
at 100% 100%,
rgba(96, 16, 48, 0) rem-calc(9),
darken($bg, 20%) rem-calc(10),
rgba(96, 16, 48, 0) rem-calc(11)
),
$bg;
background-size: rem-calc(20) rem-calc(20);
} @else if $i == 4 {
background: linear-gradient(
135deg,
darken($bg, 20%) 25%,
transparent 25%
) -50px 0,
linear-gradient(225deg, darken($bg, 20%) 25%, transparent 25%) -50px
0,
linear-gradient(315deg, darken($bg, 20%) 25%, transparent 25%),
linear-gradient(45deg, darken($bg, 20%) 25%, transparent 25%);
background-size: rem-calc(100) rem-calc(100);
background-color: $bg;
} @else if $i == 5 {
background: linear-gradient(
135deg,
darken($bg, 20%) rem-calc(22),
$bg rem-calc(22),
$bg rem-calc(24),
transparent rem-calc(24),
transparent rem-calc(67),
$bg rem-calc(67),
$bg rem-calc(69),
transparent rem-calc(69)
),
linear-gradient(
225deg,
darken($bg, 20%) rem-calc(22),
$bg rem-calc(22),
$bg rem-calc(24),
transparent rem-calc(24),
transparent rem-calc(67),
$bg rem-calc(67),
$bg rem-calc(69),
transparent rem-calc(69)
)
0 64px;
background-color: darken($bg, 20%);
background-size: rem-calc(64) rem-calc(128);
} @else {
background: $bg;
}
Now we have patters on each bar and we have accomplished our goals for this section!
Button and link states
Lets now move onto the final hands on section of this article and build a button and link component that provide appropriate feedback of each state.
Button component
The default state
button {
}
The hover state
button {
&:hover {
}
}
The active state
button {
&:active {
}
}
The focus state
button {
&:focus {
}
}
The disabled state
button {
&:disabled {
}
}
Link component
The default state
a {
}
The link state
a:link {
}
The visited state
a:visited {
}
The hover state
a:hover {
}
The active state
a:active {
}
The focus state
a:focus {
}
The disabled state
a:disabled {
}
Conclusion
That was a lot to go through but we have now went over pretty much everything we did in the design article but from the viewpoint of development.
I hope you learned a few techniques and tidbits about building accessible components or just generally about topics like how we can easily implement element states.
This was and will be the longest article in the series so if you got through it, well done you!
Up next we will look at a simple accessibility test using the aXe library and how to setup a basic gitlab-ci.yml
.
See you in the next one!
Resources
- Inclusive design principles
- Inclusive design principles - provide comparable experiences
- Connection aware performance by Max Böck
- Accessibility and performance by Marcy Sutton
- Visualising web accessibility performance to raise awareness and understanding amongst product stakeholders by Caroline Lindström
- WCAG Success Criterion
- W3C Techniques For Accessibility Evaluation And Repair Tools
- David halford and his blog post on contrasting text
- article on creating accessible data tables
- Quick Reference Web Accessibility Principles
- Randomising items in SASS
Top comments (5)
So glad to see some fellow a11y focused developers on here :)
Thanks for the comment. Yeah, it's great to see in the community, I read your "Writing alternative text that matters" article just the other day actually, nice work.
Thank you! a11y friends gotta stick together :). Saving this for later to read since I am finishing editing up a new blog post, cannot wait to read more after I publish :D
2021 now most of the browsers support prefers-reduced-motion.
Thank you for this amazing article.
You are very welcome 👍