Introduction
As you all can see, websites and web applications are an integral part of our lives. Millions of people around the world use them as tools to communicate, work, shop, and more. Unfortunately, not all websites are designed with people with disabilities in mind. According to the 2023 WebAIM (Web Accessibility In Mind) report, a staggering 96.3% of websites fail to meet the basic requirements for such individuals to be able to use common services to a full extent.
The most common accessibility issues include:
- Lack of alternative text for images (67.9% of websites)
- Insufficient text contrast against backgrounds (86.3% of websites)
- Missing or incorrect form fields labels (68% of websites)
- Keyboard navigation issues (59.6% of websites)
Accessibility is therefore a crucial aspect that deserves attention during the website development process.
What does Angular offer in this respect?
It is worth exploring the a11y package from Angular CDK. This package contains some of the most common solutions for improving the accessibility of a website for people with disabilities.
Let’s take a closer look at what to focus on overall.
HTML Structure
We are all aware of the importance of using HTML tags for search engine optimization (SEO) and web standards compliance. However, few remember the crucial role they play in assistive technologies, such as those used by the visually impaired or those who rely on keyboard navigation. Semantic tags provide additional information for screen readers to help them understand the structure and content of a webpage. They also make it easier to navigate using the keyboard and other assistive devices, as elements such as <nav> and <aside> are recognized as navigation sections.
It is beneficial to divide components into different sections and avoid relying solely on <div> or <p> tags. Enriching the structure with various other tags significantly enhances accessibility. For example:
- <article> and <section> – for grouping content
- <nav> – for grouping links
- <details> – for providing additional details that users can open and close on demand
- <mark> – for highlighting important text
You can greatly improve the navigability of your application by using these and other semantic tags.
NOT | YES |
|
|
Let’s look at an example:
<div role="button">Save</div>
and
<button>Save</button>
Both solutions perform the same function, but not both are considered best practice. Let’s look at the consequences of their use:
- <button> is natively interpreted by browsers as a button with appropriate appearance and behavior. <div> requires additional code to have these properties.
- <button> has a default role button. For <div>, you need to manually add role=’button’ and the appropriate ARIA attributes so that screen readers can correctly interpret the element as a button.
- <button> can be activated using keyboard keys. <div> requires tabindex attributes (which we will discuss more in the ‘Keyboard Usage’ section) and keyboard event handling.
- <button> has built-in properties useful in forms, such as type=’submit’, type=’reset’. <div> does not have these, so there is a need for additional code.
Screen reader
A computer program that recognizes text on a page (including hidden text) and converts it to speech or sends it to a Braille device. How do we use it?
We can use HTML attributes, including the ARIA (Accessible Rich Internet Applications) attributes available in the HTML standard.
Attributes are additional settings for tags, which we can add to the HTML tags we use if they are not sufficient, to change the properties and behavior of an element. In this way, the elements of the page are supplemented with the information necessary for screen readers. Programmers may not see the benefit of using them at first. They may seem to be an unnecessary description, but in reality, they are of vital importance to people who, unfortunately, cannot see, but who can hear.
Remember, however, to focus first on using appropriate HTML tags whenever possible, and to resort to additional attributes when the tags alone are not sufficient.
Here are some examples of attributes:
- aria-label – it assigns a name to an element, especially if the element contains no text, such as an icon or image.
<button mat-icon-button aria-label='Share our blog'>share</button>
- aria-description – it allows you to add an additional description to an element.
<button mat-icon-button aria-description="Click here for more info about the article">info️</button>
- aria-hidden – it can be used to hide non-interactive DOM elements.
<mat-icon aria-hidden="true" class="only-aesthetic"></mat-icon>
- aria-live – it provides live updates on the changes to the content of an element. In the given example, the type polite indicates that the message will be spoken at the next available opportunity, such as after the current sentence is finished.
<div>
<button [disabled]="isDisabled">Save</button>
<div class="alert" aria-live="polite">{{ isDisabled ? 'Button is active' : 'Button is inactive' }}</div>
</div>
- aria-orientation – it specifies the horizontal or vertical orientation of an element, such as a navigation bar or menu.
<ul aria-orientation="vertical" class="menu">
<li></li>
<li></li>
</ul>
- alt – alternative text for an image.
<img src="img_girl.jpg" alt="Angular Love logo"/>
We can see here that we do not need to fill the attribute with phrases like “Image showing the Angular Love blog logo”. Screen readers capture this information based on the tags applied and use it when converting content.
LiveAnnouncer also comes to the rescue. Similar to the aria-live attribute, it allows dynamic text messages to be sent to screen readers, but from within a function, effect, or service rather than from HTML.
private _liveAnnouncer = inject(LiveAnnouncer);
this._liveAnnouncer.announce('25 products found for your search query' );
Customized UI/UX
One of the principles outlined in the WCAG (Web Content Accessibility Guidelines) is to maintain color contrast on a webpage. The minimum contrast ratio should be 4.5:1, with a few exceptions where the ratio may be as low as 3:1. Therefore, the primary color scheme of every page should be designed to meet these requirements. However, what if we cannot provide this contrast and need to implement a high-contrast version? Or if we want to introduce a light mode and a dark mode? We can use CSS variables to create individual color palettes that we load based on the selected setting.
:root {
/* Light mode colors */
--primary-color-light: #3498db;
--background-color-light: #ffffff;
--text-color-light: #000000;
/* Dark mode colors */
--primary-color-dark: #2980b9;
--background-color-dark: #2c3e50;
--text-color-dark: #ecf0f1;
}
body.light-mode {
--primary-color: var(--primary-color-light);
--background-color: var(--background-color-light);
--text-color: var(--text-color-light); }
body.dark-mode { --primary-color: var(--primary-color-dark);
--background-color: var(--background-color-dark);
--text-color: var(--text-color-dark);
}
.expampleClass {
color: var (
--primary - color
)
}
It is also worth considering the possibility of adapting our site in terms of animations. Reducing these animations and the motion of animated elements can significantly improve accessibility. We can adjust them according to the user’s preferences using the CSS feature known as prefers-reduced-motion.
/* Standard animations */
.element {
transition: transform 0.5s ease-in-out;
}
/* Motion reduction when the user prefers less animation */
@media (prefers-reduced-motion: reduce) {
.element {
transition: none;
}
}
Keyboard Usage
Not all interactions with an application can be done with a mouse. For this reason, when creating it, we should provide an alternative option, such as using keyboard keys.
There are many solutions available, and Angular provides some of its own. For example, we can use libraries such as ngx-mousetrap, angular2-hotkeys, or perhaps the most popular, the @HostListener directive. All these tools allow us to capture keystrokes or keyboard shortcuts and adapt actions accordingly, such as opening a search field.
- angular2-hotkeys
private _hotkeysService = inject(HotkeysService);
ngOnInit() {
this._hotkeysService.add(new Hotkey('ctrl+s', (event: KeyboardEvent): boolean => {
event.preventDefault();
}
));
}
- ngx-mousetrap
private _mousetrap= inject(NgxMousetrapService);
ngOnInit() {
this.mousetrap.bind('ctrl+s', (e: KeyboardEvent) => {
e.preventDefault(); this.saveDocument();
}
);
}
- @HostListener
@HostListener('keydown.enter', ['$event']) onEnterKeyDown(
e: KeyboardEvent,
): void {
event.preventDefault();
}
The a11y library also provides tools for managing lists of elements, such as:
- ListKeyManager – this allows us to easily manage list elements, such as navigating through menu items using arrow keys, returning from the last item to the first (using the withWrap() function), and jumping to a specific item.
items = ['Item 1', 'Item 2', 'Item 3'];
items = viewChildren<QueryList<MatListItem>>('itemRef', { read: MatListItem })
keyManager: ListKeyManager<MatListItem>;
ngAfterViewInit() {
this.keyManager = new ListKeyManager(this.items).withWrap();
}
- ActiveDescendantKeyManager – it is an advanced version of ListKeyManager that additionally tracks and manages the active element.
items = ['Item 1', 'Item 2', 'Item 3'];
itemElements = viewChildren<QueryList<ElementRef>>('itemRef')
keyManager: ActiveDescendantKeyManager<ElementRef>;
ngAfterViewInit() {
this.keyManager = new ActiveDescendantKeyManager(this.itemElements).withWrap();
}
// isActive() function checks if a given element is active by comparing its text content with that of the currently active element
isActive(item: string): boolean {
return this.keyManager.activeItem && this.keyManager.activeItem.nativeElement.textContent.trim() === item;
}
At the beginning of this article, we mentioned the HTML attribute tabindex. It is used to manage the order of keyboard navigation (tabbing) and to define non-interactive elements.
Let’s look at a few examples of when we can use it:
- We can override the default tab order by setting the tabindex attribute to positive values in ascending order:
<input type="text" tabindex="2">
<input type="text" tabindex="1">
<input type="text" tabindex="3">
- tabindex=’0′ makes non-interactive elements (e.g., <div>, <span>, <p>) part of the tab sequence and focusable after elements with tabindex greater than 0:
<div tabindex="0">You can click me</div>
- tabindex=’-1′ removes the element from the tab sequence. This is useful if you want an element to be interactive, but not accessible by regular tabbing:
<a tabindex="-1">Hidden link</a>
The correct use of tabindex helps to create a logical navigation order, especially when this order differs from the natural layout of the elements. However, it is important to note that this is not always recommended as it can confuse users. For example, all elements with tabindex=’1′ will be focused before those with tabindex=’2′ and tabindex=’0′, so in practice, it is generally recommended to use only 0 and -1.
Therefore, just as it is good practice to use appropriate HTML tags, tabindex should be used only in non-standard situations where available tools do not adequately address the problem.
Keyboard handling also involves highlighting or indicating the active element. The default outline style in browsers provides a visible indicator of elements that are in focus. People who use assistive technology, such as screen readers, also rely on this visual indicator to help them know what element they are on.
However, the default browser outline does not always match the design of the page. It is usually a black or blue border around the element, and is one of the most common reasons for choosing to remove this feature. However, we should not do this. Instead, we can implement our own styles that are consistent with the design of our page.
Autofocus
Autofocus on web pages automatically highlights a particular interactive element, such as when a page loads or a dialog opens. While this can be useful in many cases, it can be a real challenge for users with disabilities if autofocus is not handled properly. Why is this?
- Users who rely on keyboard navigation may be confused if the focus suddenly jumps to a particular field.
- When autofocus moves the focus to another field, the screen reader may immediately start reading the contents of that field, skipping other important information on the page.
- Autofocus can cause unexpected changes in context. Users may be forced to jump to different parts of the page for no clear reason, which can be frustrating and disorientating.
But let’s not just concentrate on the negatives of autofocus.
Many available components, such as the Dialog in Angular Material, automatically set focus on action buttons or other elements by default, which can cause navigation issues. However, there are cases where opening a modal with a search bar via a button or keyboard shortcut naturally leads to the expectation that the focus will default to the text field. As a result, when we customize a site for accessibility, we should carefully manage autofocus. Below are some examples:
- Manually disabling autofocus on dialog open
this.dialog.open(YourDialogComponent, {
autoFocus: false
});
- Using FocusKeyManager – an extension of ListKeyManager that automatically sets the focus on list items:
items = viewChildren<QueryList<MatListItem>>(‘itemRef’);
private keyManager: FocusKeyManager<MatListItem>;
itemsArray = ['Item 1', 'Item 2', 'Item 3', 'Item 4'];
ngAfterViewInit() {
this.keyManager = new FocusKeyManager(this.items).withWrap();
}
- Using the CdkTrapFocus directive – it restricts interaction on the page to a specific section, applying it to a given element (e.g. temporarily) prevents the user from changing focus to another element, e.g. by pressing the Tab key:
<div *ngIf="dialogOpen" class="dialog" cdkTrapFocus>
Accessibility Testing
We should regularly check that our application is always compliant with accessibility standards. There are tools available for this purpose, such as
- Lighthouse (built into Chrome DevTools)
- Axe (browser extension)
- Screen readers (e.g., NVDA, JAWS)
Summary
Adapting web applications for people with disabilities is a broad topic. Angular provides many solutions and techniques in this area, so we don’t have to look for them in external libraries. Implementing the above practices and tools in the application development process not only improves accessibility for people with disabilities but also improves the overall user experience, making websites more user-friendly and easier to use for everyone.