CSS Tutorial menu

CSS :has(), :is(), :where() and Nesting

Modern CSS has new selectors that once needed JavaScript or a tool like Sass. All of them work in every modern browser today.

:has(): The Parent Selector

For many years CSS could only look down the page tree: "style a link inside a card". It could not say "style a card that contains an image". :has() makes this possible.

Example

css
/* A card that contains an image */
.card:has(img) {
  padding: 0;
}

/* A form that has an invalid field */
form:has(:user-invalid) button {
  opacity: 0.5;
}

Read A:has(B) as "A that has B inside it".

Example

html
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; }
.option {
  display: block;
  padding: 12px;
  margin-bottom: 8px;
  border: 2px solid #dddddd;
  border-radius: 8px;
  cursor: pointer;
}
.option:has(input:checked) {
  border-color: #264de4;
  background-color: #eef2ff;
}
.plans:has(input:checked) .hint {
  display: none;
}
</style>
</head>
<body>

<div class="plans">
  <p class="hint">Please choose a class time:</p>
  <label class="option"><input type="radio" name="time"> Morning (9 to 12)</label>
  <label class="option"><input type="radio" name="time"> Evening (5 to 8)</label>
  <label class="option"><input type="radio" name="time"> Weekend</label>
</div>

</body>
</html>
Try it Yourself »

The selected box gets a blue border, and the hint disappears, all with no JavaScript.

:has() can also look at the next sibling: h2:has(+ p) selects an <h2> that is directly followed by a <p>.

:has() is powerful, but very broad rules like body:has(...) make the browser do more work. Keep the selector inside :has() short and specific.

:is(): Shorter Selector Lists

:is() takes a list of selectors and matches any of them. It saves a lot of repeating.

Example

css
/* Old way */
article h1,
article h2,
article h3 {
  color: #264de4;
}

/* With :is() */
article :is(h1, h2, h3) {
  color: #264de4;
}

Another plus: if one selector in a normal comma list is invalid, the whole rule is ignored. Inside :is(), an invalid selector is just skipped and the rest still works.

:where(): The Same, but With Zero Specificity

:where() matches exactly like :is(). The only difference is specificity:

SelectorSpecificity
:is(#nav, .menu) aTakes the strongest item inside: here the id, so 1-0-1
:where(#nav, .menu) aEverything inside :where() counts as 0, so 0-0-1

This makes :where() perfect for default styles that should be easy to override:

Example

html
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; }
/* Default link style with zero specificity */
:where(.content) a {
  color: #264de4;
  text-decoration: underline;
}
/* A simple class easily overrides it */
.btn {
  color: white;
  background-color: seagreen;
  text-decoration: none;
  padding: 6px 12px;
  border-radius: 4px;
}
</style>
</head>
<body>

<div class="content">
  <p>Read our <a href="#">admission rules</a> first.</p>
  <p><a class="btn" href="#">Apply now</a></p>
</div>

</body>
</html>
Try it Yourself »

Without :where(), the rule .content a (0-1-1) would beat .btn (0-1-0) and the button text would be blue.

Native CSS Nesting

Nesting lets you write rules inside other rules, so related styles stay together. It was made popular by Sass, and now plain CSS can do it.

Example

css
.card {
  padding: 16px;
  border: 1px solid #dddddd;

  h3 {
    margin-top: 0;          /* same as .card h3 */
  }

  & .price {
    color: crimson;         /* same as .card .price */
  }

  &:hover {
    border-color: #264de4;  /* same as .card:hover */
  }

  &.featured {
    border-width: 3px;      /* same as .card.featured */
  }

  @media (min-width: 600px) {
    padding: 24px;          /* .card on wider screens */
  }
}

The & means "the parent selector". You need & when there must be no space, such as &:hover or &.featured. For a child selector like h3, the & is optional.

Example

html
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; }
.menu {
  display: flex;
  gap: 8px;
  padding: 0;
  list-style: none;

  a {
    display: block;
    padding: 8px 14px;
    border-radius: 6px;
    color: #1f2937;
    text-decoration: none;

    &:hover {
      background-color: #eef2ff;
    }
    &.active {
      background-color: #264de4;
      color: white;
    }
  }
}
</style>
</head>
<body>

<ul class="menu">
  <li><a class="active" href="#">Home</a></li>
  <li><a href="#">Courses</a></li>
  <li><a href="#">Tutorials</a></li>
</ul>

</body>
</html>
Try it Yourself »

Do not nest too deep. Two or three levels is plenty. Deep nesting creates long, strong selectors that are hard to override, the same problem as long selectors written by hand.

Exercise

Exercise

Which selector styles a .card only when it contains a <video> element?

Learn CSS with a teacher

Join the Web Development course at Workownix, online or in Orangi Town, Karachi, and get a certificate.

See the course Ask on WhatsApp