Améliorer l'accessibilité a11y d'un composant Vue
Vous allez analyser et améliorer l'accessibilité (a11y) d'un composant Vue existant en suivant les standards WCAG 2.1 et les bonnes pratiques Vuetify.
Objectif
Rendre le composant accessible à tous les utilisateurs, notamment ceux utilisant des technologies d'assistance (lecteurs d'écran, navigation au clavier, etc.).
Étapes d'analyse et d'amélioration
1. Analyse du composant existant
ÉTAPE 1 : Audit d'accessibilité
- Lire le composant sélectionné et analyser sa structure
- Identifier les éléments interactifs (boutons, liens, formulaires)
- Vérifier la hiérarchie des titres (h1, h2, etc.)
- Examiner l'utilisation des couleurs et contrastes
- Analyser la navigation au clavier
- Vérifier les images et médias
2. Points de contrôle WCAG 2.1
Niveau A (Critique)
- Contenu non-textuel : Alt text pour les images
- Média temporel : Sous-titres, transcriptions
- Adaptable : Structure sémantique correcte
- Distinguable : Contraste minimum 4.5:1 (3:1 pour texte large)
- Accessible au clavier : Tous les éléments interactifs
- Convulsions : Pas de contenu clignotant
- Navigable : Liens descriptifs, focus visible
- Compatible : Markup valide, noms accessibles
Niveau AA (Recommandé)
- Distinguable : Contraste amélioré 7:1 (4.5:1 pour texte large)
- Navigable : Titres de page, ordre de focus logique
- Saisie : Labels et instructions clairs
- Compatible : Changements de statut annoncés
3. Implémentation Vuetify a11y
Composants Vuetify avec accessibilité intégrée
<!-- Boutons -->
<v-btn :aria-label="buttonLabel" :disabled="isDisabled" @click="handleClick">
<v-icon
:icon="iconName"
:aria-hidden="hasText"
/>
{{ buttonText }}
</v-btn>
<!-- Navigation -->
<nav aria-label="Navigation principale">
<v-list>
<v-list-item
v-for="item in menuItems"
:key="item.id"
:to="item.path"
:aria-current="$route.path === item.path ? 'page' : undefined"
>
{{ item.title }}
</v-list-item>
</v-list>
</nav>
<!-- Formulaires -->
<v-form @submit.prevent="handleSubmit">
<v-text-field
v-model="email"
:label="$t('email')"
:error-messages="emailErrors"
:aria-describedby="emailErrors.length ? 'email-errors' : undefined"
required
type="email"
/>
<div
v-if="emailErrors.length"
id="email-errors"
class="sr-only"
>
{{ emailErrors.join(', ') }}
</div>
</v-form>
<!-- Images -->
<v-img
:src="imageSrc"
:alt="imageAlt"
:aria-describedby="hasCaption ? 'img-caption' : undefined"
/>
<p v-if="hasCaption" id="img-caption" class="text-caption">
{{ imageCaption }}
</p>
<!-- Alertes et statuts -->
<v-alert
:type="alertType"
:aria-live="isImportant ? 'assertive' : 'polite'"
role="alert"
>
{{ alertMessage }}
</v-alert>
4. Attributs ARIA essentiels
Navigation et structure
role: Définit le rôle sémantique (button,navigation,main, etc.)aria-label: Nom accessible quand le texte visible n'est pas suffisantaria-labelledby: Référence à un élément qui labellisearia-describedby: Référence à une description supplémentaire
États et propriétés
aria-current: Page/étape actuelle (page,step,true)aria-expanded: État d'un élément collapsiblearia-hidden: Cache aux technologies d'assistancearia-live: Annonce les changements (polite,assertive)aria-pressed: État d'un bouton toggle
Formulaires
aria-required: Champ obligatoirearia-invalid: Champ en erreuraria-describedby: Description d'aide ou d'erreur
5. Navigation au clavier
Support des raccourcis standards
Tab/Shift+Tab: Navigation séquentielleEnter/Space: Activation des boutonsEscape: Fermeture des modales/menus- Flèches : Navigation dans les listes/menus
<template>
<div
@keydown.enter="handleActivation"
@keydown.space.prevent="handleActivation"
@keydown.escape="handleClose"
:tabindex="isInteractive ? 0 : -1"
>
<!-- Contenu -->
</div>
</template>
6. Gestion du focus
Focus management
<script setup lang="ts">
import { ref, nextTick } from 'vue'
const focusTarget = ref<HTMLElement>()
const handleModalOpen = async () => {
// Ouvrir la modale
await nextTick()
focusTarget.value?.focus()
}
const handleModalClose = () => {
// Fermer et restaurer le focus
previousFocusElement?.focus()
}
</script>
<template>
<v-dialog v-model="isOpen" @after-enter="handleModalOpen">
<v-card>
<v-card-title>
<h2 ref="focusTarget" tabindex="-1">Titre de la modale</h2>
</v-card-title>
<!-- Contenu -->
</v-card>
</v-dialog>
</template>
7. Couleurs et contrastes
Vérification des contrastes
- Texte normal : ratio minimum 4.5:1
- Texte large (18pt+ ou 14pt+ gras) : ratio minimum 3:1
- Éléments d'interface : ratio minimum 3:1
Ne pas utiliser uniquement la couleur
<!-- ❌ Mauvais : seule la couleur indique l'état -->
<span :class="{ 'text-red': hasError }">{{ message }}</span>
<!-- ✅ Bon : couleur + icône + texte -->
<div class="d-flex align-center">
<v-icon
v-if="hasError"
icon="mdi-alert-circle"
color="error"
:aria-hidden="true"
/>
<span :class="{ 'text-error': hasError }">
{{ hasError ? 'Erreur: ' : '' }}{{ message }}
</span>
</div>
8. Responsive et zoom
Support du zoom jusqu'à 200%
<template>
<!-- Utiliser les unités relatives et les classes Vuetify -->
<v-container class="pa-4">
<v-row>
<v-col cols="12" sm="6" md="4">
<!-- Contenu adaptatif -->
</v-col>
</v-row>
</v-container>
</template>
<style scoped>
/* Éviter les tailles fixes */
.custom-element {
min-height: 44px; /* Taille minimale tactile */
padding: 0.75rem; /* Unités relatives */
}
</style>
9. Internationalisation (i18n)
Attributs de langue
<template>
<div :lang="$i18n.locale">
<h1>{{ $t('page.title') }}</h1>
<!-- Texte dans une autre langue -->
<blockquote lang="en">
"The only way to do great work is to love what you do."
</blockquote>
</div>
</template>
10. Tests d'accessibilité
Outils de test recommandés
- axe-core : Intégration avec Vitest
- Lighthouse : Audit automatique
- NVDA/JAWS : Test avec lecteurs d'écran
- Navigation clavier : Test manuel
// tests/accessibility.spec.ts
import { mount } from '@vue/test-utils'
import { axe, toHaveNoViolations } from 'jest-axe'
import Component from '@/components/Component.vue'
expect.extend(toHaveNoViolations)
describe('Accessibility tests', () => {
it('should not have accessibility violations', async () => {
const wrapper = mount(Component)
const results = await axe(wrapper.element)
expect(results).toHaveNoViolations()
})
})
Checklist finale
Structure et sémantique
- Hiérarchie des titres correcte (h1 > h2 > h3…)
- Landmarks ARIA (
main,nav,aside, etc.) - Listes structurées avec
ul/ol/li - Texte alternatif pour toutes les images informatives
Navigation et interaction
- Tous les éléments interactifs accessibles au clavier
- Ordre de tabulation logique
- Focus visible sur tous les éléments
- Pas de piège à clavier
Formulaires
- Labels associés à tous les champs
- Messages d'erreur descriptifs et liés
- Instructions claires
- Validation accessible
Couleurs et contraste
- Contraste suffisant (4.5:1 minimum)
- Information pas uniquement transmise par la couleur
- Support du mode sombre/clair
Contenu dynamique
- Changements d'état annoncés (
aria-live) - Messages d'erreur/succès accessibles
- Modales et overlays correctement gérés
Mobile et responsive
- Taille minimale tactile 44x44px
- Support du zoom 200%
- Navigation tactile accessible
Dynamic Arguments
$ARGUMENTS
IMPORTANT: Priorisez les améliorations par impact utilisateur et implémentez de manière progressive. Testez chaque modification avec les outils d'accessibilité disponibles.