Pourquoi votre z-index ne fonctionne pas
Déboguez z-index avec les outils de développement et repérez les contextes d’empilement. Trouvez l’ancêtre bloquant et corrigez modales, menus et en-têtes fixes.
Si un élément doté de z-index: 9999 s’affiche toujours derrière un élément réglé sur 2, c’est qu’un ancêtre du premier élément a créé un contexte d’empilement, et un z-index ne s’applique jamais en dehors du contexte auquel il appartient.
Vous avez déjà essayé 99999. Puis 2147483647. La modale reste sous l’en-tête, et rien dans le panneau Styles ne semble anormal.
Pour la théorie de l’ordre d’empilement, consultez CSS: Demystifying the z-index property. Ce guide traite du débogage : comment visualiser les contextes d’empilement dans les DevTools, comment remonter le DOM jusqu’à trouver l’ancêtre qui piège votre élément, et comment corriger chacun des cas courants sans augmenter la valeur.
Points clés
- Une valeur de z-index n’entre en concurrence qu’avec les éléments du même contexte d’empilement :
9999perd donc face à2lorsqu’un ancêtre du premier élément possède son propre contexte d’empilement. - Lorsqu’un z-index plus élevé perd, comparez les ancêtres des deux éléments qui partagent un parent commun. Leurs propres valeurs ne suffisent pas à trancher.
- Toute valeur de
transform,filterouclip-pathautre quenone, ou uneopacityinférieure à 1, crée un nouveau contexte d’empilement, même si l’élément n’a nipositionniz-index. - Un menu déroulant coupé au bord de sa carte est généralement rogné par
overflow, et aucune valeur de z-index n’y remédiera. - Faire d’une surcouche un enfant direct de
<body>la sort du contexte d’empilement de tous ses ancêtres.
Quelle est la règle unique qui explique pourquoi z-index ne fonctionne pas ?
Un z-index ne sert qu’à ordonner un élément par rapport aux autres membres de son propre contexte d’empilement. Un contexte d’empilement est un groupe que le navigateur peint comme une seule unité. Le guide de MDN sur les contextes d’empilement recense les propriétés qui en créent un. Lorsqu’un ancêtre crée un contexte, le z-index de l’enfant ne s’applique qu’à l’intérieur de cet ancêtre. Si l’ancêtre se trouve sous un autre contexte, l’enfant reste donc lui aussi en dessous, quelle que soit la valeur que vous lui attribuez.
Voici la version minimale du bug :
<header style="position: fixed; top: 0; z-index: 2; background: tomato;">Header</header>
<main style="opacity: 0.99;">
<div class="modal" style="position: fixed; inset: 0; z-index: 9999; background: rgb(0 0 0 / 0.6);">
Modal
</div>
</main>
La modale perd parce que main a opacity: 0.99, ce qui fait de main un contexte d’empilement. À l’intérieur de main, le 9999 de la modale fonctionne parfaitement. Mais main lui-même est comparé à l’en-tête au niveau racine, et le 2 de l’en-tête est supérieur. Lorsqu’un z-index plus élevé perd, examinez les ancêtres : trouvez l’ancêtre le plus proche de chaque élément qui partage un parent commun avec l’autre, puis comparez ces deux ancêtres.
Comment visualiser les contextes d’empilement dans les DevTools ?
Les DevTools de Chromium proposent des outils visuels pour analyser les problèmes de superposition, ce qui peut s’avérer plus rapide que de procéder par tâtonnements. Chrome comme Edge ouvrent ces vues depuis le Command Menu (menu de commandes), avec Commande+Maj+P sur macOS ou Contrôle+Maj+P sur Windows et Linux. Pour découvrir d’autres panneaux des DevTools souvent méconnus, consultez Hidden Gems in Chrome DevTools.
Chrome : le panneau Layers
- Ouvrez les DevTools et appuyez sur Commande+Maj+P ou Contrôle+Maj+P.
- Saisissez
Layerset choisissez Show Layers panel. Vous pouvez aussi passer par More options > More tools > Layers panel. - Faites pivoter le canevas et sélectionnez un calque pour afficher ses détails, notamment la raison pour laquelle le navigateur lui a attribué son propre calque.
Le panneau Layers affiche à gauche l’arborescence des calques de la page et, à côté, un diagramme 3D de ceux-ci. Utilisez ce panneau pour examiner la manière dont le navigateur a découpé la page en calques de composition, mais gardez à l’esprit que ceux-ci ne correspondent pas aux contextes d’empilement. Pour trouver l’ancêtre responsable d’un contexte d’empilement, utilisez la méthode de remontée décrite plus bas. Ce panneau est également utile pour déboguer les animations CSS saccadées.
Edge : la vue 3D
- Ouvrez le Command Menu et exécutez Show 3D View.
- Sélectionnez l’onglet Z-index.
- Cochez l’option Show all pour afficher les éléments positionnés en plus des contextes d’empilement.
La vue 3D d’Edge comporte trois onglets : Composited Layers, Z-index et DOM. L’onglet Z-index représente en 3D les contextes d’empilement de la page : un élément piégé apparaît ainsi imbriqué dans une couche inférieure.
Si vous souhaitez une vue arborescente des contextes d’empilement dans Chrome ou Firefox, l’extension CSS Stacking Context inspector ajoute un panneau affichant l’arbre des contextes, ainsi qu’une barre latérale dans le panneau Elements qui explique pourquoi l’élément sélectionné crée un contexte. Sa documentation précise qu’elle ne peut pas encore inspecter l’intérieur des iframes tierces.
La méthode de remontée des ancêtres
La méthode de remontée permet d’identifier l’ancêtre piégeant à l’aide du seul panneau Elements et du volet Computed. Elle fonctionne de la même manière dans tous les navigateurs.
- Sélectionnez l’élément qui perd (la modale, le menu).
- Dans Elements, cliquez sur son parent.
- Ouvrez le volet Computed et saisissez dans le champ de filtre chaque propriété de la liste ci-dessous :
transform,opacity,filter,position,z-index, etc. - Si aucune d’entre elles ne crée de contexte, remontez d’un niveau et recommencez jusqu’à atteindre
<html>. - Notez le premier ancêtre qui crée un contexte.
- Répétez les étapes 1 à 5 en partant de l’élément qui gagne.
- Les deux chaînes se rejoignent au niveau d’un ancêtre commun. Ses deux enfants situés sur ces chaînes sont les éléments réellement comparés.
Si vous travaillez à partir d’un rapport de bug plutôt que d’une reproduction locale, le session replay montre le symptôme du point de vue de l’utilisateur, par exemple un clic qui aboutit sur une surcouche invisible ou un menu qui s’ouvre derrière le contenu. Vous savez ainsi quel élément sélectionner à l’étape 1.
Liste de contrôle : propriétés qui créent un contexte d’empilement
Vérifiez chaque ancêtre à l’aide de ce tableau. Une seule ligne correspondante suffit à piéger ses descendants.
| Propriété | Crée un contexte lorsque | Ce qu’il faut chercher dans Computed |
|---|---|---|
position: absolute / relative | z-index n’est pas auto | Les deux valeurs définies sur le même élément |
position: fixed / sticky | Toujours | La valeur de position seule |
opacity | Inférieure à 1 | Des valeurs comme 0.99, souvent vestiges d’une animation de fondu |
transform, scale, rotate, translate | Toute valeur autre que none | Une valeur matrix(...), y compris les transformations identité |
filter, backdrop-filter | Autre que none | blur(), drop-shadow() |
clip-path, perspective | Autre que none | Toute forme ou longueur |
mask, mask-image, mask-border | Autre que none | Des valeurs d’image ou de dégradé |
will-change | Elle mentionne une propriété qui créerait un contexte si on la définissait | will-change: transform ou opacity |
isolation | isolate | Valeur explicite |
contain | layout, paint, strict ou content | Fréquent dans les listes virtualisées |
| Enfant flex ou grid | z-index n’est pas auto | display: flex ou grid sur le parent. Aucune position nécessaire |
La liste complète de MDN inclut également mix-blend-mode avec une valeur autre que normal, les éléments de la top layer, ainsi que les éléments dont les propriétés créatrices de contexte sont animées avec animation-fill-mode: forwards. MDN mentionne encore container-type: size et inline-size, mais le CSS Working Group a retiré le confinement de mise en page de container-type, qui ne crée donc plus de contexte d’empilement. Chrome a appliqué ce changement dans sa version 129, et Firefox et Safari ont suivi depuis.
Trois cas courants : symptôme, cause, correctif
Modale derrière un en-tête
Symptôme : la surcouche assombrit la page, mais l’en-tête reste lumineux par-dessus. Cause : la modale est rendue à l’intérieur d’un conteneur doté d’une transformation, d’une opacité ou d’un z-index positionné, et le contexte de l’en-tête l’emporte sur ce conteneur. Correctif : déplacez la modale dans <body> (voir les correctifs ci-dessous).
Menu déroulant rogné dans une carte avec transformation
Un menu déroulant placé dans une carte transformée souffre de l’un de deux problèmes, l’empilement ou le rognage : commencez donc par déterminer lequel. Si le menu est peint derrière la carte suivante, il s’agit d’un problème d’empilement : la carte transformée est un contexte, et les cartes situées plus loin dans le DOM sont peintes par-dessus. Surélevez la carte ouverte :
.card.is-open { position: relative; z-index: 1; }
Si le menu est coupé au bord de la carte elle-même, il s’agit d’un rognage. Vérifiez la propriété overflow de la carte et de ses ancêtres. Toute valeur autre que visible rogne le menu, et aucune valeur de z-index ne le fera réapparaître.
Élément sticky qui ne reste pas au premier plan
Symptôme : des cartes glissent par-dessus un en-tête sticky au survol ou au défilement. Cause : tout élément en position: fixed ou sticky crée un contexte. Sans z-index, il se situe toutefois au niveau 0, et les éléments frères suivants qui créent des contextes (comme des cartes avec des transformations au survol) sont peints par-dessus, car ils apparaissent plus loin dans le DOM. Correctif :
.site-header { position: sticky; top: 0; z-index: var(--z-sticky); }
Si l’en-tête ne reste pas du tout fixé, la cause est généralement la propriété overflow d’un ancêtre. Pour la réalisation complète d’un en-tête sticky, consultez Building a responsive sticky navbar with CSS.
Trois façons de libérer un élément piégé
Tout correctif pour un élément piégé modifie la structure des contextes d’empilement plutôt que la valeur du z-index : déplacer l’élément, déplacer la propriété, ou placer délibérément une frontière à l’endroit voulu.
Déplacer l’élément dans le DOM. Une surcouche qui est un enfant direct de <body> n’a aucun contexte ancêtre au-dessus d’elle : son z-index est donc comparé au niveau racine. C’est pourquoi les portails fonctionnent si bien pour les surcouches qui doivent recouvrir toute la page :
const modal = document.querySelector('.modal');
function openModal() {
document.body.appendChild(modal); // escape every ancestor context
modal.hidden = false;
}
Supprimer ou déplacer la propriété. Andy Blum, de Lullabot, a décrit un cas où une mise en page de droite à gauche inversait un conteneur de vidéo d’arrière-plan à l’aide d’une transformation, ce qui masquait le bouton lecture/pause de la vidéo. Il a résolu le problème en déplaçant la transformation du conteneur vers l’iframe de la vidéo :
/* Before: the wrapper becomes a context */
[dir="rtl"] .hero { transform: scaleX(-1); }
/* After: only the media is mirrored */
[dir="rtl"] .hero iframe { transform: scaleX(-1); }
Ajouter une isolation de manière délibérée. Définir isolation: isolate sur la racine d’un composant crée volontairement un contexte, de sorte que chaque z-index à l’intérieur du composant reste local :
.card { isolation: isolate; }
.card .badge { position: absolute; z-index: 1; } /* never competes with the site header */
Prévenir le problème avec une échelle de z-index
Un z-index de 9999 signifie presque toujours que quelqu’un n’a jamais trouvé le contexte d’empilement, et non qu’une valeur plus élevée était nécessaire. Remplacez les valeurs ponctuelles par une échelle réduite et documentée :
:root {
--z-base: 0;
--z-dropdown: 100;
--z-sticky: 200;
--z-overlay: 300;
--z-modal: 400;
--z-toast: 500;
}
.modal { z-index: var(--z-modal); }
Les tokens de l’échelle de z-index n’ordonnent que les éléments qui partagent un contexte d’empilement. C’est pourquoi ils donnent les meilleurs résultats associés aux portails pour les couches de niveau page et à isolation: isolate sur les racines des composants.
Conclusion
Un z-index qui ne fonctionne pas relève presque toujours d’un problème de contexte d’empilement, et non de la valeur elle-même. La prochaine fois que 9999 perd, ne touchez pas à la valeur. Ouvrez le panneau Layers ou la vue 3D pour vérifier ce qui est peint et à quel endroit, puis remontez à partir des deux éléments à l’aide de la liste de contrôle jusqu’à ce que les deux chaînes se rejoignent. Les deux enfants de l’ancêtre commun sont les éléments comparés, et c’est sur l’un d’eux que doit porter le correctif.
FAQ
Pourquoi z-index n'a-t-il aucun effet sur mon élément ?
z-index ne s'applique qu'aux éléments positionnés, c'est-à-dire dont la position est relative, absolute, fixed ou sticky, ainsi qu'aux éléments flex ou grid. Sur un élément conservant la valeur par défaut position: static et qui n'est pas un enfant flex ou grid, le navigateur ignore complètement z-index. Ajoutez position: relative pour que la valeur prenne effet, puis vérifiez si un contexte d'empilement ancêtre la limite encore.
Une boîte de dialogue ouverte avec showModal() échappe-t-elle aux contextes d'empilement ?
Oui. Appeler showModal() sur un élément dialog le promeut dans la top layer, qui se situe au-dessus de tous les contextes d'empilement du document. Les transformations et les valeurs de z-index des ancêtres n'affectent plus son ordre. Les popovers affichés avec showPopover() sont promus de la même manière. z-index n'a aucun effet au sein de la top layer : les éléments s'empilent dans l'ordre de leur promotion, de sorte que le dernier ouvert apparaît au premier plan.
Pourquoi ma surcouche en position: fixed est-elle positionnée par rapport à son parent plutôt qu'au viewport ?
Un ancêtre doté d'une propriété transform, perspective ou filter autre que none devient le bloc conteneur de ses descendants en position fixe. Une surcouche fixe placée à l'intérieur est alors positionnée et dimensionnée par rapport à cet ancêtre, et non au viewport. Cette même propriété crée également un contexte d'empilement : la surcouche peut donc à la fois se retrouver derrière d'autres contenus et recouvrir la mauvaise zone. Déplacer la surcouche dans body résout les deux problèmes.
Un z-index négatif peut-il placer un enfant derrière son parent ?
Uniquement si le parent ne crée pas de contexte d'empilement. Au sein d'un contexte d'empilement, le navigateur peint d'abord l'arrière-plan et les bordures de la racine du contexte, puis les enfants ayant un z-index négatif, puis le contenu du flux. Si le parent a une opacité inférieure à 1, une transformation ou un z-index positionné autre que auto, un enfant en z-index: -1 est toujours peint au-dessus de l'arrière-plan du parent, mais en dessous de son contenu normal.
Gain control over your UX
See how users are using your site as if you were sitting next to them, learn and iterate faster with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Star on GitHub12k