Intro
Everyday, accessibility gets more awareness, and yet a few weeks back, I was a total ignorant about it. I was assigned to a client project with the intent to improve their overall accessibility, and to comply with WCAG 2.1's category AA. This was an incredible challenge, and I had to learn a lot in order to effectively meet the deadline.
My intent, with this article, as well as all the others from Make it accessible, is to walk you through the challenges I faced. This project was in Angular, so I'll be going through accessibility from an Angular Developer's point of view. That being said, all the knowledge found here can be easily applied to other Javascript frameworks, and I learned that the best way to improve accessibility is to use semantic HTML.
For this article, I decided to focus on Headings, since they are one of the key mechanisms that visually impaired users rely on. At the beginning of my a11y journey, I had to make a first diagnosis of the status of the application when used with a screen reader. After installing NVDA, I was able to go through the main flow of it blindfolded, since I already knew how the application behaved.
Yes, I was blindfolded while in a call with the project leader. It was a bold move, even though it worked out. It was a false positive, because if I didn't have first hand knowledge of the app, it would have been impossible to use effectively. After a long call, we decided that in order for us to continue, we needed to know how do blind users actually use the screen readers.
After spending sometime online, I found some really good resources to help developers understand how blind people use applications. At the end of the article, I have the links to the videos. It turns out that for these users, the headings are key. Before we continue, let's define a heading.
Headings
HTML defines six levels of headings. A heading element implies all the font changes, paragraph breaks before and after, and any white space necessary to render the heading. The heading elements are H1, H2, H3, H4, H5, and H6 with H1 being the highest (or most important) level and H6 the least. w3c Organization.
If you have ever done any HTML code, you probably have seen them already. They can be used in HTML like this <h1>Main heading</h1>
. I used to think that they were just for aesthetic purposes- that their job was just to show text with a different format to attract the attention of the users.
Although I was partly right, it wasn't its only purpose. If you take a look at its definition from the accessibility point of view, you'll find some differences.
Headings communicate the organization of the content on the page. Web browsers, plug-ins, and assistive technologies can use them to provide in-page navigation. w3c Organization - WAI ARIA.
You may now have more questions than answers, but that's okay, because we are going to get there. It turns out that headings have two main roles: the one I already mentioned, and secondly, allowing AT users in-page navigation.
Screen Readers
In the previous section, I mentioned the fact that headings provide AT users with in-page navigation. Let's dive into that a bit. It turns out, that when blind users are navigating, they rely on screen reader functionalities, and there's one in every major product that allows users to see all headings, and jump directly to them.
I started wondering, is this really how people use this? It made no sense to me. But after searching, a lot I found videos of blind users using the common sites we all know: Twitter, Gmail, etc... And guess what! It's very common to see users rely on the navigation tool provided in their screen reader of choice.
I decided it was time to give my project a second chance, and use the navigation tool from NVDA. But it turns out that someone decided that <span>
tags were more appropriate for headings, and when I opened it, the heading list was empty. Screen readers cannot infer that by themselves. That's why you have to use HTML.
Believe it or not, here I go again: Use semantic HTML.
By doing so, assistive technology users will be capable of using your apps- or at least reading and understanding them.
NOTE: Blind users know how to use screen readers. They tend to create a mental map of the application by going through the headings. If you don't provide the basic tools, your app will not be accessible.
Proper Headings
Imagine that you can't rely on colors, sizes, borders, etc... To denote relationships between the parts of your app. You may think that will make the apps boring, but it's the only way to make your app usable by visually impaired people. They see apps in a very different way, we have to give them alternatives to know the relationships between the elements. But this elemental relationship is a whole topic by itself. I just want to mention it.
Headings are a way to denote relationships or even better, groupings. I like to think of it as trees.
Well, anyway. Imagine the first <h1>
is the root node of our application, just like in trees, there's a single root (node). All the consequent <h2>
are treated as children of the root or <h1>
.
Now comes the tricky part, suppose you are writing content right below one of the <h2>
's, and you want to create a new heading. There are two possible scenarios:
- You want the heading to be a child of the root.
- Solution: Use
<h2>
. - You want the heading to be a child of one of the
<h2>
. - Solution: Use
<h3>
.
By now, you should understand the idea. But there's one new problem: What about reusable components?
Reusable Components
After realizing that we had no headings, I rushed to the project leader and told him about it. We agreed that it was one of our main concerns now, and that we had to do something.
Some days later, I noticed that, for same cases, it was straightforward. But suddenly, I encountered a roadblock. We had reusable components being used at different hierarchical levels, and it was impossible to just pick one heading level.
NOTE: If you design your application having accessibility as a first class citizen, you can easily avoid this situation by creating the right components from the beginning.
I wasn't authorized to create more components because that meant making maintainability harder. So, I had a new task ahead- creating a component that could, by itself, know which heading to use according to the place where it is placed in the DOM.
I highly encourage you to avoid this, and instead, redesign from the ground up, with accessibility in mind for all the design decisions.
Solution
I started by creating a simple component I named HeadingComponent that looks like this.
@Component({
selector: 'app-heading',
template: `
<div
appHeading
[headingId]="headingId"
[parentHeadingId]="parentHeadingId"
[text]="text"
></div>
`
})
export class HeadingComponent {
@Input() headingId: string;
@Input() parentHeadingId: string;
@Input() text: string;
}
The Heading Component can take input values:
- HeadingId: Identifier that allows querying the DOM to find a specific heading.
- ParentHeadingId: Identifier that allows querying the DOM to find a specific heading, it's the headingId of the parent heading.
- Text: The text content that will be displayed inside the heading tag.
As you can see, I used an inline template because we only use a single HTML element. I created this component because I didn't want users to attach it directly to the span. In my opinion, it looks more self explanatory this way.
In order to do DOM manipulations, Angular Docs state that you should use directives. So let's create a directive that introduces the heading inside of our component with the right hierarchy.
import { Directive, ElementRef, AfterViewInit, Input } from '@angular/core';
@Directive({
selector: '[appHeading]'
})
export class HeadingDirective implements AfterViewInit {
@Input() headingId: string;
@Input() text: string;
@Input() parentHeadingId: string;
constructor(private el: ElementRef) {}
ngAfterViewInit() {
this.el.nativeElement.innerHTML = this.wrapText(
this.headingId || 'root',
this.getTag(this.getHierarchy(this.headingId, this.parentHeadingId)),
this.text
);
}
private getHierarchy(headingId: string, parentHeadingId: string) {
if (!headingId) {
return 1;
} else if (!parentHeadingId) {
return 2;
} else {
const parentHeading = document.querySelector(`#${parentHeadingId}`);
const parentHierarchy = parseInt(parentHeading.tagName[1], 10);
return parentHierarchy === 6 ? 6 : parentHierarchy + 1;
}
}
private getTag(hierarchy: number) {
return `h${hierarchy}`;
}
private wrapText(id: string, tag: string, text: string) {
return `<${tag} id=${id}>${text}</${tag}>`;
}
}
The Heading Directive has multiple methods to get the job done:
- getTag: Given a hierarchy it returns the proper heading tag.
- wrapText: Given an id, tag and text, it creates the html tag for the heading.
- getHierarchy: Given a headingId and parentId, it infers the hierarchy of the current element.
And that's it. If you want to go ahead and take a look at this working, you can take a look at this repository to see it in action. In their repo there are multiple samples showing how it works.
Conclusion
Use Semantic HTML
This is the third time you have read that in this article. When you start trying to make applications accessible, you quickly realize that HTML5 is your best friend. In this case, we focused on headings, but this idea applies to almost all the native elements. If you asked me how do I make my application accessible, I would ask, are you sure you are using the right semantic HTML?