Accueil / Articles

CSS attr() : Intégrer directement les valeurs des attributs HTML dans les styles

Découvrez comment la fonction CSS attr() transforme les attributs de données en longueurs, couleurs et durées, pourquoi ses solutions de secours échouent, pourquoi url() est bloquée, et comment la déployer en toute sécurité aujourd’hui.

Éditeur : REACTAPP.TOP18 min de lecture4056 mots

Sources: CSS attr() Reads Your HTML Now

Imaginez une barre de progression dont la valeur, 87, est déjà présente dans le code comme data-fill="87". Jusqu’à récemment, pour intégrer ce chiffre dans la propriété width, il fallait un petit script : lire dataset.fill, y ajouter un signe percentuel, puis écrire une propriété personnalisée en ligne directement sur l’élément d’où provenait le chiffre. La valeur quittait donc le document uniquement pour y être réinsérée immédiatement, au prix d’un script et d’une opération de redessin supplémentaire.

La fonction attr() élimine ce détour. Elle accepte désormais un type, permettant ainsi à une feuille de style de lire un attribut en tant que longueur, couleur, nombre ou identifiant et de l’utiliser dans n’importe quelle propriété acceptant ce type. Ce guide explique la syntaxe, les quatre manières dont une recherche peut aboutir, les schémas qui remplacent les scripts courants, les restrictions de sécurité concernant les URL, ainsi que la manière de déployer cette fonctionnalité tant que le support par les navigateurs n’est pas encore complet.

De générateur de chaîne à valeur de type

La fonction classique attr() fonctionne dans tous les principaux navigateurs depuis environ 2015, mais elle n’avait qu’une seule fonction : générer une chaîne pour la propriété content, généralement sur un pseudo-élément.

/* the version you already know */
blockquote::after {
  content: " — " attr(cite);
}

Cela est utile pour afficher une citation ou une cible de lien, et rien d’autre. Placer l’ancienne fonction attr() à l’intérieur de width faisait rejeter toute la déclaration, car cette fonction ne renvoyait que des chaînes et width n’a pas besoin de chaîne.

Les modifications apportées au niveau 5 des « CSS Values and Units » modifient ce principe : on indique au navigateur quel type de valeur contient l’attribut. Une fois que le moteur sait qu’il s’agit d’une longueur, d’une couleur ou d’un nombre, le résultat peut être utilisé partout où ce type est autorisé, ce qui, en pratique, signifie presque toutes les propriétés.

/* the version this article is about */
.bar   { width: attr(data-fill %, 0%); }
.chip  { background-color: attr(data-brand type(<color>), #888); }
.dial  { rotate: attr(data-deg deg, 0deg); }
.card  { view-transition-name: attr(id type(<custom-ident>), none); }

Deux précisions permettent de garder les attentes réalistes, car cette fonctionnalité est facile à surenchérir dans les deux sens.

  • Ce n’est pas un moyen d’éliminer JavaScript du stylage. Écrire style="--fill: 87%" directement dans l’HTML n’a jamais nécessité de script. Ce qui est nouveau est plus restreint : utiliser la valeur d’un attribut dans un style qui nécessitait auparavant un script, alors qu’il n’en a plus besoin maintenant.
  • Cela ne déplace pas la logique de l’application dans le CSS. Votre application décide toujours que la tâche est à 87 % terminée. La seule différence réside dans celui qui lit ce chiffre sur l’élément : le code que vous avez écrit, ou la feuille de style qui allait être appliquée de toute façon.

Anatomie de la fonction

La syntaxe tient sur une seule ligne.

attr( <attr-name> <attr-type>? , <fallback-value>? )

Utilisez attr(data-fill %, 0%). Il se compose de trois parties : le nom de l’attribut à lire, le type selon lequel il doit être interprété, et une valeur par défaut en cas d’absence de résultat utilisable.

Le type peut être indiqué de trois manières différentes. La première est type(), qui encadre tout type de données CSS entre des crochets.

/* 1. type() — any CSS data type */
color:  attr(data-brand type(<color>), crimson);
width:  attr(data-w type(<length>), 0px);
scale:  attr(data-s type(<number>), 1);

La deuxième forme consiste en une unité simple, indiquant que « l’attribut contient un nombre pur ; associez-lui cette unité ». La troisième forme est raw-string, qui constitue également la valeur par défaut en l’absence de type spécifié, et reproduit le comportement historique.

/* 2. unit shorthand — the attribute holds a bare number */
width:           attr(data-w px);      /* data-w="10"    → 10px  */
rotate:          attr(data-deg deg);   /* data-deg="45"  → 45deg */
animation-delay: attr(data-i s);       /* data-i="2"     → 2s    */
width:           attr(data-fill %);    /* data-fill="87" → 87%   *//* 3. raw-string — the default, and the old behaviour */
content: attr(data-name raw-string, "stranger");
content: attr(data-name);   /* identical */

La notation abrégée pour l’unité attend un nombre sans unité

attr(data-w px) signifie « lire le nombre indiqué dans data-w et le considérer comme des pixels ». L’attribut doit donc être data-w="10". Écrire data-w="10px" ne fonctionne pas, car l’unité est définie par le feuille de style et non par le code HTML. Lorsque vous souhaitez que le HTML contienne sa propre unité (par exemple parce qu’un éditeur peut saisir 2rem ou 10px), utilisez type(<length>) pour stocker la valeur complète.

Un fallback invalide échoue tôt et de manière différente

Le fallback est analysé ensemble avec le reste de la déclaration, avant même que le navigateur n’examine l’élément. Si vous écrivez attr(data-w px, banana), la déclaration est invalide au moment de l’analyse et est complètement ignorée : l’attribut n’est jamais lu, et c’est plutôt la déclaration antérieure de la même propriété dans la règle qui s’applique. Il s’agit d’une erreur complètement distincte d’un attribut manquant, qui n’est découvert que bien plus tard lors du calcul de la valeur. Séparer ces deux cas dans votre esprit facilite grandement le débogage.

Les types que vous pouvez déclarer

Les types suivants sont acceptés à l’intérieur de type(), ainsi que ce que l’attribut doit contenir et les endroits typiques où chacun peut être utilisé.

  • <length> : valeurs telles que "12px" ou "2rem", pour width, padding ou inset.
  • <percentage> : "87%", pour width ou translate.
  • <length-percentage> : l’un des deux précédents, pour tout élément nécessitant une taille.
  • <number> : "4.5", pour opacity, scale ou en entrée pour calc().
  • <integer> : "3", pour grid-column, z-index ou order.
  • <color> : "#4fc3f7" ou "tomato", pour color et background-color.
  • <angle> : "45deg" ou "0.25turn", pour rotate ou les angles de dégradé.
  • <time> : "300ms", pour animation-delay et transition-duration.
  • <resolution> : une densité telle que "2dppx", utilisée par image-resolution.
  • <custom-ident> : un identifiant comme "card-3", utile pour nommer des éléments tels que view-transition-name ou anchor-name.
  • <string> : une chaîne CSS entre guillemets, généralement utilisée par content.
  • <transform-function> : quelque chose comme "rotate(20deg)", utilisé avec la propriété transform.
  • <image> et <url> : présents dans la grammaire mais en pratique inutilisables, pour des raisons expliquées dans la section sécurité ci-dessous.
  • * : accepte n’importe quoi et est principalement utile pour la détection de fonctionnalités.
  • Les nombres nécessitent calc() pour devenir des tailles

    <number> n’a pas de dimension. 4.5 ne peut pas définir une largeur par lui-même, il se trouve donc presque toujours à l’intérieur de calc(). La méthode la plus propre consiste d’abord à le stocker dans une propriété personnalisée et à effectuer les calculs là-bas.

    .stars {
      --r: attr(data-rate type(<number>), 0);
      width: calc(var(--r) * 20%);   /* 4.5 → 90% */
    }
    

    --r n’est pas qu’une formalité. Il vous permet de réutiliser cette valeur dans plusieurs déclarations, et comme les propriétés personnalisées s’héritent, c’est également le moyen par lequel cette valeur atteint les éléments descendants. Comme vous le verrez, c’est la seule façon de partager un attribut avec des éléments enfants. Si vous utilisez déjà des propriétés personnalisées pour transmettre des données entre des scripts et des styles, l’article sur les propriétés personnalisées en temps de exécution aborde l’autre aspect de ce mécanisme.

    Comment s’effectue une recherche : quatre résultats, pas deux

    Il est tentant de penser que cette fonctionnalité fonctionne soit, soit que la règle est ignorée. En réalité, il existe quatre résultats distincts, et les confondre est la cause la plus fréquente de surprises.

    • L’attribut est absent. Si une solution de secours est fournie, elle est utilisée. Sinon, la valeur garantie comme invalide est retournée et la déclaration devient invalide au moment du calcul de la valeur (IACVT).
    • L’attribut existe mais est vide (data-x=""). Avec une fonction attr() de type spécifié, une chaîne vide n’est pas interprétée comme le type attendu, donc la solution de secours s’applique. En revanche, une recherche sans type est considérée comme réussie et produit une chaîne vide, ce qui fait que la solution de secours est ignorée.
    • La valeur ne correspond pas au type déclaré. La solution de secours est utilisée si elle existe ; sinon, la déclaration devient invalide au moment du calcul de la valeur (IACVT).
    • La valeur est bien interprétée. Elle est alors utilisée et le style s’actualise dès que l’attribut change.

    Que signifie réellement « invalide au moment du calcul de la valeur »

    Une erreur de syntaxe ordinaire est détectée lors du parsing. Le navigateur ignore cette déclaration et c’est la déclaration antérieure de la même propriété dans la même règle qui prend effet. Le problème IACVT survient bien plus tard, après que la cascade a déjà choisi une déclaration gagnante, il n’y a donc plus rien à quoi se référer. La propriété se comporte comme si elle était définie sur unset : les propriétés héritées prennent la valeur du parent, et les propriétés non héritées reviennent à leur valeur initiale.

    .bar {
      width: 50%;                  /* cascade drops this: the next one parsed fine */
      width: attr(data-fill %);    /* attribute missing → IACVT → width: auto */
    }
    /* result: auto. NOT 50%. */
    

    C’est pourquoi fournir toujours une solution de repli n’est pas une question de préférence. Sans elle, un nom d’attribut mal orthographié entraîne silencieusement l’utilisation de auto, 0 ou d’une couleur héritée, et la console reste silencieuse.

    Pourquoi attr() non typé ne peut pas produire de couleur

    La valeur par défaut raw-string mérite une attention particulière. Supposons qu’une règle définit background-color à partir d’un attribut sans type, et que vous l’appliquiez à des éléments qui ne diffèrent que par cet attribut. L’élément dépourvu de cet attribut reçoit la valeur de secours, comme prévu. Chaque élément qui possède cet attribut s’affiche en transparent, y compris celui qui contient un nom de couleur parfaitement valide.

    L’explication est qu’une fonction attr() non typée remplace le texte littéral de l’attribut sous forme de chaîne CSS, et non en tant que valeur CSS analysée. background-color ne accepte pas de chaîne, ce qui entraîne un échec lors du calcul de la valeur. La solution de secours ne sert à rien : elle ne couvre que l’absence d’un attribut ou un échec d’analyse pour un type déclaré, or rien n’a été déclaré dans cette règle. La recherche a réussi à retourner une chaîne ; elle a échoué ensuite car il s’agissait d’une couleur, sans mécanisme pour gérer ce cas.

    /* data-c="red" — and the element still renders transparent */
    .swatch { background-color: attr(data-c, gold); }
    

    La leçon est simple : chaque fois que la propriété cible n’est pas content, il faut déclarer un type.

    Modèles qui remplacent un script

    Plusieurs tâches que les équipes effectuent habituellement avec quelques lignes de JavaScript peuvent désormais être entièrement gérées dans le fichier de style.

    Une largeur provenant d’un attribut data

    Le markup contient un simple nombre.

    <div class="bar" data-fill="87"></div>
    

    Le feuille de style le lit avec l’abréviation en pourcentage et recourt alors à une barre vide.

    .bar {
      height: 8px;
      border-radius: 99px;
      background: linear-gradient(90deg, #4fc3f7, #2dd4bf);
      width: attr(data-fill %, 0%);
    }
    

    Une couleur choisie dans un CMS

    .chip { background-color: attr(data-brand type(<color>), #888); }
    

    Une couleur de marque sélectionnée par un éditeur de contenu relève des données plutôt que du design. Grâce à une fonction attr() déclarée, elle peut influencer directement la feuille de style, sans avoir à insérer des styles en ligne dans le modèle. Comme la validation de <color> a lieu dans le navigateur, une faute d’orthographe de l’éditeur entraîne simplement l’utilisation de la couleur grise par défaut au lieu d’un CSS défectueux.

    Animations de liste en cascade

    Une entrée en cascade est généralement réalisée en définissant un style en ligne à l’intérieur d’une boucle de rendu. Cependant, l’index fait déjà partie du markup et n’a pas besoin d’être transformé en propriété de style.

    <li data-i="0">Inbox</li>
    <li data-i="1">Drafts</li>
    <li data-i="2">Sent</li>
    

    Chaque élément lit son propre index en tant que nombre, et calc() le convertit en un délai.

    li {
      --i: attr(data-i type(<number>), 0);
      animation: fade-in .45s both;
      animation-delay: calc(var(--i) * .09s);
    }
    

    Si l’attribut contient déjà une durée complète, l’abréviation s peut la lire directement.

    /* or, if the attribute already holds a duration */
    li { animation-delay: attr(data-delay s, 0s); }
    

    Router l’index via --i présente un avantage supplémentaire : l’échelle de temps devient une seule constante CSS. En modifiant .09s en un seul endroit, toutes les listes décalées du site adoptent le nouveau rythme.

    Un nom de transition d’affichage unique par carte

    Cette approche permet ce qui n’était pas possible auparavant. Chaque élément participant à une transition d’affichage doit disposer de son propre view-transition-name unique, ce qui signifiait auparavant de générer une règle par carte ou d’attribuer des noms à partir d’un script. Une seule règle gère désormais toutes les cartes.

    .card {
      view-transition-name: attr(id type(<custom-ident>), none);
      view-transition-class: card;
    }
    

    Notez que l’attribut ici est id, et non un attribut data-*. La fonction attr() peut lire n’importe quel attribut de l’élément, y compris id, href, title, colspan ou lang. L’espace de noms data-* sert simplement à stocker les données personnalisées d’une page selon les attentes d’HTML. En associant ce nom à view-transition-class, vous pouvez styliser les transitions de toutes les cartes à l’aide d’un seul sélecteur, même si chacune possède un nom unique.

    Une façon utile de vérifier ces schémas consiste à supprimer temporairement la base fonctionnant avec des scripts et à confirmer que le stylisme reste visible ; s’il l’est, c’est bien attr() qui effectue le travail.

    Pseudo-éléments : le cas sans solution de contournement

    Tous les autres avantages décrits dans ce guide ont une alternative : vous pouvez toujours définir une propriété personnalisée en ligne. Les pseudo-éléments sont différents. element.style cible l’élément lui-même, tandis que ::before ou ::after ne sont pas des nœuds DOM, il n’y a donc rien à auquel attacher un style en ligne. Les options précédentes consistaient soit en une propriété personnalisée héritée du conteneur (ce qui exige néanmoins de styliser le conteneur d’une manière ou d’une autre), soit en un feuille de style générée. En revanche, un attribut sur le conteneur peut être lu directement depuis les pseudo-éléments.

    Voici un indicateur dont les seules données proviennent d’un attribut unique.

    <div class="meter" data-pct="72"></div>
    

    L’étiquette utilise la fonction de chaîne classique attr() dans son content, ce qui fonctionne dans tous les navigateurs.

    .meter::before {
      content: attr(data-pct) "% complete";
    }
    

    La barre utilise le formulaire structuré pour définir sa largeur à partir du même attribut.

    .meter::after {
      content: "";
      display: block;
      height: 10px;
      width: attr(data-pct %, 0%);
      background: linear-gradient(90deg, #fbbf24, #f472b6);
    }
    

    Un attribut alimente deux pseudo-éléments, et aucun d’eux n’a besoin d’un élément enfant supplémentaire. Les compteurs de badges, les anneaux de progression, le texte des tooltips, les préfixes ordinaux, les suffixes de unité ainsi que les étiquettes de données des graphiques suivent tous ce schéma : un pseudo-élément qui a besoin d’un nombre que l’élément hôte possède déjà. Avant l’apparition de attr(), ce nombre devait être écrit deux fois, une fois en tant qu’attribut pour le texte et une fois en tant que propriété personnalisée pour la géométrie, et ces deux versions pouvaient diverger. Aujourd’hui, il est écrit une seule fois.

    attr() ne lit que l’élément qui est stylisé

    La fonction lit les attributs de l’élément auquel correspond la règle. Elle ne prend pas en compte les ancêtres ou les frères/sœurs, et il n’existe pas d’équivalent à closest(). Comme un mécanisme de secours s’active, cette erreur échoue silencieusement, c’est pourquoi presque tout le monde la commet au moins une fois.

    Considérez une section qui définit une couleur d’accent pour son contenu.

    <section class="theme" data-accent="#4fc3f7">
      <h2>Title</h2>
    </section>
    

    Lire l’attribut depuis le titre n’a aucun effet utile, car ce dernier ne le possède pas.

    /* WRONG — this reads the h2, which has no data-accent */
    .theme h2 { color: attr(data-accent type(<color>), #888); }
    /* every heading is #888, forever, with no error */
    

    La solution consiste à lire l’attribut une seule fois, sur l’élément qui le possède, et à transmettre sa valeur via une propriété personnalisée.

    /* RIGHT — read it once on the element that has it, then inherit */
    .theme    { --accent: attr(data-accent type(<color>), #888); }
    .theme h2 { color: var(--accent); }
    .theme a  { border-bottom: 2px solid var(--accent); }
    

    L’héritage permet cette distribution. De plus, tout sous-arbre peut désormais remplacer la couleur d’accent à l’aide d’une déclaration ordinaire --accent, ce que l’approche directe via attr() ne pouvait jamais permettre.

    Pourquoi les URL sont interdites

    Toute valeur produite par attr() est marquée comme attr()-tainted, et une valeur contaminée ne doit jamais être utilisée pour construire une URL, quel que soit le chemin. Ces deux déclarations n’ont aucun effet :

    span[data-icon] { background-image: url(attr(data-icon)); }
    span[data-icon] { background: image-set(attr(data-icon)); }
    

    La contamination se transmet avec la valeur. La rediriger via une propriété personnalisée, l’envelopper dans src() ou image(), ou la placer en récursivité à l’intérieur d’autres fonctions, quelle que soit la profondeur, laisse cette trace en place. Où qu’elle se retrouve dans un contexte URL, la déclaration devient invalide au moment du calcul de la valeur.

    Le modèle de menace explique cette règle. Les valeurs des attributs sont souvent influencées par les utilisateurs : noms affichés, identifiants CMS, titres de commentaires. Si un feuille de style pouvait transformer une telle valeur en une requête réseau, quiconque capable d’écrire dans ce champ pourrait faire en sorte que le navigateur de chaque visiteur charge une adresse de son choix, ainsi que tout ce que le navigateur ajoute normalement à de telles requêtes. La résolution de ce problème dans la spécification a permis aux fournisseurs de navigateurs de s’accorder pour intégrer cette fonctionnalité. L’annonce de WebKit lors d’Interop 2026 met ce lien en évidence : avec les questions de sécurité résolues dans la spécification, tous les moteurs ont déclaré être prêts à intégrer cette capacité de manière interopérable.

    Tenez compte des limites de cette protection. Tout ce qui se trouve dans un attribut est lisible par tous les scripts de la page, visible dans l’affichage source, et transmis dans le payload HTML. L’interdiction d’utiliser des URL ferme une voie d’exfiltration ; elle ne rend pas les attributs privés. Ne mettez jamais de secrets à l’intérieur d’eux.

    Détection du support et déploiement progressif

    L’approche sûre consiste à utiliser une base commune comprise par tous les navigateurs, à laquelle on ajoute la version spécifique pour les moteurs qui la prennent en charge. Commencez par cette base, ici assurée par une propriété personnalisée en ligne de texte.

    .bar {
      width: var(--fill, 0%);          /* baseline, everyone */
    }
    

    Ensuite, laissez les navigateurs compatibles passer à l’attribut.

    @supports (width: attr(x %)) {
      .bar {
        width: attr(data-fill %, 0%);  /* modern browsers take over */
      }
    }
    

    Le même test est disponible en temps de exécution si votre code nécessite des branches.

    // the same answer at runtime, if you need to branch in code
    if (CSS.supports("width", "attr(x %)")) {
      // typed attr() is available
    }
    

    Test sur une propriété réelle

    Faites attention aux extraits de détection qui utilisent un nom de propriété placeholder, tels que @supports (x: attr(x type(*))), qui sont apparus dans la documentation de référence. Aucune propriété CSS nommée x n’existe, et tous les navigateurs répondent par faux lorsque une requête de compatibilité mentionne une propriété qu’ils ne connaissent pas, ce qui fait échouer le test même dans les navigateurs où la fonctionnalité fonctionne. Utilisez plutôt une propriété réelle. La requête @supports (width: attr(x %)) est fiable : width est une véritable propriété, et attr(x %) ne peut être interprété que par les moteurs qui implémentent l’abréviation de unité. Il est utile de vérifier la documentation actuelle de MDN concernant attr(), car les exemples y figurant sont parfois corrigés.

    S’appuyer sur la cascade plutôt que sur @supports

    Une variante plus courte évite le bloc @supports et laisse la cascade prendre la décision.

    .bar {
      width: var(--fill, 0%);        /* everyone */
      width: attr(data-fill %, 0%);  /* supporting browsers override */
    }
    

    attr() à l’intérieur de width élimine cette déclaration au moment du parsing, ce qui fait que la ligne précédente s’applique. Ce comportement découle de la manière dont le mécanisme de cascade gère les déclarations invalides. Néanmoins, la forme @supports exprime clairement l’intention et ne nécessite que quelques lignes supplémentaires ; privilégiez-la si vous souhaitez que le comportement soit évident pour les futurs lecteurs, et vérifiez la forme plus courte dans un moteur non compatible avant de vous y fier.

    attr(). Dans un navigateur compatible, l’absence d’un attribut ne fait pas basculer vers la déclaration var() ci-dessus ; la déclaration attr() a déjà gagné le mécanisme de cascade, ce qui la rend IACVT et fait que la largeur est déterminée comme auto.

    Support des navigateurs au moment de la rédaction

    Le support évolue rapidement, il convient donc de considérer ces chiffres comme un aperçu provisoire et de les vérifier sur Can I use avant de s’y fier. En août 2026, environ 70 pour cent des utilisateurs mondiaux disposaient d’un navigateur prenant en charge la fonction attr() :

    • Chrome 133 et versions ultérieures : prise en charge, étant le premier à l’intégrer (voir l’annonce de Chrome for Developers).
    • Edge 133 et versions ultérieures : prise en charge, suivant le calendrier de publication de Chromium.
    • Firefox 155 et versions ultérieures : prise en charge ; présent mais désactivé par défaut dans les versions 152 à 154.
    • Samsung Internet 29 et versions ultérieures : prise en charge.
    • Safari sur ordinateur : pas encore disponible ; uniquement en version Technology Preview.
    • Safari sur iOS : pas encore disponible, et comme tous les navigateurs iOS utilisent WebKit, cela couvre tous les navigateurs iOS.

    La fonction attr() traditionnelle qui ne prend que des chaînes de caractères et qui se trouve à l’intérieur de content est une question distincte. Elle est disponible depuis longtemps dans Baseline, depuis des décennies, dès les premières versions de chaque moteur majeur. Si tout ce dont vous avez besoin, c’est de content: attr(data-label), vous pouvez l’utiliser sans avoir à prendre aucune des précautions mentionnées ci-dessus.

    Interop 2026 cite attr() parmi ses domaines d’action prioritaires. Interop est une collaboration annuelle au cours de laquelle Apple, Google, Microsoft et Mozilla, ainsi que Igalia et Bocoup, identifient les fonctionnalités manquantes ou défectueuses dans au moins un moteur et s’engagent à les améliorer au cours de l’année. Les progrès sont suivis sur un tableau de bord public basé sur les Web Platform Tests partagés. C’est le signe public le plus fort que le support Safari est en cours de développement, mais il s’agit d’un engagement et non d’une date de sortie. Prévoyez une solution de secours pour le reste de l’année 2026. Pour en savoir plus sur une approche plus large visant à déployer de nouveaux CSS en respectant des références sûres, consultez déployer du CSS moderne en toute sécurité.

    Choisir entre attr() et JavaScript

    Aucun outil n’est supérieur partout. La répartition ci-dessous sert de guide pratique.

    Préférez attr() lorsque :

    • Le balisage contient déjà la valeur. L’inclure dans un script juste pour la réassigner sur le même nœud ne vous apporte aucun avantage.
    • Elle doit être présente dès le premier cadre. L’attribut existe dès que l’analyseur crée l’élément, tandis qu’un script s’exécute plus tard et peut provoquer une correction visible.
    • Un pseudo-élément a besoin de cette valeur. Les styles en ligne définis via element.style ne permettent pas de cibler ::before ou ::after.
    • Vous mettez en œuvre une politique de sécurité du contenu stricte. Les styles en ligne nécessitent unsafe-inline ou un nonce ; les attributs data n’en ont pas besoin.
  • Les éléments sont ajoutés dynamiquement. Les nœuds nouvellement insérés adoptent leur mise en forme selon les règles déjà existantes ; il n’est pas nécessaire de relancer de processus, et vous n’avez pas besoin d’un MutationObserver pour surveiller l’arborescence.
  • Vous souhaitez une seule source de vérité. L’approche basée sur des scripts conserve souvent la valeur à la fois dans un attribut et dans un style en ligne, ce qui peut entraîner des divergences.
  • Vous voulez que la présentation reste dans le CSS, visible par la cascade, les requêtes médias et de conteneur, ainsi que dans l’inspecteur de style du navigateur.
  • Préférez JavaScript lorsque :

    • La valeur n’a aucune raison d’exister dans le DOM. Une mesure provenant d’un ResizeObserver ou d’un gestionnaire de défilement ne doit pas être d’abord transformée en attribut.
    • Cela met à jour chaque frame d’animation. Appeler el.style.setProperty('--x', v) est plus direct et évite d’écrire un attribut.
    • Le chargement est important, imbriqué ou confidentiel. Tout ce qui se trouve dans un attribut est une chaîne visible publiquement.
    • Vous avez besoin de Safari aujourd’hui et la valeur est importante. Une barre de progression bloquée à zéro dans un navigateur constitue un défaut, et non une solution de secours fiable.

    La performance est visiblement absente de la première liste. Définir un style en ligne n’a jamais été un goulot d’étranglement. Ce que l’on gagne, c’est une meilleure organisation de la valeur et une meilleure visibilité dessus, pas plus de vitesse.

    Liste des pièges à éviter

    • Fournissez toujours une solution de secours. Une valeur manquante ou indéchiffrable sans solution de secours entraîne un IACVT et une propriété unset, et non la déclaration précédente.
    • Un attribut vide n’est pas un attribut manquant. Lorsque l’attribut n’a pas de type défini, data-x="" renvoie une chaîne vide et la solution de secours ne s’active jamais.
    • Aucun type défini signifie du texte littéral. background-color: attr(data-c, gold) avec data-c="red" ne rend rien ; il faut définir un type.
    • Aucun url() et aucun image-set(). Cette limitation est intentionnelle et persiste malgré les propriétés personnalisées.
    • Seul l’élément correspondant est lu. Pour les éléments ancêtres, stockez la valeur dans une propriété personnalisée afin qu’elle soit héritée.
    • Les abréviations de unités nécessitent des nombres bruts. Utilisez data-w="10", et non data-w="10px".
    • Une solution de secours invalide empêche l’exécution de la déclaration au moment du parsing, ce qui constitue une erreur antérieure et différente d’un attribut manquant.
    • Un <number> ne peut pas déterminer seul la taille de quoi que ce soit. Stockez-le et multipliez dans calc().
    • Les attributs sont publics. Évitez d’y mettre des informations secrètes.
    • Faites des tests fonctionnels avec une propriété réelle, comme @supports (width: attr(x %)).
    • Safari ne l’a pas encore intégré au moment de la rédaction, donc conservez une référence pour tout élément essentiel.

    Conclusion

    attr() en mode tapé représente un petit changement de syntaxe ayant un effet clair sur l’architecture : les valeurs déjà présentes dans HTML n’ont plus besoin d’un script pour devenir des styles. Cette fonctionnalité favorise la précision. Déclarez le type, écrivez toujours une valeur de secours, lisez l’attribut sur l’élément qui le possède et transmettez-le via des propriétés personnalisées, tout en acceptant que les URL soient définitivement interdites. Associez chaque utilisation essentielle à une valeur de référence en attendant la sortie de Safari, afin que ce modèle puisse être mis en production dès aujourd’hui, le fichier CSS prenant automatiquement le relais à mesure que la compatibilité s’étend. La définition formelle se trouve dans le projet CSS Values and Units Level 5 si vous avez besoin des règles de parsing exactes.