Bouton désactivé : le piège d'accessibilité que le RGAA rate
En Bref : L'essentiel à retenir
- Le critère RGAA 3.3 déclare non applicable tout composant d'interface inactif, en citant nommément « un bouton avec un attribut
disabled» : son contraste n'est donc pas opposable. - Quatre des sept tests du critère RGAA 11.10 portent sur des messages d'erreur. Sans soumission possible, l'auditeur n'a rien à observer.
- Un critère non applicable sort du dénominateur du taux de conformité : le motif peut laisser le taux intact alors que le formulaire reste infranchissable.
- Ce qui reste opposable : l'indication du caractère obligatoire avant la validation (tests 11.10.1 et 11.10.2) et les suggestions de correction (critère 11.11).
Vous grisez le bouton « Envoyer » tant que le formulaire n'est pas valide. Le motif est partout, et votre dernier audit RGAA ne vous a rien reproché.
Normal. Le référentiel écarte explicitement ce cas. Et c'est précisément ce qui devrait vous intéresser : un bouton désactivé ne fait pas échouer le RGAA, il retire au référentiel la matière qu'il sait examiner. Cet article détaille le mécanisme, liste les critères qui restent réellement opposables sur un formulaire bloqué, et donne le code à écrire selon la situation.
Ce que le RGAA dit vraiment d'un bouton désactivé
Ouvrez le critère RGAA 3.3, qui porte sur le contraste des couleurs utilisées dans les composants d'interface. Sa note de non-applicabilité, dans le texte officiel du RGAA 4.1.2, ne laisse aucune place au doute :
Le critère est non applicable dans ces situations : Composant d'interface inactif (par exemple, un bouton avec un attribut
disabled) sur lequel aucune action n'est possible.
Le référentiel nomme le cas, donne l'exemple exact, et l'écarte. L'argument le plus répandu contre le bouton grisé, « il n'est pas assez contrasté », ne tient donc pas devant un auditeur RGAA.
Le Système de Design de l'État le formule sans détour sur sa fiche d'accessibilité du bouton : « Le texte des boutons désactivés est insuffisamment contrasté. Il ne s'agit néanmoins pas d'une non-conformité au RGAA (cas particulier). »
La démonstration est chiffrée sur cette même page. Le bouton primaire du design system de l'État affiche un ratio de contraste de 2,47:1 à l'état désactivé en thème clair, contre 14,9:1 à l'état par défaut. Un composant officiel, sous le seuil de 3:1, et parfaitement conforme. Le référentiel ne se contredit pas : un composant sur lequel aucune action n'est possible n'a pas à respecter un ratio destiné à rendre une commande identifiable et actionnable.
La logique se tient. Le problème n'est pas là.
Il est ailleurs, et il est plus profond.
Pourquoi les tests de contrôle de saisie ne se déclenchent jamais
Le critère RGAA 11.10 porte sur la pertinence du contrôle de saisie. Il compte sept tests. Regardez ce qu'ils demandent réellement :
- 11.10.1 et 11.10.2 : l'indication du caractère obligatoire, « préalablement à la validation du formulaire »
- 11.10.3 : les messages d'erreur indiquant l'absence de saisie d'un champ obligatoire
- 11.10.4 : les champs portant
aria-invalid="true"et le message associé - 11.10.5 : les instructions de type ou de format, avant validation
- 11.10.6 et 11.10.7 : les messages d'erreur fournissant une instruction de type ou de format
Quatre tests sur sept portent sur des messages d'erreur. Or un message d'erreur suppose une soumission.
Si le bouton est désactivé, l'utilisateur ne soumet jamais. Le formulaire n'émet jamais d'erreur. L'auditeur ouvre sa grille, cherche à provoquer un message pour le tester, et n'a strictement rien à observer sur ces quatre tests.
Même mécanique sur le critère RGAA 7.5, qui demande que les messages de statut soient correctement restitués par les technologies d'assistance. Un blocage silencieux n'émet aucun message de statut. Il n'y a rien à restituer, donc rien à mettre en défaut.
Voilà le cœur du sujet. Le motif ne produit pas d'échec, il produit du vide.
L'effet pervers sur votre taux de conformité
Le taux de conformité RGAA se calcule en divisant les critères validés par les critères applicables. Les critères non applicables sortent du dénominateur, ils ne pèsent ni dans un sens ni dans l'autre.
La conséquence est arithmétique : un motif qui déplace des vérifications vers le non applicable retire du dénominateur des tests qui auraient pu constater un manque. Le taux publié peut rester intact pendant qu'une partie de vos utilisateurs se retrouve devant un formulaire qu'elle ne peut pas valider.
Important
Un taux de conformité mesure ce que le référentiel a pu observer, pas ce qu'un utilisateur parvient à accomplir. Sur un formulaire, les deux se rejoignent rarement sans un test manuel de bout en bout, au clavier puis au lecteur d'écran.
C'est aussi vrai d'un scan automatique que d'un audit humain. Un outil signale des anomalies structurelles, il ne franchit pas un tunnel d'inscription à votre place.
Ce qui reste réellement opposable dans un audit
Le bouton grisé n'est pas hors d'atteinte pour autant. Trois angles tiennent.
L'indication du caractère obligatoire arrive trop tard
Les tests 11.10.1 et 11.10.2 exigent une indication du caractère obligatoire « préalablement à la validation du formulaire ». Le mot compte.
Un formulaire qui répond en grisant son bouton informe après la saisie, jamais avant. Si le caractère obligatoire d'un champ n'est signalé ni dans son étiquette, ni par required ou aria-required="true", le critère échoue, bouton désactivé ou non.
<!-- Mauvais : l'obligation n'existe que dans la logique JS qui grise le bouton -->
<label for="email">Adresse e-mail</label>
<input type="email" id="email" />
<button type="submit" disabled>Envoyer</button>
<!-- Bon : l'obligation est visible dans l'étiquette ET portée par le code -->
<label for="email">Adresse e-mail (obligatoire)</label>
<input type="email" id="email" required aria-required="true" />
<button type="submit">Envoyer</button>
Le détail des règles d'étiquetage et de regroupement figure dans notre guide complet des formulaires accessibles.
Les suggestions de correction manquent
Le critère RGAA 11.11 demande que le contrôle de saisie s'accompagne, si nécessaire, de suggestions facilitant la correction : type de données attendu, format, exemple de valeur.
Un bouton inerte ne suggère rien. Il signale qu'un obstacle existe, sans jamais dire lequel. La bonne pratique consiste à laisser la soumission se produire, puis à restituer l'erreur là où l'utilisateur la cherchera, avec aria-invalid et une description liée. Le sujet est traité en détail dans notre article sur les messages d'erreur accessibles et le critère 11.10.
<!-- Bon : l'erreur nomme le champ, propose un format, et reste liée au champ -->
<label for="tel">Numéro de téléphone</label>
<input
type="tel"
id="tel"
aria-invalid="true"
aria-describedby="tel-erreur"
/>
<p id="tel-erreur" role="alert">
Le numéro de téléphone est invalide. Format attendu : 06 12 34 56 78.
</p>
Le bouton sort de l'ordre de tabulation, et les gestionnaires de mots de passe s'y cassent
Un élément portant l'attribut disabled ne reçoit pas le focus. L'utilisateur au clavier n'atteint jamais la commande, donc jamais l'explication qui pourrait l'accompagner. Les recommandations d'accessibilité d'Orange sont directes sur ce point : « Cette technique n'est pas recommandée pour un bouton de soumission de formulaire pour diverses raisons, notamment : faible contraste, bouton non atteignable au clavier, aucune information donnée à l'utilisateur. »
Par honnêteté, précisons la portée de ce constat. Ce n'est pas une non-conformité au critère RGAA 12.8, qui porte sur la cohérence de l'ordre de tabulation, pas sur le fait qu'un élément en sorte. C'est un défaut d'utilisabilité réel que le référentiel ne sanctionne pas directement.
S'y ajoute un mode de panne que Bogdan Cerovac a documenté en août 2026 : quand la validation n'écoute que l'événement keyup, un mot de passe inséré par un gestionnaire ou par le remplissage automatique du navigateur ne déclenche rien. Le champ est rempli, le bouton reste gris, et l'utilisateur est bloqué sur un formulaire qu'il a pourtant complété.
Attention
Ce scénario touche des personnes qui font exactement ce qu'on leur recommande en matière de sécurité : générer un mot de passe fort et le laisser gérer par un gestionnaire. Le correctif tient en un mot.
// Mauvais : le remplissage automatique ne déclenche pas keyup
champ.addEventListener('keyup', validerFormulaire);
// Bon : input couvre la saisie manuelle ET le remplissage programmatique
champ.addEventListener('input', validerFormulaire);
Côté standards, ce cas relève du critère WCAG 3.3.8 « Accessible Authentication (Minimum) », de niveau AA, ajouté par les WCAG 2.2 (recommandation W3C du 12 décembre 2024). Il n'est pas dans le RGAA 4.1.2, adossé aux WCAG 2.1. Une exigence à anticiper, pas un critère opposable aujourd'hui.
Quoi coder à la place d'un bouton désactivé
Toutes les désactivations ne se valent pas. Le tableau ci-dessous donne le verdict par situation.
| Situation | disabled ? | À faire | Critères |
|---|---|---|---|
| Formulaire pas encore valide | Non | Valider à la soumission | 11.10, 11.11, 7.5 |
| Double soumission | Non | aria-busy + role="status" | 7.5 |
| Étape non atteignable | aria-disabled | Explication liée au bouton | 7.1, 7.5 |
| Champ dépendant d'un choix | Non | Afficher ou masquer le bloc | 11.1 |
| Bout de pagination | aria-disabled | Contrôle annoncé | 7.1 |
| Valeur en lecture seule | Non | readonly | 11.1 |
Pour le cas le plus fréquent, la soumission d'un formulaire, le patron recommandé tient en trois gestes : garder le bouton actif, intercepter la soumission tant que la saisie est incomplète, et annoncer le résultat.
<form id="inscription" novalidate>
<!-- champs du formulaire -->
<div id="synthese" tabindex="-1"></div>
<button type="submit">Créer mon compte</button>
</form>
form.addEventListener('submit', (evenement) => {
const erreurs = validerChamps(form);
if (erreurs.length === 0) return;
evenement.preventDefault();
synthese.textContent = `${erreurs.length} champ(s) à corriger.`;
synthese.focus(); // le focus déplacé fait lire la synthèse
});
Un point de vigilance sur ce patron : ne cumulez pas le déplacement du focus et une région live. Un conteneur qui porte role="alert" et reçoit le focus se fait annoncer deux fois par certains lecteurs d'écran. Choisissez l'un des deux mécanismes, jamais les deux.
Adrian Roselli formule la règle générale dans Don't Disable Form Controls : désactiver doit être le dernier recours, jamais le réflexe, et sa recommandation pour une soumission est exactement ce preventDefault() accompagné d'un message.
Quand la désactivation est justifiée (une étape de tunnel réellement inatteignable, une pagination en bout de liste), préférez aria-disabled, qui marque l'état sans retirer le focus :
<!-- Le bouton reste atteignable au clavier et annoncé comme indisponible -->
<button
type="button"
aria-disabled="true"
aria-describedby="motif-etape"
>
Étape suivante
</button>
<p id="motif-etape">Complétez la livraison pour accéder au paiement.</p>
Si vous préférez la seconde option, celle qui laisse le focus en place et annonce le blocage par une région live, vous entrez cette fois dans le champ du critère 7.5 : le message doit alors porter role="alert". Les règles de restitution sont détaillées dans notre article sur les messages de statut et aria-live.
Sur le marquage des champs obligatoires eux-mêmes, Deque rappelait en août 2026 que l'information doit passer par plusieurs canaux à la fois : le mot dans l'étiquette, required ou aria-required="true" dans le code, et un message d'erreur qui nomme le champ (« L'adresse e-mail est obligatoire ») plutôt qu'un « Saisie invalide » anonyme.
Comment tester le motif sur votre propre formulaire
Trois passes suffisent, et elles ne demandent aucun outil payant.
- Au clavier seul. Rangez la souris. Tabulez jusqu'au bas du formulaire sans rien remplir. Si vous arrivez au pied de page sans avoir rencontré le bouton de soumission, il est
disabledet vos utilisateurs au clavier ne le trouveront pas davantage. Vérifiez au passage que le focus reste visible à chaque étape, comme l'exige le critère RGAA 10.7. Notre testeur de focus restitue la séquence complète si vous voulez la relire à froid, et les règles de style sont détaillées dans notre article sur les indicateurs de focus. - Au gestionnaire de mots de passe. Laissez votre navigateur générer et insérer un mot de passe plutôt que de le taper. Si le bouton reste gris après le remplissage, votre validation écoute
keyupau lieu deinput. - Au lecteur d'écran. Avec NVDA sur Windows ou VoiceOver sur macOS, parcourez le formulaire et tentez de le valider. Une seule question compte : le blocage est-il annoncé à un moment ou à un autre ? Si la réponse est non, rien dans votre page ne dit à cet utilisateur pourquoi il n'avance pas. Notez au passage que la restitution de l'état varie selon l'outil : le Système de Design de l'État relève « estompé » sur VoiceOver, « non disponible » sur NVDA et JAWS, « désactivé » sur Narrateur et TalkBack.
Ces trois passes prennent une dizaine de minutes et couvrent ce qu'aucun scan automatique ne tranche à votre place.
Conclusion
Le bouton désactivé occupe une zone où le référentiel n'a rien à mesurer : le contraste est écarté par le critère 3.3, les tests de message d'erreur n'ont pas de matière, et le taux de conformité n'en porte aucune trace. Un audit peut donc être juste et rassurant sur un formulaire que personne ne franchit.
Le réflexe utile est simple : avant de vous fier au verdict, remplissez votre propre formulaire au clavier, puis avec un lecteur d'écran. Si le bouton reste gris sans que rien ne vous dise pourquoi, vous tenez le défaut.
Pour défricher le reste (étiquettes manquantes, champs obligatoires non signalés, messages de statut absents), analysez votre page : le scan remonte les anomalies structurelles et vous donne la liste des critères à instruire ensuite à la main.
Questions fréquentes
Un bouton désactivé est-il une non-conformité RGAA ?
Non, pas en tant que tel. Le critère RGAA 3.3 sur le contraste des composants d'interface déclare explicitement non applicable un « composant d'interface inactif (par exemple, un bouton avec un attribut disabled) sur lequel aucune action n'est possible ». En revanche, un formulaire qui ne signale pas ses champs obligatoires avant la validation échoue aux tests 11.10.1 et 11.10.2, que son bouton soit grisé ou non.
Faut-il utiliser disabled ou aria-disabled sur un bouton ?
L'attribut disabled retire le bouton de l'ordre de tabulation : l'utilisateur au clavier ne l'atteint plus, et ne peut donc pas découvrir pourquoi l'action est indisponible. L'attribut aria-disabled="true" marque l'état sans retirer le focus, ce qui permet d'associer une explication au bouton. Pour une soumission de formulaire, la meilleure option reste de garder le bouton actif et de valider à la soumission.
Pourquoi mon bouton reste-t-il grisé alors que j'ai rempli le formulaire ?
Le cas classique est une validation branchée sur l'événement keyup. Un gestionnaire de mots de passe ou le remplissage automatique du navigateur insère la valeur sans déclencher de frappe clavier, donc sans déclencher la validation. Écoutez l'événement input, qui couvre à la fois la saisie manuelle et le remplissage programmatique.
Guides RGAA associés
Pour aller plus loin sur les sujets abordés dans cet article, consultez nos fiches techniques :
Chaque champ de formulaire doit avoir une étiquette (label) qui lui est liée explicitement.
L'intitulé de chaque lien doit permettre de comprendre sa destination ou sa fonction, soit par l'intitulé seul, soit par l'intitulé complété par son contexte (phrase, titre de section, item de liste ou cellule de tableau).
Chaque image porteuse d'information doit avoir une alternative textuelle pertinente via l'attribut alt. Les images décoratives doivent avoir un attribut alt vide.
Vidéos RGAA sur ce thème
Pour voir ces sujets en action, regardez les épisodes vidéo correspondants :
Articles similaires
Votre site est-il conforme ?
Ne prenez pas de risques avec l'accessibilité. Lancez un audit complet de votre site en quelques minutes et obtenez un rapport détaillé des corrections à apporter.