Bac à sable
Le carrousel
En audit, je tombe sur des carrousels qui défilent tout seuls et emportent la carte que tu lisais encore. Ici, la liste avec scroll-snap ne bouge que si tu cliques sur une flèche ou que tu la fais défiler.
- Technique
- scroll-snap et boutons
- Navigateurs
- Chrome 105+, Firefox 121+, Safari 16+
- JavaScript
- 19 lignes, pour les flèches
Démo
Les flèches font défiler la liste d'une vue. Le nombre de cartes visibles grandit avec la largeur du carrousel, et la carte suivante dépasse dans la marge pour montrer qu'il y a une suite.
Destinations
Lisbonne
Sept collines, des tramways jaunes et des façades en azulejos.
Kyoto
Plus de 1 600 temples bouddhistes et des jardins de mousse.
Montréal
Une ville bilingue, construite autour du mont Royal.
Édimbourg
Un château sur un ancien volcan, au-dessus de la vieille ville.
Marrakech
Les souks de la médina et la place Jemaa el-Fna.
Reykjavik
La capitale d'État la plus au nord du monde.
Oaxaca
Des marchés colorés et une cuisine connue pour ses sept moles.
Anatomie
sectionLa région, nommée par son titre.h2Le titre du carrousel.buttonLes 2 flèches, « Cartes précédentes » et « Cartes suivantes », masquées parhiddentant que le script n'a pas tourné.ulLa zone qui défile, avecscroll-snap-type.liUne carte, qui se cale sur le bord gauche de la zone.aLe lien de la carte, celui que Tab atteint.
Code HTML
<section class="carousel" aria-labelledby="carousel-title">
<h2 id="carousel-title">Destinations</h2>
<!-- Flèches : masquées tant que le script n'a pas tourné -->
<div class="carousel__arrows" hidden>
<button
type="button"
data-direction="-1"
aria-controls="carousel-track"
>
<span aria-hidden="true">←</span>
<span class="visually-hidden">Cartes précédentes</span>
</button>
<button
type="button"
data-direction="1"
aria-controls="carousel-track"
>
<span aria-hidden="true">→</span>
<span class="visually-hidden">Cartes suivantes</span>
</button>
</div>
<!-- Liste qui défile : une carte = un élément de liste -->
<ul class="carousel__track" id="carousel-track">
<li>
<h3><a href="https://fr.wikipedia.org/wiki/Lisbonne">Lisbonne</a></h3>
<p>…</p>
</li>
<li>…</li>
</ul>
</section>
<script>
// Flèches : défilent d'une vue, sans déplacer le focus
const track = document.querySelector('.carousel__track');
const arrows = document.querySelector('.carousel__arrows');
const [prev, next] = arrows.querySelectorAll('button');
const update = () => {
const atStart = track.scrollLeft < 1;
const atEnd = track.scrollLeft + track.clientWidth > track.scrollWidth - 1;
prev.setAttribute('aria-disabled', atStart);
next.setAttribute('aria-disabled', atEnd);
};
arrows.addEventListener('click', (e) => {
const button = e.target.closest('button');
if (button && button.getAttribute('aria-disabled') !== 'true') {
track.scrollBy({ left: button.dataset.direction * track.clientWidth });
}
});
track.addEventListener('scroll', update);
window.addEventListener('resize', update);
arrows.hidden = false;
update();
</script>/* Le carrousel mesure sa propre largeur (container query),
pas celle de l'écran */
.carousel {
container-type: inline-size;
}
.carousel__track {
--visible: 1;
--peek: 32px;
--gap: 12px;
display: flex;
gap: var(--gap);
/* La carte suivante dépasse dans la marge */
padding-inline: var(--peek);
scroll-padding-inline: var(--peek);
overflow-x: auto;
scroll-snap-type: x mandatory;
}
.carousel__track li {
flex: 0 0 calc((100% - (var(--visible) - 1) * var(--gap)) / var(--visible));
scroll-snap-align: start;
}
@container (min-width: 480px) {
.carousel__track {
--visible: 2;
--peek: 36px;
}
}
@container (min-width: 720px) {
.carousel__track {
--visible: 3;
}
}
@container (min-width: 960px) {
.carousel__track {
--visible: 4;
--peek: 48px;
}
}
/* Avec les flèches, la barre de défilement n'est plus utile */
.carousel:has(.carousel__arrows:not([hidden])) .carousel__track {
scrollbar-width: none;
}
.carousel__arrows button[aria-disabled="true"] {
opacity: 0.55;
cursor: default;
}
@media (prefers-reduced-motion: no-preference) {
.carousel__track {
scroll-behavior: smooth;
}
}Usages
Recommandé pour
- Une série de contenus de même nature, que l'on parcourt dans l'ordre.
- Des cartes qui mènent chacune à une page.
Déconseillé pour
- Une lecture automatique. Sans moyen de l'arrêter et de la relancer, elle ne respecte pas le critère 13.8.
- Masquer la barre de défilement sans flèches. À la souris sans trackpad, plus rien de visible ne fait défiler la liste.
- Désactiver une flèche avec
disabled. En bout de liste, le bouton perd le focus, qui retombe sur lebodyde la page. - Une information importante qu'on ne voit qu'en faisant défiler.
Accessibilité
Rôles et attributs ARIA
3 attributs ARIA : aria-labelledby nomme la région, aria-disabled signale une flèche en bout de liste, aria-hidden tait le caractère de la flèche. Pas d'aria-roledescription="carrousel" : à l'entrée, le lecteur d'écran annonce déjà « liste, 7 éléments », ce qui dit comment la parcourir.
| Attribut | Élément | Description |
|---|---|---|
aria-labelledby="carousel-title" | section | La région prend le nom de son titre, « Destinations », et apparaît dans la liste des repères. |
hidden | div des flèches | Retiré par le script au chargement. Sans JavaScript, pas de flèches qui ne marcheraient pas, et la barre de défilement reste visible. |
class="visually-hidden" | span dans chaque button | Nomme les flèches « Cartes précédentes » et « Cartes suivantes » sans afficher le texte. |
aria-hidden="true" | span de la flèche | Le caractère « ← » ou « → » n'est pas lu : le nom du bouton suffit. |
aria-controls="carousel-track" | button | Relie chaque flèche à la liste qu'elle fait défiler, par l'id de la liste. |
aria-disabled="true" | button | En bout de liste, la flèche reste focalisable et le lecteur d'écran l'annonce « estompé ». |
Rôle list (implicite) | ul | Le lecteur d'écran annonce le nombre de cartes dès l'entrée : « liste, 7 éléments ». |
Navigation au clavier
| Touche | Action |
|---|---|
| Tab | Passe des flèches au lien de la 1re carte, puis de carte en carte, et la liste défile pour afficher le lien |
| Maj+Tab | Revient au lien de la carte précédente |
| Entrée ou Espace sur une flèche | Fait défiler la liste d'une vue, sans déplacer le focus |
| Flèche gauche ou Flèche droite sur un lien de carte | Fait défiler la liste d'une carte, sans déplacer le focus. Le lien peut alors sortir du champ |
Ordre de tabulation
- La flèche « Cartes précédentes », estompée au départ.
- La flèche « Cartes suivantes ».
- Le lien « Lisbonne ».
- Le lien « Kyoto ».
- Le lien « Montréal ».
- Le lien « Édimbourg », puis ceux des 3 cartes suivantes, hors champ au départ : la liste défile quand un lien reçoit le focus.
Avec un lecteur d'écran
Exemple sur Chrome avec VoiceOver. Les mots changent d'un lecteur d'écran à l'autre.
| Action | Annonce |
|---|---|
| Entrée dans la région | « Destinations, région » |
| Tab sur la 1re flèche | « Cartes précédentes, estompé, bouton » |
| Tab, puis Entrée | « Cartes suivantes, bouton » ; le défilement n'est pas annoncé, la liste reste lisible élément par élément |
| Entrée dans la liste | « liste, 7 éléments » |
Critères RGAA concernés
- Critère 7.1 : Chaque script est-il, si nécessaire, compatible avec les technologies d’assistance ?
- Les flèches sont de vrais boutons : le lecteur d'écran reçoit leur nom, leur rôle et l'état
aria-disabled. - Critère 7.3 : Chaque script est-il contrôlable par le clavier et par tout dispositif de pointage (hors cas particuliers) ?
- Les flèches s'activent au clavier, avec Entrée ou Espace, comme à la souris.
- Critère 9.3 : Dans chaque page web, chaque liste est-elle correctement structurée ?
- Les cartes forment une vraie liste,
uletli. - Critère 10.7 : Dans chaque page web, pour chaque élément recevant le focus, la prise de focus est-elle visible ?
- Chaque lien qui reçoit le focus avec Tab est ramené dans la zone visible, où son focus se voit.
- Critère 12.8 : Dans chaque page web, l’ordre de tabulation est-il cohérent ?
- L'ordre de tabulation suit l'ordre visuel : les flèches, puis les cartes.
- Critère 13.8 : Dans chaque page web, chaque contenu en mouvement ou clignotant est-il contrôlable par l’utilisateur ?
- Rien ne bouge sans action de l'utilisateur.
Autres références
WCAG 2.2
- Understanding SC 1.3.1: Info and Relationships (en anglais)
- Understanding SC 2.1.1: Keyboard (en anglais)
- Understanding SC 2.2.2: Pause, Stop, Hide (en anglais)
- Understanding SC 2.4.3: Focus Order (en anglais)
- Understanding SC 2.4.7: Focus Visible (en anglais)
- Understanding SC 4.1.2: Name, Role, Value (en anglais)
EN 301 549
- Accessibility requirements for ICT products and services (V4.1.1, PDF) (en anglais)Clauses 9.1.3.1, 9.2.1.1, 9.2.2.2, 9.2.4.3, 9.2.4.7, 9.4.1.2
Pattern APG
- Carousel (Slide Show or Image Rotator) Pattern (en anglais)
Au-delà de l'APG
| Nouveauté | Apport | Navigateurs | Dans la démo |
|---|---|---|---|
::scroll-button() | Génère en CSS les flèches précédente et suivante, nommées par le texte alternatif de content. | Chrome 135+, Edge 135+ | Variation expérimentale |
::scroll-marker | Génère en CSS un point de navigation par carte, relié à sa carte. | Chrome 135+, Edge 135+ | Non |
Variations
Flèches générées en CSS, Expérimental
::scroll-button() crée les flèches sans JavaScript, nommées par le texte alternatif de content. Seuls Chrome et Edge les génèrent, depuis avril 2025 : ailleurs, garde la barre de défilement. En bout de liste, le navigateur les désactive et le focus retombe sur la page, comme avec disabled : en production, garde plutôt les flèches de la démo.
Destinations, flèches en CSS
Lisbonne
Sept collines, des tramways jaunes et des façades en azulejos.
Kyoto
Plus de 1 600 temples bouddhistes et des jardins de mousse.
Montréal
Une ville bilingue, construite autour du mont Royal.
Édimbourg
Un château sur un ancien volcan, au-dessus de la vieille ville.
Marrakech
Les souks de la médina et la place Jemaa el-Fna.
Reykjavik
La capitale d'État la plus au nord du monde.
Oaxaca
Des marchés colorés et une cuisine connue pour ses sept moles.
Les flèches n'apparaissent que dans Chrome et Edge. Ailleurs, la barre de défilement reste. Va au bout de la liste avec Entrée : le focus quitte la flèche.
/* En bout de liste, le navigateur désactive la flèche :
le focus retombe sur la page */
.carousel__track::scroll-button(left) {
content: "←" / "Cartes précédentes";
}
.carousel__track::scroll-button(right) {
content: "→" / "Cartes suivantes";
}Une carte par vue
flex: 0 0 100% n'affiche qu'une carte, et padding-inline: 0 retire la marge où dépassait la suivante. Les flèches deviennent alors le seul indice visible qu'il y a une suite.
Destinations, une carte par vue
Lisbonne
Sept collines, des tramways jaunes et des façades en azulejos.
Kyoto
Plus de 1 600 temples bouddhistes et des jardins de mousse.
Montréal
Une ville bilingue, construite autour du mont Royal.
Édimbourg
Un château sur un ancien volcan, au-dessus de la vieille ville.
Marrakech
Les souks de la médina et la place Jemaa el-Fna.
Reykjavik
La capitale d'État la plus au nord du monde.
Oaxaca
Des marchés colorés et une cuisine connue pour ses sept moles.
/* Sans marge, la carte suivante ne dépasse plus */
.carousel__track {
padding-inline: 0;
scroll-padding-inline: 0;
}
.carousel__track li {
flex: 0 0 100%;
}Grille sur grand écran
Avec une container query, la même liste passe en grille quand le carrousel est assez large. Toutes les cartes s'affichent, et les flèches disparaissent puisque plus rien ne défile.
Destinations, en grille
Lisbonne
Sept collines, des tramways jaunes et des façades en azulejos.
Kyoto
Plus de 1 600 temples bouddhistes et des jardins de mousse.
Montréal
Une ville bilingue, construite autour du mont Royal.
Édimbourg
Un château sur un ancien volcan, au-dessus de la vieille ville.
Marrakech
Les souks de la médina et la place Jemaa el-Fna.
Reykjavik
La capitale d'État la plus au nord du monde.
Oaxaca
Des marchés colorés et une cuisine connue pour ses sept moles.
@container (min-width: 520px) {
.carousel__track {
display: grid;
grid-template-columns: repeat(2, 1fr);
overflow: visible;
}
/* Plus rien ne défile : les flèches disparaissent */
.carousel__arrows {
display: none;
}
}