Stylus est une extension navigateur qui permet de gérer des styles utilisateur personnalisés.
L'objectif de ces feuilles de style est de visualiser instantanément les problèmes d'accessibilité et de qualité HTML sur n'importe quel site web pendant la phase de développement, sur n'importe quel environnement.
Que l'on soit développeur, testeur ou chef de projet, c'est une solution rapide pour visualiser immédiatement des oublis ou des erreurs.
En plus, c'est joli comme un sapin de Noël !
Installation de Stylus
Stylus est une extension de navigateur qui permet d'injecter des styles CSS personnalisés sur n'importe quel site web.
Téléchargez Stylus pour votre navigateur :
Ajouter les styles dans Stylus
- Installez l'extension Stylus
- Cliquez sur l'icône Stylus dans votre barre d'outils
- Cliquez sur "Gérer" (ou "Manage")
- Cliquez sur "Créer un nouveau style" (ou "Write new style")
- Donnez un nom au style (ex: "Accessibility Shame - Base")
- Copiez-collez le contenu d'une des feuilles CSS fournies
- Dans la section "S'applique à", choisissez "URLs sur le domaine" et mettez une wildcard :
* - Cliquez sur "Enregistrer"
- Répétez l'opération pour l'autre feuille CSS
Les feuilles de style
accessibility-shame-html.css
Taille brute : 4,51 kilo-octets (~1,40 kilo-octets gzip)
:root {
--shame-alert: orange;
--shame-error: red;
--shame-review: yellow;
--shame-warning: orangered;
/* Eléments sans role à la racine de body */
& body > :not(:is(header, main, footer, nav, aside)):not([role]) {
box-shadow: 0 0 0 .5rem var(--shame-alert) inset !important;
filter: contrast(150%) invert();
}
/* Images sans attribut alt, ben tiens ! */
& img:not([alt]) {
padding: 1rem;
box-shadow: 0 0 0 .25rem var(--shame-warning) inset;
transform: scaleY(-1);
}
/* button sans type, c'est un submit. Bonjour les dégâts */
& button:not([type]) {
outline: .25rem dotted var(--shame-review);
}
/* Médias sans accessibilité… ben voyons ! */
& :is(video, audio) {
&:not(:has(track)) {
outline: .25rem solid var(--shame-warning);
}
& track:not([src]) {
outline: .25rem dashed var(--shame-alert);
}
}
/* Lien sans href, crime passionnel */
& a:not([href]) {
outline: .25rem dashed var(--shame-warning);
filter: grayscale(1);
}
/* SVG sans role approprié ni aria-hidden */
& svg:not(:is([role="img"], [role="group"])):not([aria-hidden]) {
box-shadow: 0 0 0 .25rem var(--shame-warning) inset;
transform: scaleY(-1);
}
/* SVG avec role="img" mais sans label accessible */
& svg[role="img"]:not([aria-label]):not([aria-labelledby]) {
box-shadow: 0 0 0 .25rem var(--shame-warning) inset;
transform: scaleY(-1);
}
/* Boutons vides ou sans label accessible → Et on clique sur quoi ? */
& button:not([aria-label]):not([aria-labelledby]):empty {
outline: .25rem solid var(--shame-warning);
}
/* Liens vides */
& a:not([aria-label]):not([aria-labelledby]):empty {
outline: .25rem dashed var(--shame-warning);
}
/* Champs de formulaire sans label associé → Devinez ce qu'il faut saisir ! */
& input:not([type="hidden"]):not([aria-label]):not([aria-labelledby]):not([id]),
& select:not([aria-label]):not([aria-labelledby]):not([id]),
& textarea:not([aria-label]):not([aria-labelledby]):not([id]) {
outline: .25rem solid var(--shame-warning);
}
/* Éléments non-interactifs clickables SANS sémantique correcte (crime majeur) */
& :not(a):not(button):not(input):not(select):not(textarea):not(summary)[onclick]:not([role="button"]):not([role="link"]):not([tabindex]) {
outline: .25rem solid var(--shame-error);
filter: hue-rotate(180deg);
}
/* Éléments non-interactifs clickables avec role mais SANS tabindex */
& :not(a):not(button):not(input):not(select):not(textarea):not(summary)[onclick][role="button"]:not([tabindex]),
& :not(a):not(button):not(input):not(select):not(textarea):not(summary)[onclick][role="link"]:not([tabindex]) {
outline: .25rem solid var(--shame-alert);
filter: brightness(1.5);
}
/* Éléments non-interactifs clickables avec tabindex différent de 0 */
& :not(a):not(button):not(input):not(select):not(textarea):not(summary)[onclick][tabindex]:not([tabindex="0"]) {
outline: .25rem dashed var(--shame-alert);
}
/* iframes mal configurées */
& iframe {
/* iframe sans title → Contenu mystère */
&:not([title]):not([aria-label]) {
outline: .25rem solid var(--shame-warning);
}
/* Sans src (mais pourquoi faire ?) */
&:not([src]):not([srcdoc]) {
outline: .25rem solid var(--shame-error);
filter: saturate(3);
}
/* Attributs obsolètes (HTML5, ça vous parle ?) */
&:is([frameborder], [scrolling], [marginwidth], [marginheight], [align]) {
outline: .25rem dashed var(--shame-review);
filter: hue-rotate(90deg);
}
}
/* Tableaux sans caption ni aria-label */
& table:not([aria-label]):not([aria-labelledby]):not(:has(caption)) {
outline: .15rem dashed var(--shame-review);
}
/* width/height avec unités (invalide en HTML) */
& :is(img, video, iframe, canvas):is([width*="px"], [width*="%"], [width*="em"], [width*="rem"], [width*="vw"]),
& :is(img, video, iframe, canvas):is([height*="px"], [height*="%"], [height*="em"], [height*="rem"], [height*="vh"]) {
outline: .25rem dashed var(--shame-review);
filter: brightness(1.1) saturate(1.2);
}
/* Attributs dépréciés (Bienvenue en 2026 !) */
& [align]:not(table):not(td):not(th),
& [bgcolor],
& [valign],
& [cellpadding],
& [cellspacing],
& [hspace],
& [vspace],
& font,
& center,
& marquee {
outline: .25rem dotted var(--shame-review);
filter: sepia(1);
}
}
Télécharger kortic-shame-html.css
Cette feuille détecte les erreurs fondamentales de structure HTML avec, par exemple :
Images et SVG
- Images sans attribut
alt→ affichage inversé avec bordure rouge orangé - SVG sans
role="img"niaria-hidden→ signalés visuellement - SVG avec
role="img"mais sans label → marqués comme problématiques
Éléments interactifs
- Boutons et liens vides sans label → contour rouge orangé
- Éléments cliquables sans sémantique appropriée (div/span/etc. avec
onclick) :- Sans
rolenitabindex→ contour rouge vif + rotation (crime majeur) - Avec
rolemais sanstabindex→ contour orange - Avec mauvaise valeur de
tabindex→ contour orange pointillé
- Sans
Formulaires
- Inputs sans label associé → contour rouge orangé
Autres éléments
- iframes sans
title→ contour rouge orangé - Tableaux sans
captionniaria-label→ contour rouge orangé
accessibility-shame-aria.css
Taille brute : 3,07 kilo-octets (~981,00 octets gzip)
:root {
--shame-alert: orange;
--shame-error: red;
--shame-review: yellow;
--shame-warning: orangered;
/* aria-label invalide sur éléments statiques sans rôle explicite */
/* Rôles interdits ARIA 1.2 : generic, paragraph, emphasis, strong, etc. */
/* Rôles interdits ARIA 1.3 (draft) : mark, time */
& :is(div, span, p, b, i, u, s, strong, em, pre, q, samp, small, bdi, bdo, data, del, ins, sub, sup, code, mark, time):not([role])[aria-label] {
outline: .25rem solid red;
filter: saturate(3);
}
/* aria-hidden="true" sur des éléments focusables (grave) */
& [aria-hidden="true"]:is(a[href], button, input, select, textarea, summary, [tabindex]:not([tabindex="-1"])) {
outline: .25rem solid var(--shame-error);
filter: hue-rotate(180deg) saturate(2);
}
/* aria-hidden="true" sur des éléments interactifs avec onclick */
& [aria-hidden="true"][onclick] {
outline: .25rem solid var(--shame-error);
box-shadow: 0 0 1rem var(--shame-error);
}
/* role="button" sans aria-label sur des éléments vides */
& [role="button"]:not([aria-label]):not([aria-labelledby]):not([title]):not(img):not(svg):empty {
outline: .25rem dashed var(--shame-warning);
}
/* role="img" sans aria-label ni aria-labelledby */
& [role="img"]:not([aria-label]):not([aria-labelledby]) {
outline: .25rem solid var(--shame-warning);
}
/* role="presentation" ou role="none" sur des éléments interactifs */
& :is(a, button, input, select, textarea)[role="presentation"],
& :is(a, button, input, select, textarea)[role="none"] {
outline: .25rem solid var(--shame-error);
filter: invert(1);
}
/* tabindex positif (crime contre l'ordre de tabulation) */
& [tabindex]:not([tabindex="0"]):not([tabindex="-1"]):not([tabindex^="-"]) {
outline: .25rem dashed var(--shame-error);
filter: brightness(1.5) saturate(2);
}
/* aria-live sans role approprié */
& [aria-live]:not(:is([role="status"], [role*="alert"], [role*="log"], [role="marquee"], [role="timer"], [role="tooltip"])) {
outline: .25rem dotted var(--shame-alert);
}
/* role redondant avec l'élément natif */
& button[role="button"],
& a[role="link"],
& input[role="textbox"],
& nav[role="navigation"],
& main[role="main"],
& aside[role="complementary"] {
outline: .25rem dotted var(--shame-review);
filter: brightness(1.2);
}
/* aria-required sur des éléments non-form */
& :not(input):not(select):not(textarea)[aria-required] {
outline: .25rem dotted var(--shame-alert);
}
/* aria-expanded sans contrôle associé */
& [aria-expanded]:not([aria-controls]) {
outline: .25rem dashed var(--shame-alert);
}
/* role="combobox" sans aria-expanded */
& [role="combobox"]:not([aria-expanded]) {
outline: .25rem dashed var(--shame-alert);
}
/* aria-autocomplete sur des éléments sans role approprié */
& [aria-autocomplete]:not([role="combobox"]):not([role="listbox"]):not([role="textbox"]) {
outline: .25rem dashed var(--shame-alert);
}
}
Télécharger kortic-shame-aria.css
Cette feuille détecte les mauvaises pratiques ARIA avec, par exemple :
Erreurs critiques
aria-hidden="true"sur des éléments focusables → contour rouge + rotationaria-hidden="true"sur des éléments aveconclick→ contour + ombre rougerole="presentation"ourole="none"sur des éléments interactifs → contour rouge + inversiontabindexpositif (> 0) → contour rouge pointillé (perturbe l'ordre de navigation)
Problèmes de labeling
aria-labelsur desdiv/spansansrole→ contour rouge- Éléments avec
role="button"ourole="img"sans label → contour rouge orangé
Redondances et incohérences
- Roles redondants avec l'élément natif (ex:
<button role="button">) → contour jaune aria-livesans role approprié → contour orange pointilléaria-requiredsur des éléments non-formulaires → contour orange pointilléaria-expandedsansaria-controls→ contour orange pointillé
Utilisation
- Activez les styles via l'icône Stylus dans votre navigateur
- Naviguez sur votre site en développement ou en production
- Repérez les éléments visuellement modifiés :
- Contours rouges = erreurs critiques à corriger en priorité
- Contours orange = problèmes importants
- Contours jaunes = à revoir (redondances, optimisations)
- Inspectez le code de l'élément problématique pour comprendre l'erreur
- Corrigez le HTML/ARIA en suivant les bonnes pratiques WCAG
Personnalisation
Les couleurs sont définies via des variables CSS au début de chaque fichier :
:root {
--shame-alert: orange;
--shame-error: red;
--shame-review: yellow;
--shame-warning: orangered;
}
Vous pouvez modifier ces valeurs pour adapter les couleurs à vos préférences.
Limitations
Ces styles CSS ne remplacent pas :
- Une revue d'accessibilité manuelle complète ;
- Des tests avec des lecteurs d'écran.
Ils constituent un premier filtre visuel rapide pour détecter les erreurs évidentes avant des tests plus approfondis.
Contribution
Ces feuilles de style sont évolutives. N'hésitez pas à proposer de nouvelles règles de détection ou des améliorations.
Alternative temps réel
Si on veut encore mieux, il est tout à fait possible de vérifier en permanence (ou presque) l'état du front.
Par exemple, l'oubli de l'attribut alt sur une image ou de type sur un button, c'est plus que fréquent. Voir quasiment pas rare ! Une version complète de ces styles peut être chargée en conditionnant l'environnement de développement local, de build QA, etc.
Exemple ici-même.
Activer le debug pour visualiser… et hop !
La complète
Attention, ça peut faire boule à facettes ! Mais au moins ça motive.
Taille brute : 7,60 kilo-octets (~2,11 kilo-octets gzip)
/**
* Source Kortic https://www.kortic.com/le-blog-kortic/2026/02/06/utiliser-l-extension-stylus-pour-reperer-les-premieres-erreurs-d-accessibilite.html
* licence Creative Commons CC BY-NC-SA 4.0
* https://creativecommons.org/licenses/by-nc-sa/4.0/deed.fr
* Attribution, partage dans les mêmes conditions, pas d'utilisation commerciale
*/
:root.shame-code {
--shame-alert: orange;
--shame-error: red;
--shame-review: yellow;
--shame-warning: orangered;
/*
*
* PARTIE 1
* A11Y Shame
*/
/* aria-label invalide sur éléments statiques sans rôle explicite */
/* Rôles interdits ARIA 1.2 : generic, paragraph, emphasis, strong, etc. */
/* Rôles interdits ARIA 1.3 (draft) : mark, time */
& :is(div, span, p, b, i, u, s, strong, em, pre, q, samp, small, bdi, bdo, data, del, ins, sub, sup, code, mark, time):not([role])[aria-label] {
outline: .25rem solid red;
filter: saturate(3);
}
/* aria-hidden="true" sur des éléments focusables (grave) */
& [aria-hidden="true"]:is(a[href], button, input, select, textarea, summary, [tabindex]:not([tabindex="-1"])) {
outline: .25rem solid var(--shame-error);
filter: hue-rotate(180deg) saturate(2);
}
/* aria-hidden="true" sur des éléments interactifs avec onclick */
& [aria-hidden="true"][onclick] {
outline: .25rem solid var(--shame-error);
box-shadow: 0 0 1rem var(--shame-error);
}
/* role="button" sans aria-label sur des éléments vides */
& [role="button"]:not([aria-label]):not([aria-labelledby]):not([title]):not(img):not(svg):empty {
outline: .25rem dashed var(--shame-warning);
}
/* role="img" sans aria-label ni aria-labelledby */
& [role="img"]:not([aria-label]):not([aria-labelledby]) {
outline: .25rem solid var(--shame-warning);
}
/* role="presentation" ou role="none" sur des éléments interactifs */
& :is(a, button, input, select, textarea)[role="presentation"],
& :is(a, button, input, select, textarea)[role="none"] {
outline: .25rem solid var(--shame-error);
filter: invert(1);
}
/* tabindex positif (crime contre l'ordre de tabulation) */
& [tabindex]:not([tabindex="0"]):not([tabindex="-1"]):not([tabindex^="-"]) {
outline: .25rem dashed var(--shame-error);
filter: brightness(1.5) saturate(2);
}
/* aria-live sans role approprié */
& [aria-live]:not(:is([role="status"], [role*="alert"], [role*="log"], [role="marquee"], [role="timer"], [role="tooltip"])) {
outline: .25rem dotted var(--shame-alert);
}
/* role redondant avec l'élément natif */
& button[role="button"],
& a[role="link"],
& input[role="textbox"],
& nav[role="navigation"],
& main[role="main"],
& aside[role="complementary"] {
outline: .25rem dotted var(--shame-review);
filter: brightness(1.2);
}
/* aria-required sur des éléments non-form */
& :not(input):not(select):not(textarea)[aria-required] {
outline: .25rem dotted var(--shame-alert);
}
/* aria-expanded sans contrôle associé */
& [aria-expanded]:not([aria-controls]) {
outline: .25rem dashed var(--shame-alert);
}
/* role="combobox" sans aria-expanded */
& [role="combobox"]:not([aria-expanded]) {
outline: .25rem dashed var(--shame-alert);
}
/* aria-autocomplete sur des éléments sans role approprié */
& [aria-autocomplete]:not([role="combobox"]):not([role="listbox"]):not([role="textbox"]) {
outline: .25rem dashed var(--shame-alert);
}
/*
*
* PARTIE 2
* HTML Shame
*/
/* Eléments sans role à la racine de body */
& body > :not(:is(header, main, footer, nav, aside, dialog)):not([role]):not([aria-hidden="true"]) {
box-shadow: 0 0 0 .5rem var(--shame-alert) inset !important;
filter: contrast(150%) invert();
}
/* Images sans attribut alt, ben tiens ! */
& img:not([alt]) {
padding: 1rem;
box-shadow: 0 0 0 .25rem var(--shame-warning) inset;
transform: scaleY(-1);
}
/* button sans type, c'est un submit. Bonjour les dégâts */
& button:not([type]) {
outline: .25rem dotted var(--shame-review);
}
/* Médias sans accessibilité… ben voyons ! */
& :is(video, audio) {
&:not(:has(track)) {
outline: .25rem solid var(--shame-warning);
}
& track:not([src]) {
outline: .25rem dashed var(--shame-alert);
}
}
/* Lien sans href, crime passionnel */
& a:not([href]) {
outline: .25rem dashed var(--shame-warning);
filter: grayscale(1);
}
/* SVG sans role approprié ni aria-hidden */
& svg:not(:is([role="img"], [role="group"])):not([aria-hidden]) {
box-shadow: 0 0 0 .25rem var(--shame-warning) inset;
transform: scaleY(-1);
}
/* SVG avec role="img" mais sans label accessible */
& svg[role="img"]:not([aria-label]):not([aria-labelledby]) {
box-shadow: 0 0 0 .25rem var(--shame-warning) inset;
transform: scaleY(-1);
}
/* Boutons vides ou sans label accessible → Et on clique sur quoi ? */
& button:not([aria-label]):not([aria-labelledby]):empty {
outline: .25rem solid var(--shame-warning);
}
/* Liens vides */
& a:not([aria-label]):not([aria-labelledby]):empty {
outline: .25rem dashed var(--shame-warning);
}
/* Champs de formulaire sans label associé → Devinez ce qu'il faut saisir ! */
& input:not([type="hidden"]):not([aria-label]):not([aria-labelledby]):not([id]),
& select:not([aria-label]):not([aria-labelledby]):not([id]),
& textarea:not([aria-label]):not([aria-labelledby]):not([id]) {
outline: .25rem solid var(--shame-warning);
}
/* Éléments non-interactifs clickables SANS sémantique correcte (crime majeur) */
& :not(a):not(button):not(input):not(select):not(textarea):not(summary)[onclick]:not([role="button"]):not([role="link"]):not([tabindex]) {
outline: .25rem solid var(--shame-error);
filter: hue-rotate(180deg);
}
/* Éléments non-interactifs clickables avec role mais SANS tabindex */
& :not(a):not(button):not(input):not(select):not(textarea):not(summary)[onclick][role="button"]:not([tabindex]),
& :not(a):not(button):not(input):not(select):not(textarea):not(summary)[onclick][role="link"]:not([tabindex]) {
outline: .25rem solid var(--shame-alert);
filter: brightness(1.5);
}
/* Éléments non-interactifs clickables avec tabindex différent de 0 */
& :not(a):not(button):not(input):not(select):not(textarea):not(summary)[onclick][tabindex]:not([tabindex="0"]) {
outline: .25rem dashed var(--shame-alert);
}
/* iframes mal configurées */
& iframe {
/* iframe sans title → Contenu mystère */
&:not([title]):not([aria-label]) {
outline: .25rem solid var(--shame-warning);
}
/* Sans src (mais pourquoi faire ?) */
&:not([src]):not([srcdoc]) {
outline: .25rem solid var(--shame-error);
filter: saturate(3);
}
/* Attributs obsolètes (HTML5, ça vous parle ?) */
&:is([frameborder], [scrolling], [marginwidth], [marginheight], [align]) {
outline: .25rem dashed var(--shame-review);
filter: hue-rotate(90deg);
}
}
/* Tableaux sans caption ni aria-label */
& table:not([aria-label]):not([aria-labelledby]):not(:has(caption)) {
outline: .15rem dashed var(--shame-review);
}
/* width/height avec unités (invalide en HTML) */
& :is(img, video, iframe, canvas):is([width*="px"], [width*="%"], [width*="em"], [width*="rem"], [width*="vw"]),
& :is(img, video, iframe, canvas):is([height*="px"], [height*="%"], [height*="em"], [height*="rem"], [height*="vh"]) {
outline: .25rem dashed var(--shame-review);
filter: brightness(1.1) saturate(1.2);
}
/* Attributs dépréciés (Bienvenue en 2026 !) */
& [align]:not(table):not(td):not(th),
& [bgcolor],
& [valign],
& [cellpadding],
& [cellspacing],
& [hspace],
& [vspace],
& font,
& center,
& marquee {
outline: .25rem dotted var(--shame-review);
filter: sepia(1);
}
}
Télécharger kortic-shame-html-aria.css
That's all, Folks !