Accessibility in web development: 8 mistakes to be avoided

Accessibility in web development: 8 mistakes to avoid (1/3)

Alexandre has divided this article on web accessibility into three parts. In this first part, he presents common mistakes: misuse of tags and title hierarchy. He also discusses image management and the difference between links and clickable elements.

The second part, soon to be published, will cover keyboard navigation, ARIA, roles and adapted tools.

Finally, the third part will feature a case study on the accessibility of a dropdown menu.

Web accessibility enables systems to be adapted for use by everyone. It concerns both web sites and web applications. It plays a key role right from the design and development phase.

This is one of the major objectives of HTML. This semantic language is used to structure and give meaning to content. CSS manages visual presentation. JavaScript adds interactivity.

But today's web is becoming increasingly interactive. Modern JavaScript frameworks and component-oriented development are becoming widespread. As a result, the semantic use of HTML is declining. And this has a direct impact on web accessibility.

Note: The component development paradigm consists of developing in the form of more or less atomic bricks, isolated and offering easier reuse, testing and maintenance.

Web accessibility is often reduced to use by people with disabilities (e.g. the visually impaired). In reality, it goes much further.

Accessibility concerns all users. It also has a direct impact on user experience (UX) and search engine optimization (SEO).

In this article, we take a look at 8 common mistakes that affect web accessibility. And we suggest some simple solutions to remedy them.

Note: Some of the examples below use the tailwind and Angular frameworks.

1. Use the right tags

If we go back to HTML in more detail, as we said in the introduction, it's a semantic markup language. This means that tags order content according to what they contain.

Cependant, une mauvaise pratique maintenant trop répandue est la surutilisation de la balise `<div>`.

En effet, seulement 2 balises HTML n’ont pas de sens sémantique : `<div>` et `<span>`. Elles servent principalement à regrouper des éléments entre eux afin d’y appliquer du style.

				
					
<div>
  <div>Mes films préférés</div>
  <div>
    <div *ngFor="let movie of movies">
      <div>
        <img [src]="movie.imageUrl" [alt]="movie.imageAcss" />
        <div>{{movie.title}}</div>
      </div>
      <div>
        <div>{{movie.summary}}</div>
      </div>
      <div>Rating : <span>{{movie.rating}}</span></div>
    </div>
  </div>
</div>


				
			

The above code fulfills its purpose of displaying a user's list of favorite movies. However, a screen reader or indexing robot perceives neither the structure nor the logic of the elements. It renders the content as is, without understanding its meaning.


However, we can use a multitude of tags, for example :

  • `<nav>` : définit une zone contenant des éléments de navigation, utilisée par exemple dans le menu ou le breadcrumb d’une application
  • `<main>` : déclare le contenu principal. Doit être présent une seule fois par page.
  • `<section>` : déclare une zone ayant le même contenu thématique, souvent suivit par un titre.
  • `<article>` : définit un contenu indépendant de la page
  • `<header>` : déclare la partie introductive d’une page ou d’un élément. Par exemple on parle de header d’une application contenant les liens principaux, ou du header d’un article (voir exemples)
  • `<footer>` : contrairement au header, il sert à marquer la fin d’une page ou d’un élément
  •  `<aside>` : définit un contenu sans rapport direct avec le contenu principal (main, section), comme des compléments d’information ou les légendes
  • `<ol>/<ul> <li>` : déclare une liste (ordonnée/désordonnée) et chaque élément de cette liste
  • `<hr>` : déclare un changement de thématique entre des sections ou des paragraphes, souvent représenté par un trait fin de séparation

We can transform the previous example to make it more logical and accessible on the web:

				
					
<section>
  <h2>Mes films préférés</h2>
  <ul>
    <li *ngFor="let movie of movies">
      <article>
        <header>
          <img [src]="movie.src" [alt]="movie.alt" />
          <h3>{{movie.title}}</h3>
        </header>
        <p>{{movie.summary}}</p>
        <footer>Rating : <span>{{movie.rating}}</span></footer>
      </article>
    </li>
  </ul>
</section>


				
			

We therefore have a section of the page containing a level 2 title. This section contains a disordered list of articles made up of a header (containing an image and a level 3 title), a paragraph and a footer.

So, without talking about the content itself (title, summary, ...), we can understand the structure and meaning of each element.

2. Title levels must be ordered

Let's turn now to a subject that seems obvious once stated, but poses more difficulties in practice, particularly with the component development paradigm(see Introduction).

In HTML, headings (h1, h2, ..., h6) represent title levels. So a level 3 heading (h3) should always follow a level 2 heading (h2). If we compare this with a point-by-point oral presentation, it would make no sense to start III) after I), and begin with C). It's the same principle here.

Ainsi, si nous reprenons l’exemple précédent (films préférés), c’est bien le cas avec notre `<h3>` étant un enfant (même indirect) d’un `<h2>`.

Cependant, si l’on extrait le bloc de présentation d’un film (`<article>`) pour en faire un composant réutilisable, un problème se pose. Le `<h3>` à l’intérieur ne correspondra pas toujours au bon niveau de titre, car il dépendra de la structure dans laquelle le composant est intégré.

This lack of flexibility is not only detrimental to web accessibility, but also to the developer's experience.

To avoid this, you need to allow the developer to specify the title level manually. This can be done via a parameter or a free content zone (e.g. ng-content).

3. Content image vs. decorative image

Images are essential in today's web applications. We can divide them into two opposing categories: content images and decorative images. Content images convey important information to users. Decorative images are more related to design and marketing. So, one is mandatory, the other rather optional.

This question must be asked of everyone when it comes to web accessibility, particularly for screen readers, but also for SEO.

Pour les images dites de contenu, il faut alors utiliser l’attribut `alt` de la balise `<img>` en fournissant une description pertinente de l’image. Par exemple, “Image du film” reste un contenu peu informatif, que “Affiche du film Joker” remplace de manière plus précise et utile. Le personnage du Joker se tient debout dans un escalier entre 2 immeubles”.

For decorative images, remove or leave empty the `alt` attribute and use the `aria-hidden="true"` attribute. It is also possible to display the image with the CSS property `background-image`, which is not suitable for content images, since the image is then no longer part of the HTML content, but of the CSS style.

Note: When an image cannot be loaded (slow connection, bad URL...), the text defined in the alt attribute is displayed instead by the browser.

4. A link is not a button

Note: Automatically opening a link in a new tab is also bad web accessibility and UX practice, but also exposes security vulnerabilities.

Les liens sont définis par la balise `<a>` et embarquent plusieurs fonctionnalités natives d’un navigateur (ouverture dans un nouvel onglet par exemple).

They can thus be interpreted by a screen reader to decree that the next action is navigation-related.

However, more and more links are using other HTML tags via JavaScript. This has a negative impact on all users.

Le clic sur un `<button>` entrainant une navigation via du code javascript est donc une mauvaise pratique au niveau de l’expérience utilisateur (UX). En effet, il n’a pas la possibilité d’ouvrir ce lien dans un nouvel onglet par exemple. Si cette mise en place vise à effectuer divers contrôles, ces contrôles doivent alors se faire en amont.

Please note that you cannot semantically deactivate a link.

The only case where redirection via javascript can be "tolerated" is following asynchronous processing (data exchange with an API, during login for example), if this was explicit for the user.

5. Clickable elements are not always buttons

The `onclick` javascript event reacts to a user clicking on a clickable element (usually a button, except in specific cases).

Cependant, la balise `<button>` est décrite par les standards HTML comme devant contenir uniquement des éléments dits “inline”, donc un contenu principalement textuel (texte, `<span>`, `<i>`, `<b>`, …).

Note : cette règle ne s’applique pas aux liens (`<a>`) qui peuvent contenir de larges et divers contenus.

However, in web applications with a more modern design, the need for clickable zones with more structured content has arisen. This has led to the appearance of buttons with broad content, but above all click events on non-clickable tags (`div`, `article`, ...).

This has an impact on accessibility and user experience, as these elements are not interpreted by browsers (and screen readers) as clickable. They can only be interacted with by a click of the mouse (see next article).

To remedy this, a structuring element that we wish to make clickable must possess the attributes `role="button"` and `tabindex=0`, enabling it to be interpreted as a button and to receive **focus** via the use of tab navigation within the page. Finally, a `keydown`, `keyup` or `keypress` event management is required to reproduce native button behaviors, such as activation using the *Enter* key.

Note: using the `role="button"` does not make the element disabled. You must therefore manage this case manually by removing the attributes.

This repeated use of the attributes and events described can be mutualized and could, for example, be done by an Angular :

				
					@Directive({
  selector: '[myClickableElement]',
  standalone: true,
})
export class MyClickableElementDirective {
  @HostBinding('attr.tabindex') tabIndex = 0;
  @HostBinding('attr.role') role = 'button';

  constructor(private element: ElementRef<HTMLElement>) {}

  @HostListener('keypress', ['$event']) manageKeyPress(event: KeyboardEvent): void {
    if (event.key === 'Enter' && !event.ctrlKey) {
      this.element.nativeElement.click();
    }

    // Default space event is scroll, except on buttons
    if (event.code === 'Space') {
      event.preventDefault();
      event.stopImmediatePropagation();
    }
  }

  @HostListener('keyup', ['$event']) manageKeyUp(event: KeyboardEvent): void {
    if (event.code === 'Space') {
      this.element.nativeElement.click();
    }
  }
}


				
			

Warning: if you're using a web accessibility linter, using a directive will still be interpreted as an error, since the parser won't understand the dynamic application of `role`, `tabindex` and `keydown`.

End of this first part, the rest will be available very soon.

Image by Alexandre PICARD

Alexandre PICARD

Practice Leader Product Development in Toulouse
& Tech Lead Angular

Share this article

Share this article

Contents

Read also

Read the article
Read the article
Read the article