Accueil / Articles

D’où viennent les valeurs CSS lorsque vous spécifiez « None » : comment fonctionne l’héritage

Apprenez comment les navigateurs déterminent si une propriété est héritée, pourquoi les enfants reçoivent la valeur calculée du parent, et comment les propriétés inherit et initial vous permettent d’avoir un contrôle explicite.

Éditeur : REACTAPP.TOP10 min de lecture2243 mots

Sources: How Inheritance Works in CSS Internally at Algorithmic Level.

Vous définissez la propriété color sur un conteneur, et le paragraphe à l’intérieur devient rouge même si aucune règle ne la mentionne. En revanche, l’espacement que vous ajoutez à ce même conteneur reste exactement à sa place. Ces deux comportements proviennent de l’héritage CSS, mécanisme qui remplit une valeur lorsque un élément n’en possède pas de propre. Ce guide explique quelles propriétés sont héritées, ce qui est exactement transmis (la réponse surprend beaucoup de gens lorsqu’il s’agit de pourcentages), comment les mots-clés inherit et initial modifient les valeurs par défaut, ainsi que le rôle de l’héritage parmi les autres étapes qui transforment un feuille de style en valeurs finales.

Une valeur que personne n’a déclarée

Commencez par une règle appliquée à un élément parent :

.parent {
  color: red;
}

et par le code marquant l’emplacement du paragraphe à l’intérieur de ce parent :

<div class="parent">
  <p>Hello World</p>
</div>

Aucun ensemble de règles ne définit la couleur pour le <p>, pourtant son texte s’affiche en rouge. Lorsque le navigateur atteint cet endroit, il a déjà résolu les déclarations concurrentes grâce à la cascade et traité leurs valeurs. Ce qui reste, c’est de déterminer, pour chaque propriété de chaque élément, si la valeur doit provenir des déclarations propres à l’élément ou de quelque part plus haut dans la hiérarchie. L’héritage est le mécanisme qui permet de prendre cette décision.

Quelles valeurs descendent dans la hiérarchie

L’héritage permet à certaines valeurs de propriété de passer d’un élément parent à ses enfants, et de ceux-ci à leurs propres enfants :

Parent
  ↓
Child
  ↓
Grandchild

Toutes les propriétés ne participent pas à cet héritage. Les propriétés qui décrivent le texte sont celles qui sont classiquement héritées, telles que :

font-family
font-size
color

C’est pourquoi il est rarement nécessaire de répéter la typographie pour chaque élément. Plutôt que de définir la même couleur pour le body puis à nouveau pour chaque type d’élément enfant :

body {
  color: #333;
}
h1 {
  color: #333;
}
p {
  color: #333;
}
li {
  color: #333;
}

on peut la déclarer une seule fois, en haut de l’arbre du document, et laisser l’héritage la transmettre vers les éléments descendants. Le résultat est moins de CSS et un seul endroit à modifier en cas de changement de design. Définir la police de base et la couleur sur body ou :root est l’application courante de cette idée.

La décision prise par le navigateur pour chaque propriété

La logique devient plus claire lorsque l’on examine le cas où aucune déclaration ne s’applique à un élément. Pour chaque propriété, le navigateur pose en fait deux questions successivement.

Tout d’abord : existe-t-il une valeur en cascade ? Si une déclaration pour cette propriété a gagné la cascade sur cet élément, c’est cette valeur qui est utilisée et l’héritage n’intervient jamais.

Ensuite, uniquement si la réponse est non : est cette propriété héritée ? Cela dépend entièrement de la propriété, car chacune est définie par la spécification comme étant héritée ou non. Voici quelques exemples de propriétés héritées :

color        → inherited
font-family  → inherited
font-size    → inherited

et d’autres qui ne le sont pas :

margin       → not inherited
padding      → not inherited

Cette séparation est intentionnelle. Imaginez un monde où les propriétés margin seraient héréditaires : une seule marge définie pour une section serait copiée sur chaque paragraphe, liste et lien qu’elle contient, ce qui provoquerait une accumulation d’espacements à chaque niveau. Les propriétés liées aux boîtes, telles que margin, padding, border et width, sont presque toujours des éléments que l’on souhaite définir pour chaque élément individuel, tandis que les propriétés liées au texte sont généralement des éléments que l’on veut appliquer à toute une région. Si une propriété n’est pas héréditaire et ne possède aucune valeur dérivée, elle reprend sa valeur initiale, c’est-à-dire la valeur par défaut définie pour cette propriété.

Si vous n’êtes pas sûr au sujet d’une propriété spécifique, la rubrique « Hérité » dans sa documentation de référence répond directement à cette question.

Les enfants héritent de la valeur calculée, et non du texte que vous avez écrit

C’est le détail le plus important de tout le processus, et celui qui est à l’origine d’une surprise bien connue. Supposons qu’un parent définit une taille de police et un pourcentage pour la hauteur de ligne :

.parent {
  font-size: 20px;
  line-height: 150%;
}

et qu’un enfant ne modifie que la taille de police :

.child {
  font-size: 25px;
}

Quelle hauteur de ligne l’enfant obtiendra-t-il finalement ? On pourrait supposer intuitivement que l’enfant reçoit le pourcentage lui-même :

150%

puis qu’il l’applique à sa propre taille de police, plus grande :

150% of 25px

C’est ainsi que cela ne fonctionne pas. Ce qu’un élément hérite, c’est la valeur calculée de son parent, et non la valeur telle qu’elle a été déclarée. Pour line-height, un pourcentage est converti en une longueur au moment du calcul de la valeur calculée. Chez le parent, le calcul est :

150% of 20px = 30px

donc la hauteur de ligne calculée du parent est :

30px

C’est cette longueur absolue qui est transmise. L’élément enfant, malgré son police plus grande, hérite d’une hauteur de ligne de :

30px

et non :

150% of 25px

La conséquence pratique est que les lignes semblent trop étroites chaque fois qu’un élément descendant utilise une police plus grande que celle de l’élément où le pourcentage a été défini. Il en va de même pour les valeurs em utilisées avec line-height, car elles sont également converties en longueurs avant l’héritage. C’est précisément pourquoi les hautes de ligne sans unité, comme line-height: 1.5, sont fortement recommandées : un simple nombre représente déjà la valeur calculée, de sorte que chaque élément descendant le multiplie par sa propre taille de police au lieu d’hériter d’une longueur fixe.

Les propriétés qui ne s’héritent pas reviennent à leur valeur initiale

Prenons maintenant une propriété du autre groupe :

padding

Le padding n’est pas hérité. Ainsi, lorsque un élément parent déclare :

.parent {
  padding: 20px;
}

l’élément enfant ne reçoit pas automatiquement :

padding: 20px;

En l’absence de valeur cascade et d’héritage, le padding de l’élément enfant utilise la valeur initiale de cette propriété, qui pour le padding est :

0px

Cela reste valable à moins qu’une autre règle ne définit explicitement un padding différent pour l’élément enfant. Connaître la catégorie à laquelle appartient une propriété permet d’économiser du temps lors du débogage : si une valeur semble « s’infiltrer » dans les éléments enfants, il s’agit d’une propriété héritée ; si elle semble « disparaître » chez eux, c’est une propriété non héritée. Les outils de développement du navigateur aident à cela, car le panneau Computed peut afficher les valeurs héritées ainsi que l’élément ancêtre d’où elles proviennent.

Forcer l’héritage avec inherit

Vous pouvez modifier le comportement par défaut de n’importe quelle propriété. Le mot-clé inherit indique au navigateur d’adopter la valeur du parent, même pour une propriété qui ne serait normalement pas héritée :

.child {
  padding: inherit;
}

Avec cette règle, le padding de l’élément enfant correspond au padding calculé du parent. Cela est utile lorsque vous souhaitez que la relation entre le parent et l’enfant soit explicite, par exemple pour faire en sorte qu’un élément imbriqué réutilise la couleur de bordure ou l’espacement de son conteneur, ou pour forcer les contrôles de formulaire, qui ne héritent généralement pas des polices par défaut dans les feuilles de style du navigateur, à utiliser la typographie environnante avec font: inherit.

Cela vous offre deux voies distinctes pour accéder à la valeur d’un parent. L’héritage automatique ne s’applique qu’aux propriétés héritées et uniquement en l’absence de valeur en cascade. L’héritage explicite via inherit est lui-même une déclaration, ce qui lui permet de l’emporter dans la cascade comme n’importe quelle autre valeur et de fonctionner pour toute propriété.

Réinitialisation avec initial

Le mot-clé associé est :

initial

Lorsqu’il est appliqué à une propriété, il la réinitialise à sa valeur initiale telle que définie dans les spécifications :

.child {
  color: initial;
}

Il convient de noter que « initial » fait référence à la valeur par défaut définie dans les spécifications, et non à la feuille de style par défaut du navigateur ou à la valeur que l’élément aurait normalement héritée. Pour color, la valeur initiale est la couleur du texte système (généralement noire) ; par conséquent, color: initial ne restaure pas la couleur du parent, mais isole l’élément de celle-ci. Pour display, la valeur initiale est inline ; c’est pourquoi display: initial sur un div le rend en mode inline plutôt que de le ramener au mode bloc.

Ces deux mots-clés ont des effets opposés lorsqu’ils sont utilisés ensemble :

inherit  → use the parent's value
initial  → reset to the property's initial value

Ensemble, ils vous donnent un contrôle explicite sur la manière dont les valeurs se déplacent à travers l’arborescence. CSS propose également unset (qui se comporte comme inherit pour les propriétés héritées et comme initial pour le reste) ainsi que revert (qui ramène à la feuille de style par défaut du navigateur) ; ces deux options sont souvent plus utiles que initial pour réinitialiser les composants.

Comprendre le tableau complet

Il est utile de voir l’héritage en parallèle des étapes qui le précèdent. Lorsque plusieurs règles ciblent le même élément, le navigateur commence par la cascade, qui compare les déclarations dans cet ordre :

Importance
     ↓
Specificity
     ↓
Source Order

Le résultat est la valeur en cascade. Cette valeur traverse ensuite une série d’étapes de traitement jusqu’à devenir quelque chose que le rendu et la peinture peuvent utiliser :

Declared Value
      ↓
Cascaded Value
      ↓
Specified Value
      ↓
Computed Value
      ↓
Used Value
      ↓
Actual Value

Lorsqu’aucune valeur en cascade n’existe, l’héritage fournit alors la valeur pour cette séquence :

No Cascaded Value
       ↓
Can Property Be Inherited?
       ↓
      Yes
       ↓
Parent's Computed Value
       ↓
Child

Stricto sensu, la spécification place l’héritage au moment où la valeur spécifiée est déterminée : la valeur spécifiée correspond à la valeur en cascade s’il y en a une, sinon à la valeur calculée du parent pour les propriétés héritées, et enfin à la valeur initiale. À partir de là, les étapes habituelles de calcul, d’utilisation et de valeur réelle se poursuivent. Une vue d’ensemble simplifiée de l’étape de traitement CSS ressemble donc à ceci :

CSS
                      ↓
                   Cascade
                      ↓
        ┌─────────────┴─────────────┐
        ↓                           ↓
   Importance                  Specificity
                                    ↓
                               Source Order
                                    ↓
                            Cascaded Value
                                    ↓
                            Value Processing
                                    ↓
                         Computed / Used / Actual
                                    ↓
                               Inheritance
                                    ↓
                                  CSSOM

Interprétez l’ordre vertical de ce diagramme comme un regroupement de concepts plutôt que comme une chronologie stricte, car l’héritage fournit la valeur spécifiée plutôt que d’intervenir après le calcul des valeurs. L’idée principale reste la même : il se passe bien plus entre le fichier de style et l’écran que simplement « j’ai écrit une règle et le navigateur l’a appliquée ».

De la feuille de style aux pixels

En regardant d’un peu plus loin, voici le parcours complet avec l’étape CSS développée :

HTML
  ↓
DOM
  ↓
CSS
  ↓
Cascade
  ↓
Specificity
  ↓
Value Processing
  ↓
Inheritance
  ↓
CSSOM
  ↓
DOM + CSSOM
  ↓
Render Tree
  ↓
Layout
  ↓
Paint
  ↓
Pixels

Avec ce processus en tête, même une petite règle implique beaucoup de travail implicite :

.title {
  font-size: 2rem;
  color: blue;
}

Avant que quoi que ce soit ne soit affiché, le navigateur doit déterminer :

  • quelles déclarations s’appliquent à chaque élément ;
  • laquelle l’emporte en cas de conflit ;
  • quel est le niveau de spécificité des sélecteurs en concurrence ;
  • quelle valeur chaque déclaration gagnante correspond réellement (par exemple, ce que 2rem représente en pixels) ;
  • si l’héritage fournit des valeurs qui n’avaient pas été déclarées ;
  • quelles tailles et positions résultent finalement de ces valeurs.
  • C’est seulement après tout cela que la page passe à l’agencement et à l’affichage.

    CSS en tant que système plutôt qu’en tant que liste de règles

    Une grande partie de la confusion entourant CSS disparaît dès que l’on cesse de le considérer comme une série de règles isolées pour le voir plutôt comme un système doté de responsabilités claires :

    • La spécificité explique pourquoi une déclaration l’emporte sur une autre.
    • L’ordre des sources résout les cas de partage lorsque la spécificité est égale.
    • Le traitement des valeurs transforme ce que vous avez écrit en valeurs que le navigateur peut utiliser.
    • Héritage explique d’où proviennent les valeurs lorsque vous n’en avez pas définies.

    Tous ces éléments ensemble transforment un feuille de style en un ensemble complet de valeurs finales qui servent à la mise en page et à l’affichage. Même une déclaration aussi courte que celle-ci :

    width: 66%;
    

    est traitée par l’ensemble de ces mécanismes. Le navigateur détermine quelle règle s’applique, traite le pourcentage, le rapporte au bloc contenant et fixe une largeur finale, tout cela avant même qu’un seul pixel ne soit affiché.

    Ce qui reste ensuite est une question distincte : où se trouve exactement chaque élément. Les largeurs, les hautesurs, le flux normal, la positionnement et le modèle de boîte font partie du modèle de mise en forme visuelle, l’étape suivante du processus, qui prend ces valeurs finales et les transforme en mise en page.

    Points clés

    • Une propriété obtient sa valeur d’abord à partir de la cascade ; l’héritage n’est consulté que lorsque aucune déclaration ne s’applique.
    • Chaque propriété est définie comme héritée (généralement liée au texte : color, font-*) ou non héritée (généralement liée aux boîtes : margin, padding).
    • Les enfants héritent de la valeur calculée du parent, de sorte que les hautesurs de ligne en pourcentage ou en em sont transmises sous forme de longueurs fixes ; il est préférable d’utiliser line-height sans unité.
    • Les propriétés non héritées qui ne sont pas déclarées conservent leur valeur initiale, comme 0 pour le padding.
    • inherit force l’héritage pour toute propriété ; initial réinitialise vers la valeur par défaut de la spécification, et non celle du parent ; unset et revert sont souvent de meilleurs outils de réinitialisation.

    Lectures complémentaires