Opent in een nieuw tabblad

CSS & front-end

CSS :has() in de praktijk: de parent selector die alles verandert

Jarenlang was een parent selector in CSS een wensdroom. Nu kan het: zo gebruik je :has() voor formulieren, kaarten en layouts zonder extra JavaScript.

Door Manifest Digital Studio7 min lezen

In het kort

  • De CSS :has() selector selecteert een element op basis van wat het bevat of wat erna komt.
  • Sinds eind 2023 wordt :has() ondersteund in alle grote browsers.
  • De specificiteit van :has() is gelijk aan die van de meest specifieke selector tussen de haakjes.
  • Met :has() kun je veel JavaScript vervangen dat alleen classes op een parent zet.
  • Brede selectors zoals body:has() kunnen performance kosten, dus houd het anker specifiek.

De CSS :has() selector laat je een element stylen op basis van wat het bevat of wat erna komt. Daarmee is het de langgezochte ‘parent selector’: .card:has(img) selecteert elke kaart waarin een afbeelding staat. Sinds eind 2023 werkt :has() in alle grote browsers, dus je kunt hem gewoon in productie gebruiken.

In dit artikel laten we zien hoe de CSS has selector werkt, waar hij het meeste oplevert en waar je op moet letten. Met codevoorbeelden die je direct kunt overnemen.

Hoe werkt de CSS has selector?

Normaal werkt CSS van boven naar beneden: je selecteert een element en daarna zijn kinderen. Met :has() draai je dat om. Je selecteert het element zelf, maar alleen als de selector tussen de haakjes iets oplevert binnen (of relatief ten opzichte van) dat element.

/* Kaart met een afbeelding erin */
.card:has(img) { grid-template-rows: auto 1fr; }

/* Alleen als de afbeelding een direct kind is */
.card:has(> img) { padding-top: 0; }

/* Een kop die direct gevolgd wordt door een paragraaf */
h2:has(+ p) { margin-bottom: 0.5rem; }

Dat laatste voorbeeld laat zien dat :has() meer is dan een parent selector. Met de combinators + en ~ kun je ook een element stylen op basis van wat er na komt. Dat was voorheen onmogelijk zonder JavaScript.

Specificiteit en beperkingen

  • De specificiteit van :has() is gelijk aan die van de meest specifieke selector in de haakjes, net als bij :is().
  • Je kunt :has() niet in een andere :has() nesten.
  • Pseudo-elementen zoals ::before werken niet binnen :has().

Vier toepassingen van de CSS has selector

De theorie is simpel, de kracht zit in de toepassingen. Dit zijn de vier plekken waar we :has() het vaakst inzetten.

Toepassing 1: slimmere formulieren

Formulieren zijn misschien wel de beste use case. Je kunt een heel veldblok stylen op basis van de status van het invoerveld erin:

/* Markeer het hele veld als de invoer ongeldig is (na interactie) */
.field:has(input:user-invalid) {
  border-color: #d00;
}

/* Toon extra velden zodra een checkbox is aangevinkt */
.extra { display: none; }
form:has(#zakelijk:checked) .extra { display: grid; }

/* Verzendknop dimmen zolang het formulier ongeldig is */
form:has(:invalid) button[type="submit"] { opacity: 0.6; }

Het tweede voorbeeld vervangt een klassiek stukje JavaScript: velden tonen als iemand ‘zakelijke klant’ aanvinkt. Minder code, minder kans op fouten. Let op dat verborgen velden met display: none ook voor schermlezers verdwijnen; dat is hier precies de bedoeling, maar controleer of de volgorde logisch blijft.

Toepassing 2: kaarten en layouts die zich aanpassen

In contentrijke sites ziet niet elke kaart er hetzelfde uit. De ene heeft een afbeelding, de andere niet; de ene heeft een label, de andere niet. Met :has() hoef je daar geen aparte classes meer voor te maken in je CMS.

/* Kaart zonder afbeelding krijgt een gekleurde achtergrond */
.card:not(:has(img)) { background: var(--accent); }

/* Grid met meer dan drie items schakelt naar vier kolommen */
.grid:has(> :nth-child(4)) {
  grid-template-columns: repeat(4, 1fr);
}

Dat laatste is een handige truc: je telt kinderen zonder JavaScript. Zodra er een vierde item is, verandert de layout. Combineer dit met container queries voor componenten die zich volledig aanpassen aan hun inhoud en ruimte.

Toepassing 3: interactie en state op paginaniveau

Omdat je :has() ook op body of html kunt zetten, kun je de hele pagina laten reageren op een element ergens diep in de DOM:

/* Scroll blokkeren zolang het menu open is */
body:has(.nav[data-open="true"]) { overflow: hidden; }

/* Donkere modus via een toggle-checkbox */
html:has(#dark-toggle:checked) {
  color-scheme: dark;
}

/* Andere kaarten dimmen als je over één kaart hovert */
.grid:has(.card:hover) .card:not(:hover) { opacity: 0.5; }

Vooral het laatste effect is populair in interactieve portfolio’s en productoverzichten: de kaart onder je cursor springt eruit, de rest valt terug. Het is een microinteractie die je vroeger met JavaScript-events bouwde en nu in drie regels CSS schrijft. Meer van dit soort effecten vind je in ons artikel over gedurfde CSS-knoppen en hover-effecten.

Toepassing 4: navigatie en menu’s

Navigatie is een plek waar veel kleine stukjes JavaScript zich ophopen: een class op het menu-item met een submenu, een class op de header als het menu open staat, een class op de link van de huidige pagina. Een deel daarvan kun je met :has() vervangen.

/* Pijltje tonen bij menu-items met een submenu */
.menu li:has(> ul) > a::after { content: " \25BE"; }

/* Header krijgt een schaduw zodra een submenu open is */
.header:has(.submenu:focus-within) { box-shadow: 0 2px 0 #000; }

/* Ouder-item markeren als een link in het submenu actief is */
.menu li:has([aria-current="page"]) > a { font-weight: 700; }

Vooral dat laatste is handig. Veel CMS’en markeren alleen de actieve link zelf, niet het hoofdmenu-item erboven. Met :has() lees je de bestaande aria-current uit en hoef je de menu-output niet aan te passen. Je gebruikt dus een toegankelijkheidsattribuut dat er al hoort te staan als bron voor je styling. Dat is precies hoe je het wilt: de semantiek bepaalt de stijl, niet andersom.

Wanneer je :has() beter niet gebruikt

Hoe krachtig hij ook is, :has() is niet voor alles de beste keuze. Een paar situaties waarin we hem bewust laten liggen:

  • Als een gewone class duidelijker is. Bepaalt je CMS of component al welke variant iets is, dan is een expliciete class vaak beter leesbaar voor collega’s dan een slimme selector.
  • Als er echte logica achter zit. Formulierlogica met validatie op de server, opgeslagen voorkeuren of berekeningen horen in JavaScript of in de backend. CSS kan alleen reageren op wat er nu in de DOM staat.
  • Als de selector de hele pagina moet doorzoeken. Een regel als :has(.iets) zonder duidelijk anker laat de browser bij elke wijziging veel controleren. Zet hem op het kleinste element dat logisch is.
  • Als toegankelijkheid ervan afhangt. Een menu dat visueel opengaat via :has(), moet voor schermlezers nog steeds de juiste status doorgeven. Dat regel je met attributen, niet met CSS.

Onze vuistregel: gebruik :has() voor presentatie die volgt uit de structuur van je HTML, en houd gedrag en status waar ze horen. Dan blijft je code voorspelbaar en houd je de winst van minder JavaScript.

Waar je op moet letten

Aandachtspunt Advies
Browsersupport Breed ondersteund in moderne browsers; zorg dat de pagina zonder :has() nog bruikbaar is als je oudere browsers moet bedienen
Performance Brede selectors als body:has(...) of *:has(...) dwingen de browser veel te controleren; houd het anker zo specifiek mogelijk
Leesbaarheid Lange ketens worden snel cryptisch; zet er een korte comment bij
Toegankelijkheid Visuele state via CSS vervangt geen ARIA-attributen zoals aria-expanded

Wil je zeker weten dat je fallback klopt, dan kun je feature detection gebruiken met @supports selector(:has(a)). Daarmee schrijf je de :has()-regels alleen voor browsers die ze begrijpen. Uitgebreide referentie vind je op MDN Web Docs.

Zo pak je het aan: :has() invoeren in een bestaand project

  1. Zoek naar ‘state-JavaScript’: scripts die alleen een class toevoegen aan een parent om de styling te veranderen. Dat zijn je eerste kandidaten.
  2. Begin met formulieren: veldstatus, conditionele velden en knopstatus zijn snelle winst met weinig risico.
  3. Ruim CMS-classes op: vervang modifier-classes als .card--no-image door :has()-regels op basis van de echte inhoud.
  4. Test zonder support: bekijk of de pagina nog werkt als de :has()-regels wegvallen. Progressive enhancement, geen afhankelijkheid.
  5. Documenteer je patronen, zodat je team weet waarom een component reageert zoals hij reageert.

Liever direct werkende voorbeelden? In ons CSS-lab met gratis snippets vind je kant-en-klare patronen die je kunt kopiëren en aanpassen.

Conclusie

De CSS has selector maakt een hele categorie JavaScript overbodig. Formulieren die reageren op hun eigen status, kaarten die zich aanpassen aan hun inhoud en pagina’s die reageren op interactie: het kan nu allemaal in pure CSS. Gebruik hem met beleid, houd selectors specifiek en bouw op als progressive enhancement. Begin klein, bijvoorbeeld met één formulier of één kaartcomponent, en breid uit zodra je team vertrouwd is met de manier van denken. Elke regel JavaScript die je zo schrapt, is een regel minder om te onderhouden.

Wil je een site met dit soort slimme, vloeiende interacties zonder zware scripts? Ontdek wat we doen met interactief webdesign en animaties.

Veelgestelde vragen

Wordt :has() door alle browsers ondersteund?

Alle grote moderne browsers ondersteunen :has() sinds eind 2023. Voor oudere browsers bouw je het als progressive enhancement, zodat de pagina ook zonder werkt.

Kan ik met :has() ook naar een vorig element kijken?

Indirect wel. Met h2:has(+ p) style je een element op basis van het element dat erna komt, wat eerder alleen met JavaScript kon.

Is :has() slecht voor de performance?

Bij normaal gebruik niet merkbaar. Zeer brede selectors op body of met de universele selector kunnen wel extra rekenwerk kosten.

Kan ik :has() nesten?

Nee, een :has() binnen een andere :has() is niet toegestaan. Pseudo-elementen werken er ook niet in.

Manifest Digital Studio

Wij bouwen gedurfde, snelle websites en maken merken vindbaar in Google en AI-zoekmachines. Vragen over dit artikel? Stel ze direct aan de developer.