Wat de nieuwe CSS-selector :has ons heeft gebracht

Al sinds december 2023 is de :has CSS selector toegevoegd aan de baseline, wat aangeeft dat we deze veilig kunnen gebruiken. Deze selector biedt nieuwe mogelijkheden voor CSS styling die voorheen alleen met JavaScript of extra classes mogelijk waren. In deze blog wil ik uitleggen wat de :has selector kan en toon ik twee praktijkvoorbeelden waarbij het ons veel werk en code scheelt.

Wat is de :has selector in CSS?

De :has selector in CSS is een krachtige toevoeging waarmee je styling kunt toepassen op basis van de aanwezigheid van specifieke child-elementen of volgende elementen (siblings). Het werkt als een “parent selector”
je kunt een element stylen op basis van wat het bevat of wat erna komt, zonder de context van je selector te veranderen.

article.typeA {
  margin-bottom: 50px;
}

article.typeA:has(+ article.typeA) {
  margin-bottom: 0;
}

Dit voorbeeld toont hoe je dubbele marges kunt voorkomen. Het + symbool betekent “direct volgend element”. Dus article.typeA:has(+ article.typeA) betekent: “selecteer een article met class typeA die direct gevolgd wordt door nog een article met class typeA”.

Voorheen losten we dubbele marges op met negatieve marges:
article.typeA + article.typeA {margin-top: -50px}, maar dat kan ongewenste bijeffecten hebben. Met de :has selector houden we de oplossing veel schoner.

Voorbeeld: Styling op basis van inhoud

article.typeA:has(img) { display: flex; }

Hiermee kun je de styling van een element laten afhangen van de inhoud, zonder extra classes zoals article.hasImage te hoeven gebruiken.

Praktijkvoorbeeld: Hover states in combinatie met popups en menu’s

Het probleem
In onze applicatie Clientbox hebben we een takenmodule met een kanban-weergave. Een kaart in deze weergave heeft een footer met actieknoppen die alleen zichtbaar zijn bij het hoveren van de kaart of wanneer er een actie actief is.

Het probleem ontstaat wanneer popups (die na een klik op een actieknop worden getoond) in het body-element van de pagina worden gerenderd. Als je over zo’n popup hovert, hover je niet meer over de kaart zelf, waardoor de footer verdwijnt. Dit ziet er onprofessioneel uit.

De oplossing
In plaats van complexe state-management in React toe te voegen, kozen we voor een elegante CSS-oplossing. Wanneer een popup openstaat na een button-klik, zetten we een attribuut op de bijbehorende button:

<button data-active="true">planning</button></code>

Nu kunnen we met een simpele CSS selector zorgen dat de footer zichtbaar blijft:

.cardFooter:has(button[data-active="true"]) {
  display: block;
}

Het resultaat

Deze oplossing is schoner dan JavaScript state-management omdat:

  • De styling logica blijft in CSS
  • Geen extra React state of props nodig
  • Automatisch responsive gedrag
  • Minder JavaScript code om te onderhouden

Praktijkvoorbeeld 2: Slim margin management bij website blokken

Het probleem
Bij het ontwikkelen van websites bouwen wij verschillende contentblokken met elk hun eigen achtergrondkleur om visueel onderscheid te maken. Tussen deze blokken zit standaard ruimte: .block {margin-bottom: 100px}.

Het probleem ontstaat wanneer twee blokken met dezelfde achtergrondkleur na elkaar komen
dan ziet die tussenruimte er onnatuurlijk uit.

De traditionele oplossing
Jarenlang losten we dit op met negatieve marges op het tweede blok:

.block
backgroundGreen + .block
backgroundGreen { margin-top: -100px; }

Maar eigenlijk willen we gewoon de marge van het eerste blok weghalen, niet het tweede blok naar boven trekken.

De elegante oplossing met :has
Met de :has selector kunnen we dit veel logischer oplossen:

.block
backgroundGreen:has(+ .block
backgroundGreen) { margin-bottom: 0; }

Dit betekent: “Als een groen blok direct gevolgd wordt door nog een groen blok, haal dan de bottom margin weg.”

Nog verder geoptimaliseerd
We kunnen dit zelfs omdraaien en alleen marges toepassen wanneer er geen blok met dezelfde kleur volgt:

.block
backgroundGreen:not(:has(+ .block
backgroundGreen)) { margin-bottom: 100px; }

Het resultaat
Visueel hetzelfde eindresultaat

  • Logischer: we beïnvloeden het eerste element in plaats van het tweede
  • Minder CSS regels nodig
  • Verantwoordelijkheid ligt op de juiste plek
  • Gemakkelijker te onderhouden

Conclusie

De :has selector heeft onze manier van CSS schrijven fundamenteel veranderd. Waar we vroeger complexe JavaScript oplossingen of omslachtige CSS trucs nodig hadden, kunnen we nu elegante, declaratieve styling schrijven.

De belangrijkste voordelen:

  • Logischer CSS: Styling ligt waar het hoort, bij het element dat gestyled wordt
  • Minder JavaScript: Veel state management kan nu puur in CSS
  • Betere performance: CSS engines zijn geoptimaliseerd voor deze selectors
  • Onderhoudbaarder: Minder code, duidelijkere intenties

Wanneer gebruik je :has?

  • Parent styling op basis van child elementen
  • Sibling relationships waar je vooruit wilt kijken
  • State management zonder JavaScript (forms, popups, hovers)
  • Layout optimalisaties zoals margin management

Onze ervaring
Na anderhalf jaar gebruik van :has in productie kunnen we stellen dat het onze CSS aanzienlijk heeft vereenvoudigd. Bugs zijn verminderd, onderhoud is gemakkelijker geworden, en nieuwe features implementeren gaat sneller.

De :has selector is niet zomaar een nieuwe CSS feature
het is een paradigma shift die CSS dichter bij echte programmeertalen brengt, zonder de simpliciteit te verliezen waar CSS om bekendstaat.

Onze aanbeveling:
Begin vandaag nog met experimenteren. De browser ondersteuning is excellent en de voordelen zijn onmiddellijk merkbaar.

Bas Everaarts
Technologie