:has() pseudo-class

The CSS :has() pseudo-class, introduced in 2022 with CSS Selectors Level 4, allows developers to style parent elements based on their child elements’ state or presence. This guide covers all :has() use cases with practical HTML and CSS examples to build interactive layouts without JavaScript.

Use Case

:has() Selector

 CSS Example

HTML Example

Checkbox toggle

:has(input:checked)

.box:has(input:checked {background:lightgreen}

<div class="box"><input type="checkbox"></div>

Unchecked state

:has(input:not(:checked))

.box:has(input:not(:checked)){opacity:.5}

<div class="box"><input type="checkbox"></div>

Input focused

:has(input:focus)

.field:has(input:focus){border:2px solid blue}

<div class="field"><input></div>

Any focus inside

:has(:focus-within)

.card:has(:focus-within){outline:2px solid}

<div class="card"><input></div>

Disabled input

:has(input:disabled)

.row:has(input:disabled){opacity:.4}

<div class="row"><input disabled></div>

Required field

:has(input:required)

.field:has(input:required)::after{content:"*"}

<div class="field"><input required></div>

Valid input

:has(input:valid)

.group:has(input:valid){border:green}

<div class="group"><input required></div>

Invalid input

:has(input:invalid)

.group:has(input:invalid){border:red}

<div class="group"><input required></div>

Empty input

:has(input:placeholder-shown)

.wrap:has(input:placeholder-shown){opacity:.6}

<div class="wrap"><input placeholder="Name"></div>

Read-only

:has(:read-only)

.box:has(:read-only){background:#eee}

<div class="box"><input readonly></div>

Component

Selector

CSS

HTML

Show content

:has(:checked)

.wrap:has(:checked) .content{display:block}

<div class="wrap"><input type="checkbox"><div class="content"></div></div>

Accordion

:has(:checked ~ .panel)

.item:has(:checked~.panel){height:auto}

<div class="item"><input type="checkbox"><div class="panel"></div></div>

Tabs

:has(input[type=radio]:checked)

.tabs:has(#t1:checked) .c1{display:block}

<div class="tabs"><input id="t1" type="radio"></div>

Details open

:has(details[open])

.box:has(details[open]){border:2px solid}

<div class="box"><details open></details></div>

Modal open

body:has(dialog[open])

body:has(dialog[open]){overflow:hidden}

<dialog open></dialog>

Interaction

Selector

CSS

HTML

Child hover

:has(:hover)

.card:has(:hover){background:#f5f5f5}

<div class="card"><button></button></div>

Button hover

:has(button:hover)

.card:has(button:hover){transform:scale(1.05)}

<div class="card"><button>Hover</button></div>

Menu hover

:has(li:hover)

.menu:has(li:hover){background:#eee}

<ul class="menu"><li>Item</li></ul>

Link hover

:has(a:hover)

.box:has(a:hover){border-color:blue}

<div class="box"><a>Link</a></div>

Keyboard focus

:has(:focus-visible)

.card:has(:focus-visible){outline:2px solid}

<div class="card"><button></button></div>

Case

Selector

CSS

HTML

Active item

:has(.active)

.nav:has(.active){border-left:4px solid}

<nav class="nav"><a class="active"></a></nav>

Direct child active

:has(> .active)

.menu:has(>.active){background:#ddd}

<div class="menu"><div class="active"></div></div>

ARIA current

:has([aria-current="page"])

.nav:has([aria-current]){font-weight:bold}

<a aria-current="page"></a>

Dropdown open

:has([aria-expanded="true"])

.item:has([aria-expanded="true"]){}

<button aria-expanded="true"></button>

Media

Selector

CSS

HTML

Image present

:has(img)

.card:has(img){padding:0}

<div class="card"><img></div>

Video

:has(video)

.post:has(video){background:black}

<div class="post"><video></video></div>

Audio

:has(audio)

.post:has(audio){}

<div class="post"><audio></audio></div>

Iframe

:has(iframe)

.embed:has(iframe){}

<div class="embed"><iframe></iframe></div>

SVG icon

:has(svg)

.btn:has(svg){padding-left:40px}

<button class="btn"><svg></svg></button>

State

Selector

CSS

HTML

Error exists

:has(.error)

.form:has(.error){border:red}

<form><span class="error"></span></form>

Success

:has(.success)

.box:has(.success){border:green}

<div class="box"><span class="success"></span></div>

Loading

:has(.loading)

.card:has(.loading){opacity:.5}

<div class="card"><span class="loading"></span></div>

Badge

:has(.badge)

.item:has(.badge){padding-right:20px}

<div class="item"><span class="badge"></span></div>

Empty content

:has(:empty)

.box:has(:empty){display:none}

<div class="box"><span></span></div>

Use

Selector

CSS

HTML

List hover

:has(li:hover)

.list:has(li:hover){}

<ul class="list"><li></li></ul>

Table hover

:has(tr:hover)

.table:has(tr:hover){}

<table class="table"><tr></tr></table>

Empty cell

:has(td:empty)

.row:has(td:empty){}

<tr class="row"><td></td></tr>

Table exists

:has(table)

.wrap:has(table){overflow:auto}

<div class="wrap"><table></table></div>

Logic

Selector

CSS

HTML

Multiple states

:has(input:checked):has(.premium)

.card:has(input:checked):has(.premium){border:gold}

<div class="card"><input checked><span class="premium"></span></div>

Not disabled

:has(:not(.disabled))

.box:has(:not(.disabled)){}

<div class="box"><span></span></div>

Structural check

:has(> :nth-child(3))

.grid:has(>:nth-child(3)){}

<div class="grid"><div></div><div></div><div></div></div>

Leave a Reply

Your email address will not be published. Required fields are marked *