Utilisation de djLint

djLint inclut de nombreuses règles pour vérifier le style et la validité de vos modèles. Profitez pleinement du linter en le configurant pour utiliser un profil prédéfini pour la langue du modèle de votre choix.

djlint /path/to/templates --lint

# with custom extensions
djlint /path/to/templates -e html.dj --profile=django

# or to file
djlint /path/to/this.html.j2  --profile=jinja

Activation ou désactivation des règles

La plupart des règles sont activées par défaut. Les règles peuvent être désactivées en ligne de commande avec l’option --ignore. Les règles peuvent être activées avec l’option --include.

Par exemple :

djlint . --lint --include=H006,H017 --ignore=H013,H015

Cela peut également se faire par l’intermédiaire de l’option Configuration fichier.

Rules

Code Signification Défaut
T001 Les variables doivent être entourées d’un espace. Ex : {{ this }} ✔️
T002 Les doubles quotes doivent être utilisées dans les balises. Ex : {% extends "this.html" %} ✔️
T003 Le bloc de fin doit avoir un nom. Ex : {% endblock body %}. -
D004 (Django) Les urls statiques doivent suivre le modèle {% static path/to/file %}. ✔️
J004 (Jinja) Les urls statiques doivent suivre le modèle { url_for('static'..) }}. ✔️
H005 La balise Html doit avoir un attribut lang non vide. ✔️
H006 La balise img doit avoir les attributs height et width. -
H007 LA BALISE <!DOCTYPE ... > doit être présent avant la balise html. ✔️
H008 Les attributs doivent être entre guillemets. ✔️
H009 Les noms de balises doivent être en minuscules. ✔️
H010 Les noms d’attributs doivent être en minuscules. ✔️
H011 Les valeurs des attributs doivent être citées. ✔️
H012 Il ne doit pas y avoir d’espace autour de l’attribut =. ✔️
H013 La balise img doit avoir des attributs alt. ✔️
H014 Plus de lignes vides que la configuration n’en garde. ✔️
H015 Les balises “h” doivent être suivies d’un retour à la ligne. ✔️
H016 Balise title manquante dans le html. ✔️
H017 Les balises vides doivent être auto-fermantes (incompatible avec : H018). -
D018 (Django) Les liens internes doivent utiliser le modèle {% url ... %}. ✔️
H018 Les balises vides sont auto-fermantes par nature et doivent se terminer par “>”, et non “/>” (incompatible avec : H017). -
J018 (Jinja) Les liens internes doivent utiliser le modèle {% url ... %}. ✔️
H019 Remplacez javascript:abc() par l’événement on_ et l’url réelle. ✔️
H020 Couple de balises vide trouvé. Envisagez de le supprimer. ✔️
H021 Les styles en ligne doivent être évités. ✔️
H022 Utilisez HTTPS pour les liens externes. ✔️
H023 N’utilisez pas de références d’entités. ✔️
H024 Omettre le type sur les scripts et les styles. ✔️
H025 La balise semble être orpheline. ✔️
H026 Les balises id et class vides peuvent être supprimées. ✔️
T027 Chaîne non fermée trouvée dans la syntaxe du modèle. ✔️
T028 Envisagez d’utiliser des balises sans espace à l’intérieur des valeurs d’attributs. {%- if/for -%} -
H029 Pensez à utiliser des valeurs de méthode de formulaire en minuscules. ✔️
H030 Pensez à ajouter une méta-description. ✔️
T032 Espace blanc supplémentaire trouvé dans les balises du modèle. ✔️
H033 Espace supplémentaire dans l’action du formulaire. ✔️
T034 Aviez-vous l’intention d’utiliser {% … %} au lieu de {% … }% ? ✔️
H036 Évitez d’utiliser les balises br. ✔️
H037 Attribut en double trouvé. ✔️
T038 La balise de bloc n’a pas de balise de fin correspondante. ✔️
T039 Balise de template non fermée trouvée. ✔️
T040 Nom de template manquant ou vide dans une balise extends ou include. ✔️
H041 La balise est fermée dans un bloc de template différent de celui où elle a été ouverte. ✔️
T041 La balise extends devrait être la première balise du template. ✔️
H042 L’attribut for d’un label n’a pas d’id correspondant dans ce fichier. ✔️
T042 Le contenu hors d’un bloc n’est pas rendu dans un template qui en étend un autre. ✔️
H043 La balise button devrait avoir un attribut type. ✔️
T043 Le nom de bloc est utilisé plus d’une fois dans le template. ✔️
H044 Un thead ne devrait pas mélanger des cellules th et td. ✔️
T044 La balise de sortie contient un mot-clé d’instruction ; utilisez une balise de bloc. ✔️
H045 La balise iframe devrait avoir un attribut title. ✔️
T045 Une balise de template dans un commentaire html s’exécute quand même ; utilisez un commentaire de template pour la désactiver. ✔️
H046 La valeur de tabindex ne devrait pas être positive. ✔️
H047 aria-hidden ne devrait pas être posé sur un élément focusable. ✔️
H048 Cet attribut aria n’est pas défini par la spécification. ✔️
H049 Le viewport ne devrait pas empêcher le zoom de la page. ✔️
H050 Élément obsolète, à remplacer. ✔️
H051 Le rôle n’est pas un de ceux qu’ARIA définit pour le balisage. ✔️
H052 Meta refresh ne doit pas recharger ni rediriger la page après un délai. ✔️
H053 L’id est utilisé plus d’une fois dans le fichier. ✔️
H054 Un élément interactif ne devrait pas être imbriqué dans un autre. ✔️
H055 L’attribut lang devrait être une étiquette de langue comme en ou pt-BR. ✔️
H056 Src ne devrait pas être vide. ✔️
H057 La vidéo devrait avoir une piste de sous-titres. ✔️

Modèles de code

La première lettre d’un code suit le modèle :

  • D : s’applique spécifiquement à Django
  • H : s’applique au html
  • J : s’applique spécifiquement à Jinja
  • M : s’applique spécifiquement à Handlebars
  • N : s’applique spécifiquement à Nunjucks
  • T : s’applique généralement aux modèles

Détails des règles

T001

Les variables doivent être entourées d'un espace. Ex : {{ this }}

Une syntaxe de modèle comme {{user.name}} sans espaces intérieurs est plus difficile à parcourir et à comparer dans les diffs, et un espacement incohérent à travers une base de code rend les refactorisations à base de grep (recherche d’une variable ou d’une balise) peu fiables, car la même expression existe sous plusieurs orthographes. Les guides de style de Django comme de Jinja écrivent {{ var }} et {% tag %} avec des espaces simples.

Non appliquée aux profils handlebars et golang.

À éviter :

{{user.name}}

À faire :

{{ user.name }}

T002

Les doubles quotes doivent être utilisées dans les balises. Ex : {% extends "this.html" %}

Mélanger guillemets simples et doubles dans les balises de modèle ({% extends %}, {% include %}, {% with %}, {% trans %}, {% now %}) fait apparaître le même nom de modèle sous deux orthographes : les recherches et les renommages en masse manquent alors la moitié des occurrences. Standardiser sur les guillemets doubles garde les arguments des balises cohérents avec les guillemets des attributs HTML dans le reste du fichier.

Les guillemets simples à l’intérieur des valeurs d’attributs HTML (par exemple <span title="{% trans 'x' %}">) ne sont pas signalés, puisque les guillemets doubles de l’attribut y imposent des guillemets simples.

Avec quote_style = "single", la règle s’inverse et réclame des guillemets simples, que le formateur écrit également.

--reformat réécrit ces guillemets pour vous : un signalement ne demande jamais de travail manuel.

À éviter :

{% extends 'base.html' %}

À faire :

{% extends "base.html" %}

T003

Le bloc de fin doit avoir un nom. Ex : {% endblock body %}.

Lorsqu’un {% block %} s’étend sur de nombreuses lignes ou que des blocs sont imbriqués, un simple {% endblock %} ne donne aucun indice sur le bloc qu’il ferme : il est alors facile de fermer le mauvais bloc en éditant ; les modèles enfants remplacent alors le mauvais contenu. Nommer le endblock documente l’appariement et permet à djLint comme à Django (qui lève une TemplateSyntaxError en cas de nom de endblock non concordant) de détecter un bloc fermé au mauvais endroit. Les erreurs d’appariement (blocs non fermés, endblock orphelins et noms non concordants) sont des vérifications de justesse assurées par T038.

Désactivée par défaut ; à activer avec --include=T003. --name-endblocks écrit le nom pour vous : un signalement ne demande jamais de travail manuel.

Un nom n’est pas requis lorsque le bloc s’ouvre et se ferme sur la même ligne, par exemple {% block title %}``{% endblock %}.

À éviter :

{% block content %}
<p>hello</p>
{% endblock %}

À faire :

{% block content %}
<p>hello</p>
{% endblock content %}

D004

(Django) Les urls statiques doivent suivre le modèle {% static path/to/file %}.

Coder en dur les chemins /static/ contourne la balise {% static %} de Django : les modèles cassent dès que STATIC_URL change (par exemple lors du déplacement des ressources vers un CDN ou d’un déploiement sous un sous-chemin) et ne récupèrent jamais les noms de fichiers hachés de ManifestStaticFilesStorage, ce qui provoque des erreurs 404 ou des ressources obsolètes en cache en production. La règle cherche le préfixe littéral /static/ : un projet qui sert ses fichiers statiques depuis un autre chemin n’est pas couvert.

À éviter :

<link rel="stylesheet" href="/static/css/style.css">

À faire :

<link rel="stylesheet" href="{% static 'css/style.css' %}">

J004

(Jinja) Les urls statiques doivent suivre le modèle { url_for('static'..) }}.

Coder en dur les chemins /static/ contourne url_for(‘static’, …) de Flask/Jinja : les ressources renvoient des 404 lorsque l’application est montée sous un préfixe d’URL ou que le dossier ou l’hôte statique change, et les chaînes de requête anti-cache ajoutées par le framework sont perdues. La règle cherche le préfixe littéral /static/ : un projet qui sert ses fichiers statiques depuis un autre chemin n’est pas couvert.

À éviter :

<link rel="stylesheet" href="/static/css/style.css">

À faire :

<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">

H005

La balise Html doit avoir un attribut lang non vide.

Sans attribut lang sur <html>, les lecteurs d’écran devinent les règles de prononciation et peuvent lire la page dans la mauvaise langue, et les navigateurs ne peuvent pas proposer correctement la traduction, la césure ou les guillemets adaptés à la locale. Déclarer la langue de la page correspond au critère de succès 3.1.1 de WCAG 2.1 (niveau A).

lang="" et un lang sans valeur signifient tous deux que la langue est inconnue : ils sont signalés comme un attribut manquant.

À éviter :

<!DOCTYPE html>
<html>
</html>

À faire :

<!DOCTYPE html>
<html lang="en">
</html>

H006

La balise img doit avoir les attributs height et width.

Désactivée par défaut ; à activer avec --include=H006.

Lorsqu’une <img> n’a ni width ni height, le navigateur ne peut pas réserver l’espace avant le téléchargement de l’image, donc le contenu environnant saute pendant le chargement des images. Ce décalage de mise en page dégrade le Cumulative Layout Shift (une métrique Core Web Vitals) et peut amener les utilisateurs à cliquer au mauvais endroit pendant que la page se stabilise.

À éviter :

<img src="cat.png" alt="Cat">

À faire :

<img src="cat.png" alt="Cat" width="120" height="80">

H007

LA BALISE <!DOCTYPE ... > doit être présent avant la balise html.

Sans <!DOCTYPE> avant la balise <html>, les navigateurs rendent la page en mode quirks, en imitant le modèle de boîte et les comportements de mise en page hérités, si bien que le CSS se rend de manière incohérente d’un navigateur à l’autre. Les balises de modèle et les commentaires avant le doctype ne posent pas de problème ; seule la balise <html> elle-même doit en être précédée.

À éviter :

<html lang="en">
</html>

À faire :

<!DOCTYPE html>
<html lang="en">
</html>

H008

Les attributs doivent être entre guillemets.

Des styles de guillemets mélangés rendent les valeurs d’attributs plus difficiles à parcourir et à rechercher, et les valeurs entre guillemets simples cassent dès que le contenu contient une apostrophe. Les guillemets doubles sont la convention utilisée par les spécifications HTML, les formateurs et la plupart des guides de style : les adopter garde les modèles cohérents avec l’écosystème.

À éviter :

<div class='content'></div>

À faire :

<div class="content"></div>

H009

Les noms de balises doivent être en minuscules.

Les analyseurs HTML acceptent les noms de balises en majuscules, mais les sérialisations XHTML et XML sont sensibles à la casse et les rejettent, et une casse mélangée rend la recherche de texte et la relecture des diffs peu fiables (un grep sur <h1> manque <H1>). Des noms de balises en minuscules gardent les modèles portables et cohérents.

À éviter :

<H1>Welcome</H1>

À faire :

<h1>Welcome</h1>

H010

Les noms d'attributs doivent être en minuscules.

Les noms d’attributs en majuscules sont invalides dans les sérialisations XHTML/XML et mettent en échec la recherche de texte dans les modèles (un grep sur src= manque SRC=). Le DOM normalise de toute façon les noms d’attributs HTML en minuscules, donc les orthographes en majuscules ajoutent de l’incohérence sans aucun bénéfice.

À éviter :

<img SRC="cat.png" alt="Cat" width="120" height="80">

À faire :

<img src="cat.png" alt="Cat" width="120" height="80">

H011

Les valeurs des attributs doivent être citées.

Les valeurs d’attributs sans guillemets s’arrêtent au premier espace : une valeur comme class=btn primary perd silencieusement tout ce qui suit l’espace (le navigateur traite “primary” comme un attribut booléen séparé). Les valeurs issues de variables de modèle sont particulièrement fragiles : tout espace, “=” ou “>” rendu corrompt la balise. Les guillemets rendent la limite de la valeur explicite et sûre.

À éviter :

<div class=test></div>

À faire :

<div class="test"></div>

H012

Il ne doit pas y avoir d'espace autour de l'attribut =.

Avec des espaces autour de “=”, la balise se lit comme trois éléments séparés, et elle est à une modification près de se disloquer : un retour à la ligne ou une troncature au milieu laisse un attribut booléen nu plus du texte parasite. Garder name=“value” d’un seul tenant est aussi ce que supposent les outils texte simples (grep, rechercher-remplacer) : un espacement hétérogène rend les attributs difficiles à trouver et à refactoriser de manière fiable.

À éviter :

<div class = "test"></div>

À faire :

<div class="test"></div>

H013

La balise img doit avoir des attributs alt.

Sans attribut alt, les lecteurs d’écran annoncent le nom de fichier de l’image, ou rien du tout, ce qui enfreint WCAG 1.1.1 (Contenu non textuel). Le texte alternatif est aussi ce que voient les utilisateurs lorsque l’image ne se charge pas. Les images décoratives doivent porter un alt=“” explicitement vide pour que les technologies d’assistance sachent les ignorer ; cela satisfait également cette règle.

Un alt sans valeur équivaut à alt="", le cas de l’image décorative : il est accepté.

À éviter :

<img src="cat.jpg" height="200" width="300">

À faire :

<img src="cat.jpg" height="200" width="300" alt="A sleeping cat">

H014

Plus de 2 lignes vides.

Les suites de lignes vides n’ont aucun effet sur la page rendue (le HTML replie les espaces) mais alourdissent les modèles et créent des diffs bruyants quand les lignes voisines changent. Le formateur de djLint les supprime entièrement par défaut (en conservant au plus max_blank_lines lignes vides, valeur par défaut 0), donc des suites restantes indiquent du code non formaté.

À éviter :

<div>one</div>


<p>two</p>

À faire :

<div>one</div>

<p>two</p>

H015

Les balises "h" doivent être suivies d'un retour à la ligne.

Les titres sont des repères de niveau bloc qui définissent le plan du document ; entasser l’élément suivant sur la même ligne que la balise h fermante masque cette structure dans le source et fait apparaître toute modification de l’un des deux éléments comme un changement des deux dans les diffs. Un saut de ligne après chaque titre garde la structure visuelle du modèle alignée sur le plan rendu.

À éviter :

<h1>Heading</h1><p>Intro text.</p>

À faire :

<h1>Heading</h1>
<p>Intro text.</p>

H016

Balise title manquante dans le html.

La spécification HTML exige un élément title dans chaque document. Sans lui, les onglets du navigateur, les favoris et l’historique affichent une URL brute au lieu d’un nom de page, les moteurs de recherche perdent l’étiquette principale de la page, et les utilisateurs de lecteurs d’écran perdent la première chose annoncée au chargement, un manquement à WCAG 2.4.2 (Titre de page, niveau A).

Ne se déclenche que sur les fichiers contenant un document <html></html> complet, donc les partiels et les modèles enfants qui étendent une base ne sont jamais signalés. Les coquilles de SPA qui définissent le titre côté client ont quand même besoin d’un <title> statique : c’est ce qui apparaît au premier rendu, pour les robots d’indexation et lorsque JavaScript échoue.

À éviter :

<html lang="en">
<body>Content</body>
</html>

À faire :

<html lang="en">
<head>
<title>My page</title>
</head>
<body>Content</body>
</html>

H017

Les balises vides doivent être auto-fermantes (incompatible avec : H018).

Les modèles qui doivent aussi être analysés comme du XML/XHTML (ou alimenter des outils basés sur XML) rejettent les éléments vides écrits sans barre oblique de fermeture, et mélanger <br> et <br /> à travers une base de code produit des diffs incohérents. Cette règle impose la convention de style XHTML afin que chaque élément vide soit fermé de la même manière.

Désactivée par défaut ; à activer avec --include=H017. Mutuellement exclusive avec H018 : n’activez qu’une seule des deux conventions.

À éviter :

<br>
<meta charset="utf-8">

À faire :

<br />
<meta charset="utf-8" />

D018

(Django) Les liens internes doivent utiliser le modèle {% url ... %}.

Les URLs internes codées en dur deviennent silencieusement obsolètes lorsque le chemin d’une route change dans urls.py, produisant des liens cassés et des actions de formulaire mortes qu’aucun test sur l’URLconf ne détectera. {% url %} résout le chemin à partir du nom de la route, donc renommer un chemin met à jour tous les liens d’un coup.

À éviter :

<a href="/accounts/login">Login</a>

À faire :

<a href="{% url 'login' %}">Login</a>

H018

Les balises vides sont auto-fermantes par nature et doivent se terminer par ">", et non "/>" (incompatible avec : H017).

Dans le standard vivant HTML, la barre oblique finale d’un élément vide n’a aucune signification (l’analyseur l’ignore), donc écrire <br /> suggère un comportement d’auto-fermeture à la XML que le HTML n’a pas, et peut inciter les lecteurs à ajouter des barres obliques à des balises non vides, où un / parasite est silencieusement ignoré et masque des bugs de balises non fermées. Cette règle impose des fins simples en > sur les éléments vides.

Désactivée par défaut ; à activer avec --include=H018. Mutuellement exclusive avec H017 : n’activez qu’une seule des deux conventions. Le <path /> SVG est exempté, car SVG est du XML et exige la barre oblique.

À éviter :

<br />
<meta charset="utf-8" />

À faire :

<br>
<meta charset="utf-8">

J018

(Jinja) Les liens internes doivent utiliser le modèle {% url ... %}.

Les URLs internes codées en dur cassent silencieusement lorsque le chemin d’une route change ou que l’application est montée sous un préfixe, laissant des liens morts et des actions de formulaire qui envoient vers des 404. url_for() construit l’URL à partir du nom de l’endpoint, donc les changements de routes se propagent automatiquement à tous les modèles.

À éviter :

<a href="/accounts/login">Login</a>

À faire :

<a href="{{ url_for('login') }}">Login</a>

H019

Remplacez javascript:abc() par l'événement on_ et l'url réelle.

Les URLs javascript: cassent le clic du milieu et l’ouverture dans un nouvel onglet, ne font rien quand JavaScript est désactivé ou ne se charge pas, sont bloquées par les Content Security Policies strictes, et constituent un point d’injection XSS classique. Utilisez plutôt une véritable URL pour le href et attachez le comportement avec un gestionnaire d’événement. Sous une CSP stricte, les gestionnaires on* en ligne sont eux aussi bloqués : le onclick montré est le correctif minimal dans le modèle ; préférez attacher l’écouteur avec addEventListener depuis un fichier de script.

À éviter :

<a href="javascript:openPopup()">Open popup</a>

À faire :

<a href="{% url 'popup' %}" onclick="openPopup(event)">Open popup</a>

H020

Couple de balises vide trouvé. Envisagez de le supprimer.

Une paire de balises vide ne rend aucun contenu mais crée quand même un nœud DOM qui peut récupérer des marges, des bordures ou des espacements flex/grid depuis les feuilles de style, produisant un espacement fantôme difficile à tracer ; c’est généralement un reste de balisage d’une modification antérieure. Les balises légitimement vides dans un balisage normal (td, th, li, dt, dd, slot) sont exemptées. Les balises portant un attribut quelconque (points de montage JS comme <div id="app">``</div>, éléments de police d’icônes comme <i class="fa fa-user">``</i>) ne sont pas signalées non plus ; seules les paires vides totalement dépourvues d’attributs sont concernées.

À éviter :

<p>Saved.</p>
<span> </span>

À faire :

<p>Saved.</p>

H021

Les styles en ligne doivent être évités.

Les styles en ligne ont une spécificité supérieure à n’importe quel sélecteur de feuille de style, donc les surcharger ensuite exige !important ; ils sont bloqués par les Content Security Policies sans ‘unsafe-inline’ dans style-src ; et ils éparpillent la présentation dans les modèles, si bien qu’un changement de thème ou de design signifie éditer le balisage plutôt qu’une seule feuille de style. Déplacez la déclaration vers une classe CSS. Une exception légitime : les modèles d’e-mails HTML, où de nombreux clients de messagerie suppriment les blocs <style> et où les styles en ligne sont la technique standard ; excluez vos répertoires de modèles d’e-mails ou désactivez cette règle pour eux.

À éviter :

<div style="color: red;">Wrong username or password.</div>

À faire :

<div class="error">Wrong username or password.</div>

H022

Utilisez HTTPS pour les liens externes.

Les sous-ressources en simple http:// sur une page servie en HTTPS constituent du contenu mixte : les navigateurs bloquent purement et simplement les scripts, feuilles de style et iframes, et mettent automatiquement à niveau les images ou affichent un avertissement. Un lien <a> vers une page http:// n’est pas du contenu mixte, mais il envoie tout de même les visiteurs sur une connexion non chiffrée, exposée à l’interception et à la falsification. Les références à des hôtes internes qui n’ont réellement pas de TLS seront signalées aussi ; faites taire ces endroits avec un bloc {# djlint:off H022 #} plutôt qu’en désactivant la règle.

À éviter :

<a href="http://example.com">Example</a>

À faire :

<a href="https://example.com">Example</a>

H023

N'utilisez pas de références d'entités.

Les documents HTML5 sont en UTF-8, donc le caractère littéral fonctionne partout et c’est ce que les relecteurs lisent réellement ; une faute de frappe dans une référence d’entité (par exemple &mdsah;) n’est pas détectée par le navigateur et s’affiche telle quelle comme du texte cassé. djLint autorise les entités qui portent de la syntaxe (&lt;, &gt;, &amp;, &quot;, &apos;, ainsi que les accolades &lbrace; et &rbrace; avec &percnt;, &num; et &dollar;, qui écrits en clair formeraient un délimiteur de template) et celles qui nomment un caractère invisible, donc impossible à relire sous forme littérale : les espaces (&nbsp;, &thinsp;, &hairsp;), les liants et marques (&zwnj;, &zwj;, &lrm;, &rlm;) et &shy;, sous forme nommée, décimale ou hexadécimale.

--reformat réécrit l’entité en caractère pour vous : un signalement ne demande jamais de travail manuel. Une entité écrite à l’intérieur d’une balise de template fait partie de la balise et non de la page : ni la règle ni le formateur n’y touchent.

À éviter :

<p>Dates 1900 &mdash; 2000</p>

À faire :

<p>Dates 1900 — 2000</p>

H024

Omettre le type sur les scripts et les styles.

text/javascript et text/css sont les valeurs par défaut de HTML5 pour <script> et <style>, donc l’attribut est un poids mort que le navigateur ignore ; la spécification WHATWG dit explicitement de l’omettre. Le supprimer évite aussi les chaînes MIME périmées qui cassent l’élément une fois copié sur des scripts de module (où type=“module” compte réellement).

À éviter :

<script type="text/javascript" src="app.js">

À faire :

<script src="app.js"></script>

H025

La balise semble être orpheline.

Une balise sans sa balise d’ouverture ou de fermeture correspondante force la récupération d’erreur du navigateur à deviner où l’élément se termine : le balisage qui suit se fait avaler par le mauvais élément ; la mise en page, les sélecteurs CSS et les requêtes DOM de JavaScript cassent alors silencieusement, et différemment selon les navigateurs. H025 signale aussi un <ol> ou <ul> ouvert à l’intérieur d’un <p> : l’analyseur HTML ferme le paragraphe avant la liste, donc le balisage ne s’imbrique jamais comme il est écrit.

À éviter :

<div>
  <p>Hello</p>

À faire :

<div>
  <p>Hello</p>
</div>

H026

Les balises id et class vides peuvent être supprimées.

Aucun sélecteur de classe ou d’id ne correspond à un attribut vide, et un id vide est du HTML invalide (la valeur de l’id ne doit pas être la chaîne vide). Un sélecteur de présence comme div[class] le sélectionne malgré tout : le retirer est donc visible pour une feuille de style écrite ainsi. Cela signale généralement un bug de modèle où une variable devait être interpolée : le supprimer ou le remplir empêche ce bug de se cacher au grand jour.

À éviter :

<div id="" class="">content</div>

À faire :

<div>content</div>

T027

Chaîne non fermée trouvée dans la syntaxe du modèle.

Un guillemet ouvert mais jamais fermé à l’intérieur de {% ... %} ou {{ ... }} fait mal analyser la balise par le moteur de modèles : Django et Jinja lèvent une TemplateSyntaxError au rendu ou avalent silencieusement le reste des arguments de la balise comme contenu de chaîne, si bien que la page renvoie une erreur 500 ou s’affiche avec des arguments manquants.

À éviter :

{% trans "Welcome %}

À faire :

{% trans "Welcome" %}

T028

Envisagez d'utiliser des balises sans espace à l'intérieur des valeurs d'attributs. {%- if/for -%}

Désactivée par défaut ; à activer avec --include=T028.

L’espace qu’une balise sans espace supprime est un espace qui s’affiche : ne l’appliquez que là où l’attribut n’a rien à perdre. alt="{%- if brand -%}Acme{%- endif -%} logo" s’affiche Acmelogo, et un d="M12 {%- if big -%}20{%- endif -%} 4Z" svg devient un autre tracé. D’où le caractère optionnel de cette règle.

Les balises de modèle à l’intérieur d’une valeur d’attribut émettent dans l’attribut rendu les espaces et sauts de ligne qui les entourent : un href ou un src construit avec de simples balises {% if %}/{% for %} peut donc contenir des espaces parasites et produire des URLs cassées. Les balises de contrôle des espaces de Jinja/Nunjucks ({%- ... -%}) suppriment ces espaces environnants, si bien que l’attribut se rend comme une seule valeur propre. L’attribut class est exempté, car des espaces supplémentaires entre noms de classes sont sans conséquence.

Non appliquée au profil django : les balises de modèle Django ne prennent pas en charge le contrôle des espaces {%- -%}.

À éviter :

<a href="{% if x %}/home{% endif %}"></a>

À faire :

<a href="{%- if x -%}/home{%- endif -%}"></a>

H029

Pensez à utiliser des valeurs de méthode de formulaire en minuscules.

La spécification HTML définit les mots-clés de méthode de formulaire en minuscules (get, post) ; les navigateurs n’acceptent les variantes en majuscules que par une correspondance de repli insensible à la casse. Conserver la forme canonique en minuscules garde les modèles cohérents et faciles à rechercher, et évite les plaintes des validateurs stricts et des chaînes d’outils basées sur XHTML.

À éviter :

<form method="POST"></form>

À faire :

<form method="post"></form>

H030

Pensez à ajouter une méta-description.

Les moteurs de recherche utilisent la meta description comme extrait affiché sous le titre de votre page dans les résultats ; sans elle, ils synthétisent un extrait à partir d’un texte arbitraire de la page, ce qui nuit au taux de clic et produit de mauvais aperçus de lien lorsque la page est partagée.

Ne se déclenche que sur les fichiers contenant un document <html></html> complet. L’argument de l’extrait s’applique aux pages indexées publiquement ; pour les applications derrière authentification ou en intranet, cette règle est couramment désactivée.

À éviter :

<html lang="en">
  <head><title>Home</title></head>
  <body>Welcome</body>
</html>

À faire :

<html lang="en">
  <head>
    <title>Home</title>
    <meta name="description" content="A short summary of this page.">
  </head>
  <body>Welcome</body>
</html>

T032

Espace blanc supplémentaire trouvé dans les balises du modèle.

Les suites d’espaces ou de tabulations entre les arguments d’une balise de modèle sont du bruit invisible : elles masquent les vraies différences dans les diffs, peuvent rendre difficile de repérer un argument manquant, et s’écartent du style à espace unique produit par le formateur de djLint, provoquant des reformatages inutiles. Les espaces à l’intérieur des chaînes entre guillemets sont préservés et ne sont pas signalés.

À éviter :

{% static  'css/style.css' %}

À faire :

{% static 'css/style.css' %}

H033

Espace supplémentaire dans l'action du formulaire.

Les espaces en début ou en fin de la valeur action d’un formulaire deviennent partie intégrante de l’URL rendue. Les navigateurs les suppriment à l’analyse, mais les clients hors navigateur et les tests qui utilisent la valeur littérale peuvent ne pas le faire, et autour d’une balise {% url %} l’espace parasite signale presque toujours une faute de frappe produisant une URL de soumission que le routage côté serveur ne reconnaît pas.

À éviter :

<form action="{% url 'search' %} " method="get">
    <button>Search</button>
</form>

À faire :

<form action="{% url 'search' %}" method="get">
    <button>Search</button>
</form>

T034

Aviez-vous l'intention d'utiliser {% ... %} au lieu de {% ... }% ?

}% est presque toujours une faute de frappe pour %}. Le moteur de modèles ne reconnaît pas }% comme délimiteur de balise, donc la balise n’est jamais analysée : le texte brut {% … }% fuit dans le HTML rendu, ou le moteur lève une erreur de syntaxe en rencontrant la balise non fermée.

À éviter :

{% include "footer.html" }%

À faire :

{% include "footer.html" %}

H036

N'utilisez pas les balises br pour l'espacement.

La spécification html n’autorise <br> que pour un saut de ligne faisant partie du contenu lui-même, comme dans une adresse postale ou un poème, et cet usage n’est pas touché. Ce qui est signalé est l’usage présentationnel que la spécification écarte : une suite d’au moins deux sauts, qui est de l’espace vertical, et un saut collé au bord intérieur d’un élément de bloc, qui ne rend rien que sa propre marge ne rendrait. Les deux cassent le renvoi à la ligne aux largeurs étroites, et un lecteur d’écran annonce un saut forcé là où il n’y a rien à annoncer.

À éviter :

<p>Shipping is free.<br><br>Delivery takes 3 days.</p>

À faire :

<p>Shipping is free.</p>
<p>Delivery takes 3 days.</p>

H037

Attribut en double trouvé.

Les attributs en double sont du HTML invalide, et les navigateurs ne conservent que la première occurrence et abandonnent silencieusement les suivantes : la deuxième valeur de class ou de style ne prend donc jamais effet, ce qui cache de vrais bugs. La vérification tient compte des modèles : un attribut répété dans des branches mutuellement exclusives ({% if %}/{% else %}) n’est pas signalé, puisqu’une seule copie peut être rendue.

À éviter :

<div class="card" id="profile" class="active">...</div>

À faire :

<div class="card active" id="profile">...</div>

T038

La balise de bloc n'a pas de balise de fin correspondante.

Une balise de bloc telle que {% if %}, {% for %} ou {% macro %} sans sa balise de fin correspondante est une TemplateSyntaxError pure et simple dans Django et Jinja : la page échoue au rendu au moment de la requête, ce que cette règle détecte avant le déploiement. Elle signale aussi les balises de fin orphelines sans balise d’ouverture et les blocs incorrectement entrelacés (par exemple {% if %}``{% for %}``{% endif %}).

L’appariement {% block %}/{% endblock %} et les noms de endblock non concordants sont vérifiés par cette règle ; T003 (désactivée par défaut) exige en plus un nom sur chaque {% endblock %} multiligne. Les balises de bloc personnalisées enregistrées via custom_blocks sont également vérifiées, y compris leur forme auto-fermante / %}.

À éviter :

{% if user.is_authenticated %}
<p>Welcome back!</p>

À faire :

{% if user.is_authenticated %}
<p>Welcome back!</p>
{% endif %}

T039

Balise de template non fermée trouvée.

Une balise de modèle ouverte avec {{ ou {% mais jamais fermée par le }} ou %} correspondant n’est pas analysée comme une balise : Django/Jinja lèvent une TemplateSyntaxError ou rendent les accolades brutes dans la page, et tout ce qui suit jusqu’au prochain délimiteur peut être silencieusement avalé. Ces fautes de frappe (une seule accolade manquante, un délimiteur non concordant) sont faciles à manquer en relecture, car le modèle peut encore se rendre partiellement.

À éviter :

<p>{{ user.name }</p>

À faire :

<p>{{ user.name }}</p>

T040

Nom de template manquant ou vide dans une balise extends ou include.

Une balise {% extends %} ou {% include %} dont le nom de modèle est manquant, vide ou composé uniquement d’espaces n’a rien à charger : Django lève une TemplateSyntaxError lorsque le nom est totalement absent, et TemplateDoesNotExist au rendu lorsqu’il est vide, si bien que la page renvoie une erreur 500 en production alors même que le fichier de modèle semble syntaxiquement plausible.

À éviter :

{% extends "" %}

À faire :

{% extends "base.html" %}

H041

La balise est fermée dans un bloc de template différent de celui où elle a été ouverte.

Lorsqu’une balise HTML est ouverte dans un {% block %} mais fermée dans un autre, un modèle enfant qui ne remplace qu’un seul de ces blocs hérite de la moitié de l’élément, produisant un balisage déséquilibré dans la page rendue : les navigateurs ferment ou réimbriquent alors les éléments de façon imprévisible, cassant la mise en page et les sélecteurs CSS loin du modèle réellement modifié. Garder chaque élément ouvert et fermé dans le même bloc rend chaque bloc sûr à remplacer indépendamment.

À éviter :

{% block content %}
<div class="wrapper">
{% endblock content %}
{% block footer %}
</div>
{% endblock footer %}

À faire :

{% block content %}
<div class="wrapper">
</div>
{% endblock content %}
{% block footer %}
{% endblock footer %}

T041

La balise extends devrait être la première balise du template.

Django refuse de compiler un modèle dans lequel une autre balise précède {% extends %}, et le texte écrit avant elle est rendu, si bien qu’il fuit dans la page avant tout ce que produit le modèle parent. Jinja rend aussi ce texte, et nunjucks le supprime, de sorte que dans chaque moteur le modèle ne fait pas ce qu’il semble faire.

Un commentaire {# #} ne rend rien et ne compte pas, pas plus que ce qui se trouve dans un bloc que djLint n’analyse pas, comme un bloc {% comment %}, {% raw %} ou {% verbatim %} ou une région {# djlint:off #}. Seul le premier {% extends %} est vérifié ; un second est une erreur à part entière. Une balise de branche placée avant ne compte pas non plus, puisque jinja documente {% if x %}{% extends "a.html" %}{% else %}{% extends "b.html" %}{% endif %} comme la façon de choisir un parent.

Non appliquée aux profils handlebars, golang, liquid et angular.

À éviter :

{% load static %}
{% extends "base.html" %}

À faire :

{% extends "base.html" %}
{% load static %}

H042

L'attribut for d'un label n'a pas d'id correspondant dans ce fichier.

La vérification ne s’exécute que sur les fichiers analysables de façon fiable : si le fichier contient quoi que ce soit pouvant rendre un id invisible ici (une sortie {{ ... }} telle qu’un widget de formulaire, un {% include %} ou {% extends %}, ou une balise de modèle inconnue), la règle reste silencieuse pour ce fichier. Là où elle s’exécute, un signalement est une association réellement cassée.

À éviter :

<label for="email">Email</label>
<input id="username">

À faire :

<label for="email">Email</label>
<input id="email">

T042

Le contenu hors d'un bloc n'est pas rendu dans un template qui en étend un autre.

Dès qu’un modèle en étend un autre, le parent décide de ce qui est produit et l’enfant ne fait que remplir les blocs du parent. Le texte ou le html écrit après {% extends %} et hors de tout {% block %} est silencieusement écarté au rendu, si bien qu’un paragraphe qui paraît correct dans la source n’atteint jamais la page.

Une balise de template placée là s’exécute quand même, si bien que {% load %}, {% set %} et un {% if %} enveloppant un bloc sont laissés tels quels, de même que les commentaires {# #} et {% comment %}, les blocs {% raw %} et {% verbatim %}, et le corps d’un {% macro %} ou d’un {% set %} sous forme de bloc, qui est capturé plutôt que produit. Un commentaire html est produit comme n’importe quel autre texte, si bien qu’un commentaire hors d’un bloc est signalé, tout comme le texte d’un {% blocktrans %}, qui n’est pas un {% block %}. Seul le contenu après la balise extends est considéré, et chaque suite ininterrompue de contenu est signalée une fois, à son début.

Non appliquée aux profils handlebars, golang, liquid et angular.

À éviter :

{% extends "base.html" %}
<p>This paragraph is never shown.</p>
{% block content %}
<h1>Welcome</h1>
{% endblock %}

À faire :

{% extends "base.html" %}
{% block content %}
<h1>Welcome</h1>
<p>This paragraph is shown.</p>
{% endblock %}

H043

La balise button devrait avoir un attribut type.

Un <button> sans type vaut submit, si bien qu’un bouton écrit pour lancer un script soumet aussi le formulaire qui l’entoure et la page se recharge. Écrire le type l’évite.

À éviter :

<form>
  <button onclick="preview()">Aperçu</button>
</form>

À faire :

<form>
  <button type="button" onclick="preview()">Aperçu</button>
</form>

T043

Le nom de bloc est utilisé plus d'une fois dans le template.

Django, Jinja et Nunjucks refusent tous d’analyser un modèle qui donne le même nom à deux blocs, si bien que la page ne se charge pas du tout. Les moteurs ne se soucient pas que les deux blocs se trouvent dans des branches différentes d’un {% if %}, si bien que chaque nom de bloc doit être unique dans tout le fichier, que les blocs soient côte à côte ou que l’un soit imbriqué dans l’autre.

Seul {% block %} compte : un {% blocktrans %} n’est pas un bloc, un {% endblock name %} ne fait que nommer le bloc qu’il ferme, et un bloc écrit dans un commentaire n’atteint jamais l’analyseur. Les noms sont comparés tels qu’ils sont écrits, puisque les moteurs traitent Content et content comme deux blocs.

Non appliquée aux profils handlebars, golang, liquid et angular.

À éviter :

{% block content %}{% endblock %}
{% block content %}{% endblock %}

À faire :

{% block content %}{% endblock %}
{% block sidebar %}{% endblock %}

H044

Un thead ne devrait pas mélanger des cellules th et td.

Chaque ligne est jugée séparément : la ligne d’explication en td que la spécification html place dans un thead à côté de la ligne d’en-têtes n’est donc pas un mélange. Un td vide en tête de ligne est la cellule d’angle d’un tableau dont la première colonne porte des en-têtes, le balisage que recommande le tutoriel d’accessibilité du W3C ; il est ignoré.

Un th et un td n’ont pas le même sens pour un lecteur d’écran et reçoivent en général un css différent : une cellule isolée dans une ligne d’en-tête se lit donc comme une donnée et s’affiche autrement que les colonnes voisines. Le mélange est du html valide, ce qui le rend difficile à repérer.

À éviter :

<thead>
  <tr>
    <th>Nom</th>
    <td>Taille</td>
  </tr>
</thead>

À faire :

<thead>
  <tr>
    <th>Nom</th>
    <th>Taille</th>
  </tr>
</thead>

T044

La balise de sortie contient un mot-clé d'instruction ; utilisez une balise de bloc.

Non appliquée aux profils golang, handlebars et angular.

Une balise de sortie affiche une valeur, et if, for, url, include et les autres sont des instructions qui ont leur place dans une balise de bloc. Django, Jinja et Nunjucks rejettent tous {{ if x }} par une erreur de syntaxe, et un mot-clé de fermeture seul, comme {{ endif }}, est lu comme une variable qui ne rend rien tandis que le bloc qu’il devait fermer reste ouvert, si bien que la page soit ne se charge pas, soit affiche ce que la condition aurait dû masquer.

Un mot-clé nu est un nom de variable ordinaire, si bien que {{ url }}, {{ url|default:"/" }} et {{ set.name }} ne sont pas signalés. Seul un mot-clé suivi d’un argument l’est, ainsi qu’un mot-clé de fermeture ou de branche seul comme {{ endif }} ou {{ else }}. Une expression jinja qui se trouve commencer par l’un de ces noms, comme {{ url ~ "/x" }} ou {{ url if url else "#" }}, est laissée telle quelle.

À éviter :

{{ if user.is_active }}

À faire :

{% if user.is_active %}

H045

La balise iframe devrait avoir un attribut title.

Un lecteur d’écran annonce une iframe par son nom accessible. Sans ce nom, il lit l’url du cadre, ou ne dit rien, et rien n’indique ce que contient la page intégrée avant d’y entrer. La WCAG classe ce point sous 4.1.2 Nom, rôle et valeur, et axe comme html-validate activent la vérification par défaut.

Le nom peut venir de title, aria-label ou aria-labelledby ; l’un des trois suffit. Un nom écrit par une balise de template compte aussi, si bien qu’un cadre titré page par page n’est pas signalé.

À éviter :

<iframe src="/report/"></iframe>

À faire :

<iframe src="/report/" title="Rapport trimestriel"></iframe>

T045

Une balise de template dans un commentaire html s'exécute quand même ; utilisez un commentaire de template pour la désactiver.

Un commentaire html cache le balisage au navigateur, pas au moteur de modèles. <!-- {% include "debug.html" %} --> rend toujours le fichier, et <!-- {% if debug %}...{% endif %} --> est toujours évalué, dans Django, Jinja, Nunjucks, Handlebars et Go sans distinction, si bien qu’une balise mise en commentaire de cette façon continue de s’exécuter, et ce qu’elle écrit atterrit dans le commentaire ou, si cela contient -->, en sort. Seul un commentaire de template, {# #} dans Django et Jinja, {{! }} dans Handlebars ou {{/* */}} dans Go, empêche une balise de s’exécuter.

Seule une balise d’instruction est signalée : {% %}, une section, une fermeture ou un partiel handlebars, et un mot-clé Go comme {{if}} ou {{end}}. Une valeur affichée dans un commentaire, comme dans <!-- built {{ version }} -->, est un usage délibéré et est laissée telle quelle, de même qu’une balise dans un commentaire de template ou un bloc {% comment %}, et un commentaire conditionnel pour Internet Explorer, <!--[if IE]> ... <![endif]-->, dont le corps est du balisage destiné au navigateur qu’il nomme.

À éviter :

<!-- {% include "banner.html" %} -->

À faire :

{# {% include "banner.html" %} #}

H046

La valeur de tabindex ne devrait pas être positive.

Un tabindex positif place l’élément en tête de l’ordre de tabulation, devant tout ce qui n’en a pas. Un seul suffit à réorganiser la page entière pour qui navigue au clavier, et l’ordre doit ensuite être tenu à la main dans chaque gabarit qui ajoute un contrôle. La WCAG traite ce point sous 2.4.3 Ordre de focus.

0 place l’élément dans l’ordre de tabulation là où le document le met, et -1 l’en retire tout en le laissant focusable par script : ni l’un ni l’autre n’est signalé, pas plus qu’une valeur écrite par une balise de template, dont le nombre n’est pas connu ici.

À éviter :

<input tabindex="1">

À faire :

<input tabindex="0">

H047

aria-hidden ne devrait pas être posé sur un élément focusable.

aria-hidden="true" retire l’élément de l’arbre d’accessibilité mais le laisse dans l’ordre de tabulation : la navigation au clavier s’y arrête encore et rien n’est annoncé. Masquer une icône décorative est l’usage courant de l’attribut et n’est pas signalé ; seul un élément qui prend le focus de lui-même l’est.

Un élément est considéré comme focusable s’il s’agit d’un button, select, textarea, iframe ou summary, d’un a ou area avec href, d’un input non caché, d’un audio ou video avec controls, ou de tout élément portant contenteditable ou un tabindex supérieur ou égal à 0. Un contrôle disabled, ou muni de tabindex="-1", est déjà hors de l’ordre de tabulation et reste intact.

À éviter :

<button aria-hidden="true">Close</button>

À faire :

<button type="button" aria-label="Close"><span aria-hidden="true">x</span></button>

H048

Cet attribut aria n'est pas défini par la spécification.

Un attribut aria mal orthographié ne fait rien du tout. Aucun navigateur n’avertit, aucun lecteur d’écran ne le signale, et le balisage garde l’apparence d’avoir été rendu accessible : aria-lable peut ainsi rester des années dans un gabarit pendant que le contrôle qu’il devait nommer reste sans nom.

Les noms connus de la règle sont ceux que définit ARIA. Une liaison écrite par un framework n’est pas un nom aria simple : :aria-label, v-bind:aria-label et [attr.aria-label] sont donc ignorés.

À éviter :

<button type="button" aria-lable="Close">x</button>

À faire :

<button type="button" aria-label="Close">x</button>

H049

Le viewport ne devrait pas empêcher le zoom de la page.

user-scalable=no, ainsi qu’un maximum-scale inférieur à 2, empêchent d’agrandir la page sur un téléphone, ce qui est pour beaucoup le seul moyen de la lire. La WCAG demande 200 % sous 1.4.4 Redimensionnement du texte. Les navigateurs ignorent de plus en plus cette restriction, mais la balise coupe encore le zoom partout où elle est respectée.

Le nom et le contenu sont trouvés quel que soit l’ordre dans lequel les deux sont écrits.

À éviter :

<meta name="viewport" content="width=device-width, user-scalable=no">

À faire :

<meta name="viewport" content="width=device-width, initial-scale=1">

H050

Élément obsolète, à remplacer.

<center>, <font>, <big>, <strike> et <tt> ont disparu lorsque css a pris en charge la présentation, et <marquee>, <blink>, <nobr> et <spacer> n’ont jamais été standard. Html n’en définit plus aucun, donc rien ne garantit la manière dont un navigateur les dispose, et une feuille de style ne peut pas les cibler comme elle cible une classe.

Les éléments que la règle connaît sont acronym, applet, basefont, bgsound, big, blink, center, dir, font, frame, frameset, isindex, keygen, marquee, menuitem, nobr, noembed, noframes, plaintext, spacer, strike, tt et xmp. Seule la balise ouvrante est signalée, de sorte que chaque élément n’est nommé qu’une fois, et un élément personnalisé dont le nom ne fait que commencer par l’un d’eux, comme <font-picker>, est laissé tel quel.

À éviter :

<center><font color="red">Warning</font></center>

À faire :

<p class="warning">Warning</p>

H051

Le rôle n'est pas un de ceux qu'ARIA définit pour le balisage.

Un rôle qu’aucune spécification ne nomme est purement et simplement ignoré, et l’élément conserve le sens qu’il avait déjà : role="buton" laisse une <div> être une <div>, c’est-à-dire rien pour un lecteur d’écran, alors que le balisage donne l’impression d’avoir reçu une intention. Rien ne le signale, et c’est ce qui rend la faute de frappe intéressante à détecter.

Les noms que la règle connaît sont les rôles qu’ARIA définit pour les auteurs, auxquels s’ajoutent ceux de DPUB-ARIA (doc-chapter et les autres) et de GRAPHICS-ARIA (graphics-symbol et les autres). Les rôles abstraits d’ARIA, comme landmark et sectionhead, sont signalés : la spécification indique qu’ils servent à construire son ontologie et ne doivent pas être écrits dans le balisage.

Un role peut contenir plusieurs noms, sous forme de liste de repli, et chacun est vérifié. Une valeur contenant de la syntaxe de gabarit est indéterminable et est laissée telle quelle, tout comme une liaison écrite par un framework, comme :role ou [attr.role].

À éviter :

<div role="buton">Save</div>

À faire :

<button type="button">Save</button>

H052

Meta refresh ne doit pas recharger ni rediriger la page après un délai.

Un rafraîchissement minuté déplace la page sous les yeux de qui la lit. Une personne qui lit lentement, qui utilise un lecteur d’écran ou qui a simplement été interrompue perd sa place sans avertissement et sans pouvoir l’empêcher, ce qui constitue un échec du critère WCAG 2.2.1 « Réglage du délai », et un rafraîchissement qui recharge la même page jette ce qui y avait été saisi.

Un délai de zéro est une redirection immédiate plutôt qu’un minuteur, ce que WCAG autorise là où une redirection serveur n’est pas possible : il n’est donc pas signalé. Les deux attributs sont trouvés quel que soit l’ordre dans lequel ils sont écrits.

À éviter :

<meta http-equiv="refresh" content="30">

À faire :

<meta http-equiv="refresh" content="0; url=/next-page">

H053

L'id est utilisé plus d'une fois dans le fichier.

Un id nomme un seul élément. Un second élément portant le même id casse getElementById, <label for>, les liens de fragment et aria-labelledby : le navigateur prend le premier et ignore silencieusement les autres, si bien qu’un label, un lien ou un script atterrit sur le mauvais élément sans aucun avertissement.

Deux ids dans des branches exclusives d’un même {% if %}...{% else %}...{% endif %} ne sont jamais rendus tous les deux, si bien qu’ils ne sont pas signalés. Une boucle {% for %} ou un {% block %} n’est pas une branche : un id à l’intérieur et le même id à l’extérieur sont tous deux rendus, et le second est signalé. Une valeur écrite par une balise de template est indéterminable et est laissée telle quelle, tout comme une valeur vide. Les ids sont comparés à l’identique, comme le fait le navigateur, si bien que save et Save sont deux ids.

À éviter :

<button type="submit" id="submit">Save</button>
<button type="submit" id="submit">Save and continue</button>

À faire :

<button type="submit" id="submit">Save</button>
<button type="button" id="cancel">Cancel</button>

H054

Un élément interactif ne devrait pas être imbriqué dans un autre.

Html interdit le contenu interactif dans <a> et <button>. Un bouton dans un lien, ou un lien dans un bouton, est un balisage invalide que les navigateurs réparent chacun à leur manière, et un utilisateur de lecteur d’écran ou de clavier se retrouve avec un contrôle qui se comporte comme deux. axe signale la même chose sous le nom « nested-interactive ».

Les conteneurs surveillés sont un <a> avec un href et un <button>, et les contrôles signalés à l’intérieur sont un lien avec un href, button, input, select et textarea. Un <a> sans href n’est pas interactif et est laissé tel quel d’un côté comme de l’autre, de même qu’un input caché ou un input dont le type est écrit par une balise de template. Un lien ou un bouton laissé ouvert se termine avec l’élément qui l’entoure, comme dans un navigateur, si bien qu’une faute de frappe ne signale pas le reste du fichier.

À éviter :

<a href="/cart"><button>Add</button></a>

À faire :

<a href="/cart" class="button">Add</a>

H055

L'attribut lang devrait être une étiquette de langue comme en ou pt-BR.

H005 demande un lang sur <html>, mais une valeur comme lang="english" ou lang="en_US" la satisfait sans nommer aucune langue connue d’un navigateur. Un lecteur d’écran se rabat alors sur sa voix par défaut, et la traduction et la césure choisissent les mauvaises règles ou aucune. La valeur doit être une étiquette BCP 47 : deux ou trois lettres, puis un nombre quelconque de sous-étiquettes d’une à huit lettres ou chiffres, chacune après un trait d’union, comme dans en, pt-BR ou zh-Hant-TW.

Seule la balise <html> est vérifiée, comme pour H005, et une valeur vide est laissée à cette règle. Une valeur écrite par une balise de template, comme dans lang="{{ LANGUAGE_CODE }}", est indéterminable et est laissée telle quelle, et xml:lang ou data-lang n’est pas lu comme lang.

À éviter :

<html lang="english">

À faire :

<html lang="en">

H056

Src ne devrait pas être vide.

La spécification html indique qu’un src vide est invalide, et avertit qu’un navigateur le résout par rapport à l’url du document lui-même, si bien que <img src=""> récupère à nouveau la page comme image et <script src=""></script> la récupère comme script. Il s’agit généralement d’un espace réservé qu’un script devait remplir, et la correction consiste à retirer l’attribut, ou à garder la valeur dans un attribut data, jusqu’à en avoir une vraie. Un src écrit sans aucune valeur, comme dans <img src>, est vide lui aussi et est signalé.

Seuls img, script, iframe, embed, source, track, audio et video sont vérifiés, puisque ce sont les éléments qui récupèrent ce que src nomme. Une valeur écrite par une balise de template est laissée telle quelle, tout comme une valeur composée uniquement d’espaces, et srcset et data-src sont des attributs différents que la règle ne juge pas.

À éviter :

<img src="" alt="Logo">

À faire :

<img src="{% static 'logo.png' %}" alt="Logo">

H057

La vidéo devrait avoir une piste de sous-titres.

Une vidéo sonore ne porte ses paroles que dans l’audio, si bien qu’un spectateur sourd ou malentendant n’en tire rien sans sous-titres, ce qu’exige le critère WCAG 1.2.2 Sous-titres (pré-enregistrés). Un <track> dont le kind vaut captions ou subtitles à l’intérieur du <video> satisfait la règle, tout comme un <track> sans kind du tout, puisque subtitles est la valeur par défaut.

Une vidéo muted n’a pas d’audio à sous-titrer et n’est pas signalée. Pas plus qu’une vidéo dont la balise ouvrante ou le corps contient une balise de template, puisque les pistes, ou l’attribut muted, peuvent être écrits par le modèle là où djLint ne peut pas les voir.

À éviter :

<video controls src="talk.mp4"></video>

À faire :

<video controls src="talk.mp4">
  <track kind="captions" src="talk.vtt" srclang="en">
</video>

{% endraw %}

Ajout de règles

Nous accueillons volontiers les pull requests contenant de nouvelles règles !

Une bonne règle consiste en

  • Name
  • Code
  • Message - Message à afficher lorsqu’une erreur est trouvée.
  • Flags - Drapeaux de regex. La valeur par défaut est re.DOTALL. ex : re.I|re.M
  • Patterns - Expressions regex qui trouveront l’erreur.
  • Exclude - Liste facultative de profils dont la règle doit être exclue.

Veuillez inclure un test pour valider la règle.

Règles personnalisées

Il est possible d’ajouter des règles personnalisées directement au sein de votre projet.
Pour cela, créez un fichier .djlint_rules.yaml à côté de votre pyproject.toml.
Un fichier de règles situé ailleurs peut être indiqué avec l’option CLI --rules.
Des règles peuvent être ajoutées à ce fichier et djLint les reprendra.

Règle basé sur la recherche d’un regex

Vous pouvez ajouter une règle qui échouera si l’un des regex listés dans patterns
est trouvé dans le code html.

- rule:
    name: T001
    message: Trouver la Trichotillomanie
    flags: re.DOTALL|re.I
    pattern:
      - Trichotillomanie

Règle utilisant un module python externe

Vous pouvez ajouter une règle qui va importer et executer une fonction python
personalisée.

- rule:
    name: T001
    message: Le mot 'bad' a été trouvé
    python_module: votre_package.votre_module

Le module indiqué dans python_module doit contenir une fonction run() qui sera
executé sur chacun des fichiers testés. La fonction doit accepter les arguments suivants :

  • rule: Le dictionnaire python qui représente votre règle dans .djlint_rules.yaml.
    Utilisez cette variable pour accéder aux name et message que vous avez défini dans
    le yaml.
  • config: L’objet de configuration global de DJLint.
  • html: Le contenu html complet du fichier testé.
  • filepath: Chemin du fichier testé.
  • line_ends: Liste qui, pour chacune des lignes du fichier html testé, contient un
    dictionnaire avec start et end qui donnent les indexes globaux dans le fichier du
    début et fin de la ligne. Cette variable peut être utilisée avec djlint.lint.get_line()
    pour récupérer le numéro de ligne à partir de l’indexe du caractère dans le fichier html.
  • *args, **kwargs: Il est possible que nous ajoutions d’autres arguments à l’avenir,
    il est donc fortement conseillé d’ajouter ces deux arguments pour diminuer les risques
    de bugs en cas de mise à jour de djLint.

La fonction doit retourner une liste de dictionnaire, un pour chacune des erreurs
trouvées. Le dictionnaire doit contenir les clées suivantes :

  • code: Code de la règle qui rapporte l’erreur (généralement rule['name'])
  • line: Numéro de ligne et numéro de caractère dans cette ligne, séparées par un :.
    Par exemple, "2:3" veut dire que l’erreur a été trouvée sur la ligne 2, au caractère 3.
  • match: La partie du contenu qui contient l’erreur
  • message: Le message qui serra affiché pour signaler l’erreur (généralement rule['message'])
from typing import Any, Dict, List
from djlint.settings import Config
from djlint.lint import get_line
import re


def run(
    rule: Dict[str, Any],
    config: Config,
    html: str,
    filepath: str,
    line_ends: List[Dict[str, int]],
    *args: Any,
    **kwargs: Any,
) -> List[Dict[str, str]]:
    """
    Rule that fails if if the html file contains 'bad'. This is just an exemple, in
    reality it's much simpler to do that with "pattern rule".
    """
    errors: List[Dict[str, str]] = []
    for match in re.finditer(r"bad", html):
        errors.append({
            "code": rule["name"],
            "line": get_line(match.start(), line_ends),
            "match": match.group().strip()[:20],
            "message": rule["message"],
        })
    return errors
Modifier cette page Actualisé Sep 18, 2026