← Tous les articles
Sam. 4 avril 2026 · 5 min de lecture

🎹 CSS `:has()` et anchor positioning : ce que je n'Ă©cris plus en JS en 2026

CSS has et anchor positioning

📚 Introduction

Deux features CSS qui ont mis du temps à arriver, et qui sont désormais utilisables en production : le sélecteur :has() et le module CSS Anchor Positioning. Le premier permet de cibler un parent en fonction de ses enfants (le « sélecteur parent » que la communauté demandait depuis 20 ans). Le second permet de positionner un élément par rapport à un autre, sans JavaScript ni librairie de popper.

Ce post est un tour pratique : ce que je n’écris plus en JS depuis que ces deux features sont supportĂ©es, avec des exemples concrets que tu peux copier dans une feuille de style aujourd’hui.

🎯 :has() : le sĂ©lecteur parent natif

DisponibilitĂ© : Baseline Widely Available depuis dĂ©cembre 2023 (Chrome 105+, Safari 15.4+, Firefox 121+). Tu peux l’utiliser partout en 2026.

L’idĂ©e : parent:has(child) matche le parent si une condition sur l’enfant est vraie.

/* Une card qui a un <img> reçoit un padding-top différent */
.card:has(img) {
	padding-top: 0;
}
 
/* Un form invalide colore son label */
.field:has(input:invalid) label {
	color: var(--color-error);
}
 
/* Un <li> qui contient une checkbox cochée se barre */
li:has(input[type='checkbox']:checked) {
	text-decoration: line-through;
	opacity: 0.5;
}

ConcrÚtement, ça remplace une bonne partie des useEffect qui écoutaient un input pour appliquer une classe au parent. Le navigateur le fait, gratuitement, et sans cycle de rendu React.

Combinaisons utiles

:has() accepte n’importe quel sĂ©lecteur, y compris des combinateurs. Quelques patterns que j’utilise tout le temps :

/* Le body change de fond si une modal est ouverte */
body:has(dialog[open]) {
	overflow: hidden;
}
 
/* Le nav passe en mode compact si une sidebar est visible */
.app:has(.sidebar.is-open) .nav {
	width: 64px;
}
 
/* Un <details> ouvert se distingue */
details:has([open]) summary {
	border-bottom: 2px solid var(--accent);
}

Le navigateur gÚre le re-style en temps réel quand le DOM change. Pas besoin de MutationObserver.

📌 Anchor positioning : la fin des libs de tooltip

Disponibilité : Chrome/Edge 125+ (mai 2024), Safari 26+ (2025), Firefox 147+ (2025). Couverture globale ~83 % en 2026. Pour les navigateurs non-supportés, le fallback est généralement une position fixe acceptable.

L’idĂ©e : tu dĂ©clares un Ă©lĂ©ment comme « ancre », puis tu positionnes d’autres Ă©lĂ©ments par rapport Ă  elle, sans getBoundingClientRect().

<button id="trigger" class="anchor">Plus d'options</button>
<div class="popover">Menu déroulant...</div>
.anchor {
	anchor-name: --trigger;
}
 
.popover {
	position: fixed;
	position-anchor: --trigger;
	position-area: bottom span-right;
	margin-top: 8px;
}

anchor-name dĂ©clare l’ancre, position-anchor attache un Ă©lĂ©ment. position-area est une grille 3×3 autour de l’ancre : top, bottom, left, right, center, et leurs combinaisons. TrĂšs intuitif quand tu connais les utilitaires Tailwind.

Position calculée via anchor()

Pour un contrĂŽle plus fin :

.popover {
	position: fixed;
	position-anchor: --trigger;
	top: anchor(bottom);
	left: anchor(center);
	translate: -50% 0;
}

anchor(bottom) renvoie la coordonnĂ©e du bord bas de l’ancre. Tu combines avec translate et calc() pour le positionnement prĂ©cis.

Taille calquĂ©e sur l’ancre

anchor-size() permet de copier la taille de l’ancre :

.popover {
	width: anchor-size(width);
	min-width: 200px;
}

TrĂšs utile pour les comboboxes / dropdowns oĂč le menu doit faire la largeur du bouton.

Fallback positions

Le navigateur peut basculer la position si la popover déborde du viewport. Tu déclares un nom de stratégie et tu lui passes des positions de secours :

.popover {
	position-anchor: --trigger;
	position-area: bottom;
	position-try-fallbacks: top, right, left;
}

Si bottom dĂ©borde, le navigateur essaie top, puis right, puis left. C’est exactement ce que faisaient Popper.js et Floating UI Ă  coups de calculs JavaScript. Plus besoin d’embarquer 8 Ko de lib.

đŸ§© Patterns concrets remplacĂ©s

Tooltip pur CSS

<button class="tooltip-anchor">Hover me</button>
<div class="tooltip">Plus d'infos</div>
.tooltip-anchor {
	anchor-name: --tip;
}
 
.tooltip {
	position: fixed;
	position-anchor: --tip;
	position-area: top;
	opacity: 0;
	transition: opacity 0.15s;
}
 
.tooltip-anchor:hover ~ .tooltip,
.tooltip-anchor:focus-visible ~ .tooltip {
	opacity: 1;
}

Filtres conditionnels (:has())

/* Cache "Aucun résultat" sauf si aucune card n'est visible */
.results:has(.card) .empty-state {
	display: none;
}
 
.results:not(:has(.card)) .empty-state {
	display: block;
}

Accordion qui désactive son voisin

/* Si un &lt;details&gt; est ouvert, les autres se grisent */
.faq:has(details[open]) details:not([open]) {
	opacity: 0.5;
}

Le tout sans une ligne de JavaScript.

⚠ Quelques prĂ©cautions

  • Polyfills : il existe des polyfills pour anchor-positioning (oddbird/css-anchor-positioning), mais le bundle est lourd (~20 Ko). PĂšse l’option : tu peux souvent te contenter d’un fallback dĂ©gradĂ© en position statique pour les vieux navigateurs.
  • SpĂ©cificitĂ© :has() : le sĂ©lecteur a la spĂ©cificitĂ© de son argument le plus fort, ce qui peut surprendre. Lis la spec MDN en cas de cascade qui foire.
  • Performance :has() : c’est calculĂ© Ă  chaque mutation du DOM. Sur des arbres trĂšs gros (10 000+ nƓuds), profile avant de t’enflammer.
  • AccessibilitĂ© : le positionnement CSS ne suffit pas pour un menu dĂ©roulant accessible. Tu as encore besoin d’aria-expanded, aria-controls, gestion clavier. Pense au popover attribute HTML natif qui complĂšte trĂšs bien anchor-positioning.

🎯 Combo avec popover natif

L’attribut HTML popover (Baseline 2024) est le compagnon idĂ©al d’anchor positioning. Tu obtiens un menu dĂ©roulant accessible, focus-trap gĂ©rĂ©, ESC pour fermer, sans JavaScript :

<button popovertarget="menu" id="trigger" class="anchor">Menu</button>
<div popover id="menu" class="popover">
	<button>Action 1</button>
	<button>Action 2</button>
</div>
.anchor {
	anchor-name: --t;
}
 
[popover] {
	position-anchor: --t;
	position-area: bottom span-right;
	margin-top: 8px;
	position-try-fallbacks: top span-right;
}

C’est la plus belle combinaison de specs CSS rĂ©centes pour faire propre, accessible, et performant en 2026.

🎉 Conclusion

:has() et anchor positioning sont les deux meilleures additions CSS de la dĂ©cennie pour les dĂ©veloppeurs front. CombinĂ©s au popover natif, ils rendent inutile la majoritĂ© des micro-libs JavaScript qu’on traĂźnait pour faire des tooltips, dropdowns et Ă©tats conditionnels.

Pour les projets neufs en 2026, j’écris directement en CSS natif et je tombe sur un polyfill seulement si l’analytics du client le justifie. Si tu maintiens un Astro / Vue / React, regarde oĂč tu peux jeter quelques useEffect qui ne servaient qu’à styler un parent. Tu vas ĂȘtre surpris.

🔗 Liens utiles