À un instant donné, une liste virtualisée ne contient dans le DOM qu’une quarantaine d’éléments, quelle que soit la taille réelle du jeu de données qu’elle représente. Ce constat technique, qui fait tout l’intérêt de la virtualisation pour la performance de rendu, devient un problème frontal pour un lecteur d’écran : celui-ci ne peut annoncer que ce qu’il trouve dans le DOM, et n’a donc aucun moyen de savoir qu’il navigue dans une liste de plusieurs milliers d’éléments plutôt que dans une simple liste de quarante lignes.
Cette architecture s’adresse à un composant de défilement optimisé, technique parfois appelée windowing, utilisé pour afficher un catalogue produit ou un annuaire volumineux sans dégrader la performance du navigateur. Elle ne traite pas de la pagination classique, où chaque page reste un ensemble stable et intégralement présent dans le DOM.
Le problème structurel de la virtualisation
Le principe de la virtualisation consiste à ne monter dans le DOM que les éléments visibles à l’écran, plus une marge de sécurité au-dessus et en dessous, et à recycler ces nœuds au fil du défilement. Un conteneur de très grande hauteur, calculée à partir du nombre total d’éléments, simule visuellement la présence de l’ensemble de la liste, alors que son contenu réel change en permanence :
<div class="wpm-liste-virtuelle" style="height: 480px; overflow-y: auto;">
<div style="height: 125000px; position: relative;">
<div style="position: absolute; top: 3200px;">Élément 128</div>
<div style="position: absolute; top: 3225px;">Élément 129</div>
<!-- … environ 40 éléments visibles à la fois -->
</div>
</div>
Pour un utilisateur voyant à la souris, l’illusion fonctionne parfaitement : la barre de défilement reflète la position dans l’ensemble. Pour un lecteur d’écran, qui parcourt le contenu du DOM au fil de la navigation séquentielle, l’expérience équivaut à naviguer dans une liste de quarante éléments qui se renouvelle mystérieusement à chaque déplacement, sans jamais indiquer où l’on se trouve dans l’ensemble.
L’architecture à trois couches
La correction structurelle repose sur la séparation de trois responsabilités, jusque-là mélangées dans un seul conteneur :
wpm-catalogue/
├── conteneur-scroll (role="region", aria-label nommant la liste)
│ └── fenetre-rendu (les ~40 éléments réellement montés, recyclés)
└── zone-annonce (aria-live="polite", hors du flux visuel)
└── texte-position ("Élément 128 sur 4 512")
Le conteneur de défilement porte un rôle region et un aria-label décrivant la nature de la liste — « Catalogue produits, 4 512 résultats » par exemple, valeur qui ne change pas au défilement puisqu’elle reflète le total, pas la position. La fenêtre de rendu reste l’implémentation technique de la virtualisation, inchangée dans son fonctionnement interne. La zone d’annonce, en revanche, est un ajout : un élément séparé, positionné hors du flux visuel mais toujours présent pour les technologies d’assistance, qui reçoit la mise à jour de position à chaque changement significatif.

Le déclenchement de l’annonce
L’annonce ne doit pas se déclencher à chaque pixel de défilement, ce qui produirait un flot verbal ininterrompu et inutilisable. Elle se déclenche plutôt sur un événement de navigation explicite, comme le déplacement du focus d’un élément à l’autre avec les flèches du clavier, ou à intervalle contrôlé pendant un défilement à la souris ou au geste tactile :
function annoncerPosition( indexVisible, totalElements ) {
var zone = document.querySelector( '.wpm-zone-annonce' );
zone.textContent = 'Élément ' + ( indexVisible + 1 ) + ' sur ' + totalElements + '.';
}
conteneurListe.addEventListener( 'keydown', function ( evenement ) {
if ( evenement.key === 'ArrowDown' || evenement.key === 'ArrowUp' ) {
var indexCourant = obtenirIndexElementFocus();
annoncerPosition( indexCourant, totalElementsCatalogue );
}
} );
Un point d’architecture important : la zone d’annonce doit exister dans le DOM avant le premier changement de contenu, faute de quoi certains lecteurs d’écran n’attachent pas correctement leur observateur à un élément aria-live créé après coup. Elle est donc rendue dès le montage initial du composant, vide, prête à recevoir sa première mise à jour.
Le rôle du conteneur global
Au-delà de la zone d’annonce, le conteneur principal bénéficie d’un second niveau d’information : un texte visuellement discret mais lisible par tous, du type « Affichage des éléments 121 à 160 sur 4 512 », positionné juste au-dessus de la fenêtre de rendu. Ce texte, contrairement à la zone aria-live, reste consultable à tout moment par navigation normale, sans dépendre d’un déclenchement ponctuel, ce qui offre un filet de sécurité si l’annonce dynamique venait à être manquée.
Conseil maison : sur un composant de virtualisation développé en interne, prévoir la zone d’annonce dès la première itération du composant plutôt que de l’ajouter en correctif — la retrofitter sur un composant déjà stabilisé demande presque toujours de revoir la gestion du focus au passage.
En résumé
Une liste virtualisée reste un choix d’architecture parfaitement légitime pour la performance, mais elle déplace la responsabilité de communiquer le contexte hors du DOM visible : ce que le rendu partiel masque doit être compensé ailleurs, par une zone d’annonce dédiée et un texte de position toujours disponible. Traiter ces deux éléments comme des composants à part entière, dès la conception du composant de liste, évite d’avoir à démêler plus tard un mélange de responsabilités entre rendu visuel et information structurelle.