Après avoir exploré les 8 erreurs courantes en accessibilité web dans les deux premières parties, place maintenant au cas pratique !
L'exemple de la Dropdown
Nous allons maintenant mettre en application les concepts évoqués dans un exemple : la Dropdown.
Le composant Dropdown est probablement l’un des plus “recodé”, puisqu’il vise à remplacer la balise native `<select>` qui est très peu personnalisable pour des applications au design moderne.
Ainsi, nous devons reproduire un à un les comportements et fonctionnalités proposés nativement par le `<select>` natif pour ne pas perdre en accessibilité :
- l’apparition de la liste d’éléments lors du déclanchement d’un `<input>` (click ou via le clavier)
- navigation via les flèches directionnelles (haute et basse) pour choisir un élément à sélectionner
- sélectionner un élément choisi précédemment via le clavier
- sélectionner un élément via un click
- l’affichage de la valeur sélectionnée disponible dans un `<input>` (champ de saisie) en `readonly` pouvant recevoir le focus
Note : Beaucoup d’autres fonctionnalités doivent aussi être implémenter, comme le focus direct sur un élément filtrer au clavier, plus de touches de navigation … Voir ici
Pour répondre au besoin défini, nous allons utiliser plusieurs rôles et ARIA afin de compléter sémantiquement l’utilisation de nos balises html :
- `role=”combobox”` : Input contrôlant un autre élément apparaissant dynamiquement pour aider l’utilisateur à choisir une valeur
- `role=”listbox”` : L’utilisateur peut sélectionner des éléments dans cette liste
- `role=”option”` : Elément d’une liste pouvant être sélectionné (équivalent à la balise `<option>` mais hors d’un select et autorisant plus de contenu)
- `aria-controls` : La combobox contrôle cette listbox
- `aria-expanded` : Décrit si la listbox liée est visible ou non
- `aria-activedescendant` : Cette propriété identifie l’option active d’une liste lors de la navigation au clavier (via son `id`)
- `aria-selected` : Cette propriété indique qu’une option est sélectionnée
my-dropdown.component.html
@for (option of options; track $index) {
@let isCurrent = selectedValue === option;
-
{{ option }}
}
my-dropdown.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'my-dropdown',
templateUrl: './my-dropdown.component.html'
})
export class MyDropdownComponent {
/**
* The possible values
*/
options = ['Choose a value', 'first value', 'second value', 'third value'];
/**
* The selected value
*/
selectedValue = this.options[1];
/**
* Manage the display of the list of values
*/
protected isOpened = false;
/**
* The currently active value using keyboard management
*/
protected activeValue!: string;
/**
* The currently active value using keyboard management
*/
protected activeValueIndex!: number;
/**
* Select the value and focus the dropdown input
*/
selectValue(value: string): void {
this.selectedValue = value;
this.activeValue = value;
this.isOpened = false;
}
/**
* Trigger the opening of the dropdown and the focus element
*/
protected triggerOpening(): void {
this.isOpened = !this.isOpened;
if (this.isOpened) {
this.setActiveItem(this.selectedValue);
} else {
this.selectValue(this.activeValue);
}
}
protected manageInputKeydown(event: KeyboardEvent): void {
switch (event.code) {
case 'Enter':
case 'NumpadEnter':
case 'Space':
this.triggerOpening();
break;
case 'ArrowUp':
if (this.isOpened) {
if (this.activeValueIndex > 0) {
this.setActiveItem(this.options[this.activeValueIndex - 1]);
}
}
break;
case 'ArrowDown':
if (this.isOpened) {
if (this.activeValueIndex < this.options.length) {
this.setActiveItem(this.options[this.activeValueIndex + 1]);
}
}
}
}
protected manageBackdropKeydown(event: KeyboardEvent): void {
switch (event.code) {
case 'Enter':
case 'NumpadEnter':
case 'Space':
case 'Escape':
this.isOpened = false;
}
}
protected manageOptionKeydown(event: KeyboardEvent, value: string): void {
switch (event.code) {
case 'Enter':
case 'NumpadEnter':
this.selectValue(value);
break;
case 'Escape':
this.isOpened = false;
break;
}
}
/**
* Set the active item after keyboard navigation
*/
private setActiveItem(id: string): void {
const item = this.options.find(option => option === id);
if (item) {
this.activeValue = item;
this.activeValueIndex = this.options.findIndex(option => option === id);
}
}
}
Bonus : A vous de tester
Lors des différents tests réalisés sur les interfaces développées, vous pouvez déjà tester certains points relatifs à l’accessibilité.
Par exemple en utilisant un lecteur d’écran dès la phase de développement et des tests fonctionnels. On peut également vérifier si l’utilisateur peut naviguer simplement et efficacement dans l’application et ses composants, uniquement au clavier, sans utiliser la souris.
Il faut alors se poser les bonnes questions :
- Suis-je capable de comprendre les informations que je souhaite transmettre à mon utilisateur ?
- Puis-je accéder facilement aux fonctionnalités que je recherche ?
- Suis-je capable de comprendre la structure de la page et le sens logique de positionnement du contenu ?
- Ai-je pu interagir facilement avec les différents éléments ?
- Ai-je pu identifier quels étaient les différents liens, boutons, etc ainsi que leurs effets ?
Conclusion
Nous avons vu un aperçu de 8 différentes erreurs trop largement répandues dans le développement web nuisant à l’accessibilité des applications, ainsi que des solutions simples à mettre en place pour les corriger.
Dans l’idéal, on évoque ces règles dès le lancement d’un projet ou la conception de nouvelles fonctionnalités. Cela permet de les respecter dès le départ et de limiter, voire éviter, les coûts d’adaptation.
La mise en place de règles définies, que ce soit lors de la phase de développement ou de validation permet également de maximiser leur respect.
Les règles à respecter en terme d’accessibilité sont nombreuses et s’appliquent en fonction du contenu de l’application développée. Il est donc essentiel de se documenter, par exemple auprès du [WCAG] (guideline d’accessibilité du web) ou plus largement du [W3C] (consortium définissant les standards du web).
Alexandre PICARD
Practice Leader Product Development à Toulouse
& Tech Lead Angular