đš CSS `:has()` et anchor positioning : ce que je n'Ă©cris plus en JS en 2026
đ 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 <details> 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 aupopoverattribute HTML natif qui complĂšte trĂšs bienanchor-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.