CSS Style Parent based on Child

How to use: has as a Parent Selector

Understand the functionality of the has method starting from the fundamentals. Consider a specific <figure> element as depicted in the illustration. Occasionally, our illustration encompasses solely an image.

Example

<figure>
  <img src="https://placehold.co/400x300/34495e/ffffff?text=Logo" alt="C# Tutorial logo">
</figure>
Other times, there's an image with a caption.
<figure>
  <img src= "https://placehold.co/400x300/34495e/ffffff?text=Logo" alt= "black dog smiling in the sun">
 
<figcaption>Maggie loves being outside off-leash.</figcaption>
</figure>

Now, we will implement certain styles to the image that will be activated only when a figcaption is present within the figure.

Example

figure:has(figcaption) {
  background: white;
  padding: 0.6rem;
}

Let us understand this by taking an example:

HTML Code:

Example

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <title>Document</title>
 <style>
  figure {
 margin: 0;
 box-shadow: 1px 1px 4px rgba(1, 1, 1, 0.4);
}
figure:has(figcaption) {
/* if a figure has a figcaption, 
  apply this to the figure... */
 background: white;
 padding: 0.6rem;
}
figure:has(pre) {
/* if a figure has a pre, 
  apply this to the figure... */
 background: rgb(252, 232, 255);
 border: 3px solid white;
 padding: 1rem;
}
figure:not(:has(:not(img))) {
/* if a figure does not have 
 any element that is not an image, 
 apply this to the figure... */ 
 display: flex;
}
/* figure:has(img:only-child) 
works too, which is easier to understand but doesn't show off:not */

figure:has(img) figcaption {
/* if a figure has an image, 
 apply this to its figcaption... */
 font-size: 90%;
 font-style: italic;
 margin: 0.6rem 0 0.1rem;
}

figure figcaption {
 font-family: Georgia;
 color: black;
}
figure:has(pre) figcaption {
/* if a figure has a pre, 
 apply this to its figcaption... */
 margin: 0.6rem 0 0.1rem;
}

figure img {
 max-width: 100%;
 display: block;
 object-fit: cover;
}
figure pre {
 font-family: Courier New, monospace;
 margin: 0;
 color: rgb(159, 43, 148);
}
body {
 background-color: #2a4a00; 
 background-color: color(display-p3 0.12 0.28 0.021);
}
article {
 margin: 2rem;
 display: grid;
 grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
 gap: calc(1rem + 2vw);
 font-family: Georgia;
}

/* Warning message 
  about support for :has() */

@supports selector(:has(img)) {
 body small {
  display: none;
 }
}
small {
 background: crimson;
 color: white;
 padding: 1rem;
 display: block;
 font-family: Helvetica;
 font-weight: 600;
 font-size: 1rem;
 max-width: max-content;
 margin-bottom: 2rem;
 position: absolute;
 top: 1rem;
 left: 1rem;
}
small code {
 font-family: courier;
 font-size: 1.2rem;
 background: rgba(255, 255, 255, 0.33);
}
small a {
 color: white;
}
small a:hover {
 color: rgba(255, 255, 255, 0.8);
}
 </style>
</head>
<body>
 

<article>
 <figure>
  <img src="https://placehold.co/400x300/1abc9c/ffffff?text=Sample+Image" alt="spring flowers">
 </figure>
 <figure>
  <img src= "https://placehold.co/400x300/1abc9c/ffffff?text=Sample+Image" alt= "black dog smiling in the sun">
  <figcaption>Maggie loves being outside off-leash.</figcaption>
 </figure>
 <figure>
  <figcaption>Using <code>:has()</code> can be easy.</figcaption>
<pre><code>
figure:has(pre) {
 background: rgb(252, 232, 255);
 border: 3px solid white;
 padding: 1rem;
}
</code></pre>
 </figure>
</article>

<small>This demo <a href="https://caniuse.com/css-has" target="_blank">requires a browser that supports <code>:has()</code></a>.</small>

</body>
</html>

Output:

Output

In the above code, we have targeted any figure that contains a pre-element by using figure:has(pre).
figure:has(pre) { 
  background: rgb(252, 232, 255);
  border: 3px solid white;
  padding: 1rem;
}

In addition, we utilized the Selector Feature Query to conceal a notification regarding browser compatibility whenever the existing browser supports the has function.

Example

@supports selector(:has(img)) {
  small {
    display: none;
  }
}

Example using: has with CSS Grid

Let's explore another instance where we can employ has as a parent selector to efficiently address a highly practical requirement.

The: has pseudo-class makes this simple:

Example

article:has(img) {
  grid-column: span 2;
  grid-row: span 2;
}

HTML code:

Example

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <title>Document</title>
 <style>
  

body {
 font-family: Avenir;
 background-color: #0b5775;
 background: color(display-p3 0 0.296 0.42);
}
main {
 display: grid;
 grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
 gap: 1rem;
 margin: 1rem;
}
article {
 background: white;
 background: color(display-p3 0.925 0.912 0.865);
 border-radius: 8px;
 padding: 1rem;
 display: flex;
 flex-flow: column;
}

article:has(img) {
 grid-column: span 2;
 grid-row: span 2;
}
img {
 order: -1;
}
h2 {
 margin: 0;
 font-weight: 200;
 line-height: 1.1;
}
h2 + p {
 margin-top: 0.33rem;
}
article:has(img) h2 {
 margin-top: 1rem;
}
p {
 margin-bottom: 0;
 font-size: 0.8rem;
}
main a {
 text-decoration-thickness: 0.4rem;
 text-decoration-color: rgba(9, 146, 163, 0.3);
 text-underline-offset: -0.2rem;
 text-decoration-skip-ink: none;
 color: inherit;
}
main a:hover {
 color: inherit;
 color: rgba(9, 146, 163, 1);
}
img {
 max-width: 100%;
}

/* Warning message 
  about support for :has() */

@supports selector(:has(figcaption)) {
 body small {
  display: none;
 }
}
small {
 background: crimson;
 color: white;
 padding: 1rem;
 display: block;
 font-family: Helvetica;
 font-weight: 600;
 font-size: 1rem;
 max-width: max-content;
 margin-bottom: 2rem;
 position: absolute;
 top: 1rem;
 left: 1rem;
}
small code {
 font-family: courier;
 font-size: 1.2rem;
 background: rgba(255, 255, 255, 0.33);
}
small a {
 color: white;
}
small a:hover {
 color: rgba(255, 255, 255, 0.8);
}

 </style>
</head>
<body>
 <h1>Welcome to C# Programming</h1>
 

<main>
 <article>
  <h2>This is an article headline</h2>
  <p> Here's the description of this article if you decide to click through to read it.</p>
 </article>
 <article>
  <h2>This is a teaser for another article</h2>
  <p>This is a shorter description.</p>
 </article>
 <article>
  <h2>Night</h2>
  <img src='https://placehold.co/400x300/1abc9c/ffffff?text=Sample+Image' alt=''>
  <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
 </article> 
 <article>
  <h2>Winter Storms</h2>
  <img src='https://placehold.co/400x300/1abc9c/ffffff?text=Sample+Image' alt=''>
  <p>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
 </article>
 <article>
  <h2>Lorem ipsum</h2>
  <p>Duis aute irure dolor in reprehenderit in voluptate.</p>
 </article>
 <article>
  <h2>:has( )</h2>
  <p>Read about <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:has">:has( ) at MDN</a>.</p>
 </article> 
 </main>
 
 <small>This demo <a href="https://caniuse.com/css-has" target="_blank">requires a browser that supports <code>:has()</code></a>.</small>
 
 
</body>
</html>

Output:

The preceding pair of instances employs an element selector. Nonetheless, by utilizing has, we can merge all selectors, such as the class selector, attribute selector, and ID selector.

Using :has with the Child Combinator

Before utilizing the has method, it is important to understand the descendant selector and child combinator.

The descendant selector has been a part of CSS since the beginning. It is a stylish term used when we separate two basic selectors with a space. For example:

Example

a img { ... }

With this selector, we can merge <img> with <a> regardless of their distance in the HTML DOM structure.

Example

<a>
  <figure>
    <img src="https://placehold.co/400x300/1abc9c/ffffff?text=Sample+Image" alt="don't forget alt text" width="200" height="100">
  </figure>
</a>

Let us understand this by taking an example:

HTML code:

Example

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <title>Document</title>
 <style>
  

a:has(img) {
 border: 10px solid darkolivegreen;
 background: pale goldenrod;
 colour: saddle brown;
}
a:has(> img) {
 border: 10px solid chocolate;
 background: pink;
}
a {
 display: block;
 text-decoration: none;
}

img {
 max-width: 100%;
 display: block;
}
body {
 background-color: antique white;
}
p {
 margin: 0.5rem 0 0;
}
main {
 margin: 1.5rem 1rem;
 display: grid;
 grid-template-columns: 155px 155px;
 gap: 1rem;
 font-family: Avenir, Helvetica;
 font-size: 0.8rem;
 align-items: start;
}
figure {
 margin: 0;
}
figcaption {
 padding: 0.5rem;
}
/* Warning message 
  about support for :has() */

@supports selector(:has(figcaption)) {
 body small {
  display: none;
 }
}
small {
 background: crimson;
 color: white;
 padding: 1rem;
 display: block;
 font-family: Helvetica;
 font-weight: 600;
 font-size: 1rem;
 max-width: max-content;
 margin-bottom: 2rem;
 position: absolute;
 top: 1rem;
 left: 1rem;
}
small code {
 font-family: courier;
 font-size: 1.2rem;
 background: rgba(255, 255, 255, 0.33);
}
small a {
 color: white;
 display: inline;
 padding: 0;
 background: none;
}
small a:hover {
 color: rgba(255, 255, 255, 0.8);
}

 </style>
</head>
<body>
 <h1>Welcome To C# Tutorial</h1>
 

<main>
 <a href="#">
  <figure>
  <img src='https://placehold.co/400x300/1abc9c/ffffff?text=Sample+Image' alt=''>
   <figcaption>This image is wrapped in a figure element.</figcaption>
  </figure>
 </a>
  <a href="#">
  <img src='https://placehold.co/200x150/1abc9c/ffffff?text=Sample+Image' alt=''>
 </a>
</main>

<small>This demo <a href="https://caniuse.com/css-has" target="_blank">requires a browser that supports <code>:has()</code></a>.</small>

</body>
</html>

Output:

Here, within the preceding example, there exist two supplementary types of combinators. These particular combinators are known as siblings. They play a crucial role in expanding the functionality of beyond just being a parent selector.

Using :has with Sibling Combinators

Let's examine the two selectors involving sibling relationships. One is the adjacent sibling combinator (+) and the other is the general sibling combinator (~).

Now, we will proceed with incorporating the next-sibling combinator (+) into our code. This combinator specifically targets paragraphs that immediately follow an h2 element.

h2 + p

Example

<h2>Headline</h2>
<p>Paragraph that is selected by `h2 + p`, because it's directly after `h2`. </p>

By utilizing the subsequent sibling combinator (~), we have the ability to target all paragraphs following the h2 tag. It is important to note that these elements must be siblings, meaning they share the same parent element, allowing for other HTML elements to appear in between them.

h2 ~ p

Example

<h2>Headline</h2>
<h3>Something else</h3>
<p>Paragraph that is selected by `h2 ~ p`.</p>
<p>This paragraph is also selected.</p>

Now, utilizing both the h2 + p and h2 ~ p selectors enables us to specifically target paragraph elements without affecting the h2 headings. Just like with the previous element, the selector will focus on the last element in the list. However, if our goal is to target the h2 element itself, we can achieve this by incorporating sibling combinators along with the has method.

Styling form states without JS

There are numerous pseudo-classes nested within the has selector. These pseudo-classes have the capability to perform a variety of tasks. Additionally, utilizing pseudo-classes allows us to specifically style an element based on a particular state or style one of its child elements. Moreover, pseudo-classes enable the capturing of states without relying on JavaScript, allowing for the styling of any element in the Document Object Model (DOM) based on its state.

Form input fields offer a robust method to collect and store user input. Pseudo-classes specific to forms encompass :autofill, :enabled, :disabled, :read-only, :read-write, :placeholder-shown, :default, :checked, :indeterminate, :valid, :invalid, :in-range, :out-of-range, :required, and :optional.

Let's tackle a scenario and work on developing a simple form.

Example

<form>
  <div>
    <label for="name">Name</label> 
    <input type="text" id="name">
  </div>
  <div>
    <label for="site">Website</label> 
    <input type="url" id="site">
  </div>
  <div>
    <label for="email">Email</label>
    <input type="email" id="email">
  </div>
</form>

Let's apply some background style for the form.

Example

form:has(:focus-visible) { 
  background: antique white;
}

Now, we will transition to utilizing form:focus-within instead, which will function similarly to form: has(:focus). In this case, whenever a field is in focus, the focus pseudo-class will consistently implement CSS styles. The focus-visible pseudo-class offers a dependable method to customize a focus indicator exclusively when the browser intends to display one inherently, employing the intricate algorithms the browser employs to decide when to showcase a focus ring.

Now, the intention is to customize the appearance of the remaining fields, those that are not currently active, by adjusting the color of their label text and the border color of the input. Previously, accomplishing this task necessitated the use of JavaScript through the has method. However, with advancements, this can now be achieved using CSS.

Example

form:has(:focus-visible) div:has(input:not(:focus-visible)) label {
  color: Peru;
}
form:has(:focus-visible) div:has(input:not(:focus-visible)) input {
  border: 2px solid peru;
}

You can observe this code in action in the demo below by clicking within any of the text fields. As previously explained, the background of the form undergoes modifications, and the colors of the label and input borders for fields that are not currently selected also alter.

Now, let's consider an example to enhance comprehension.

HTML code:

Example

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <title>Document</title>
 <style>
  

@layer basic, focus, dark Mode, invalid;

/* ---------------------------------
  The styling when a field has the focus
  --------------------------------- */
@layer focus {
 /* -- Styling the whole form when any field has focus -- */
 /* Could use `form:focus-within` instead */
 form:has(:focus-visible) { 
  background: antique white;
  border: 4px solid antique white;
 }
 /* -- Styling other fields when one field has focus -- */
 form:has(:focus-visible) div:has(input:not(:focus-visible)) label {
  color: Peru;
 }
 form:has(:focus-visible) div:has(input:not(:focus-visible)) input {
  border: 2px solid peru;
 }
} /* End "focus" layer */

/* --------------------------------- 
  Styling field if input is invalid 
  --------------------------------- */
@layer invalid {
 input: invalid {
  outline: 4px solid red;
  border: 2px solid red;
 }
 div:has(input:invalid) label {
  color: crimson;
 } 
 label:has(+ input:invalid)::before {
  content: '✖ ';
  color: crimson;
 }
} /* End "invalid" layer */

/* ---------------------------------
  Dark Mode when the checkbox is checked 
  --------------------------------- */
@layer dark Mode {
 body:has(input[type="checkbox"]:checked) {
  background: blue;
  --primary-color: white;
 }
 body:has(input[type="checkbox"]:checked) form { 
  border: 4px solid white;
 }
 body:has(input[type="checkbox"]:checked) form:has(:focus-visible) {
  background: navy;
 }
 body:has(input[type="checkbox"]:checked) input:focus-visible {
  outline: 4px solid lightsalmon;
  border: 2px solid lightsalmon; /* needed for Chrome */
 }
} /* End "darkmode" layer */

/* --------------------------------- 
  Basic styling 
  --------------------------------- */
@layer basic {
 body {
  background: burlywood;
  --primary-color: black;
  color: var(--primary-color);
  margin: calc(1rem + 2vw);
 }
 
 /* ---- Layout ---- */
 article {
  display: grid;
  grid-template-columns: 1fr min-content max-content 1fr;
  grid-column-gap: calc(4vw + 0.5rem);
 }
 form {
  grid-column: 2 / 3;
 }
 footer {
  width: 100%;
 }
 
 /* -- Basic form styling -- */
 form { 
  border: 4px solid white;
  padding: 2rem 3rem;
  width: min-content;
 }
 form label,
 form input {
  display: block;
 }
 label, input {
  font-size: 1.2rem;
  font-family: Avenir, Helvetica, sans-serif;
 }
 div { 
  margin: 1.25rem 0;
 }
 input {
  margin: 0.25rem 0;
  padding: 0.5rem;
  appearance: none;
  border: 2px solid var(--primary-color);
 }
 :focus-visible {
  outline: 4px solid sienna;
  border: 2px solid sienna; /* needed for Chrome */
 }
 input[type="checkbox"] {
  float: left;
  margin-right: 0.66rem;
 }
 footer:has(input[type="checkbox"]) label {
  display: inline-block;
  margin: 1px 0 0;
 }

 /* -- Custom checkbox styling -- */
 @supports(appearance: none) {
  input[type="checkbox"] {
   appearance: none;
   width: 1.6rem;
   height: 1.6rem;
   border: 2px solid var(--primary-color);
   background: white;
   margin-top: 0;
  }
  input[type="checkbox"]:checked {
   position: relative;
   background: none;
  }
  input[type="checkbox"]:checked::after {
   position: absolute;
   top: 0.35rem; 
   left: 0.12rem;
   content:" ";
   width: 0.9rem;
   height: 0.3rem;
   border: 4px solid var(--primary-color);
   border-right: none;
   border-top: none;
   transform: rotate(-45deg);
  }
 }
} /* End "basic" layer */

/* Warning message 
  about support for :has() */

@supports selector(:has(img)) {
 body small {
  display: none;
 }
}
small {
 background: crimson;
 color: white;
 padding: 1rem;
 display: block;
 font-family: Helvetica;
 font-weight: 600;
 font-size: 1rem;
 max-width: max-content;
 margin-bottom: 2rem;
 position: absolute;
 top: 1rem;
 left: 1rem;
}
small code {
 font-family: courier;
 font-size: 1.2rem;
 background: rgba(255, 255, 255, 0.33);
}
small a {
 color: white;
}
small a:hover {
 color: rgba(255, 255, 255, 0.8);
}

 </style>
</head>
<body>
 <h1>Welcome to C# Programming.</h1>
 

<article>
 <form>
  <div>
   <label for="name">Name</label> 
   <input type="text" id="name">
  </div>
  <div>
   <label for="site">Website</label> 
   <input type="url" id="site">
  </div>
  <div>
   <label for="email">Email</label>
   <input type="email" id="email">
  </div>
 </form>
 <footer>
  <input type="checkbox" id="check"> <label for="check">Dark mode</label>
 </footer>
</article>
<small>To see the affect of <code>:has( )</code>, use a <a href="https://caniuse.com/css-has" target="_blank">browser that supports it</a>.</small>

</body>
</html>

Output:

In the provided code snippet, we will implement a mechanism to alert the user when an error is encountered. Below is the revised code snippet for achieving this functionality:

Example

try:
    # Code that may cause an error

<figure>

C# Tutorial logo

</figure>

Other times, there's an image with a caption.

<figure>

black dog smiling in the sun

<figcaption>Maggie loves being outside off-leash.</figcaption>

</figure>

Example

except Exception as e:
    print("An error has occurred: {}".format(e))
    # Additional code to handle the error

figure:has(figcaption) {

background: white;

padding: 0.6rem;

}

Example

Example

input:invalid {
  outline: 4px solid red;
  border: 2px solid red;
}

Now, let's make the warning text color red.

Example

div:has(input:invalid) label {
  color: red;
}

Dark Mode Toggle with no JS

Let's offer users the functionality to switch between a light and dark theme. Below is the code snippet for implementing this feature.

Example

body:has(input[type="checkbox"]:checked) {
  background: blue;
  --primary-color: white;
}
body:has(input[type="checkbox"]:checked) form { 
  border: 4px solid white;
}
body:has(input[type="checkbox"]:checked) form:has(:focus-visible) {
  background: navy;
}
body:has(input[type="checkbox"]:checked) input:focus-visible {
  outline: 4px solid lightsalmon;
}

Now, let's include the select menu feature to enable users to choose multiple colors.

Example

body:has(option[value="pony"]:checked) {
  --font-family: cursive;
  --text-color: #b10267;
 
--body-background: #ee458e;
 
--main-background: #f4b6d2;
}

Now, let's write the complete code :

HTML code:

Example

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <title>Document</title>
 <style>
  

body:has(option[value="pony"]:checked) {
 --font-family: cursive;
 --text-color: #b10267;
 --body-background: #ee458e;
 --main-background: #f4b6d2;
}
body:has(option[value="wine"]:checked) {
 --font-family: avenir, helvetica;
 --text-color: white;
 --body-background: black;
 --main-background: brown;
}
body:has(option[value="molly"]:checked) {
 --font-family: helvetica;
 --text-color: white;
 --body-background: #6c3;
 --main-background: #09c;
}
body:has(option[value="Zeldman"]:checked) {
 --font-family: Georgia;
 --text-color: black;
 --body-background: #c30;
 --main-background: #f60;
}
body { 
 background-color: var(--body-background); 
}
main {
 color: var(--text-color);
 background-color: var(--main-background);
 padding: 2rem;
 height: 80vh;
 font-family: var(--font-family);
}
h1 {
 margin: 0;
 font-weight: 400;
 font-size: 2.4rem;
}

/* Warning message about support for :has() */

@supports selector(:has(img)) {
 body small {
  display: none;
 }
}
small {
 background: red;
 color: white;
 padding: 1rem;
 display: block;
 font-family: Helvetica;
 font-weight: 600;
 font-size: 1rem;
 max-width: max-content;
 margin-bottom: 2rem;
 position: absolute;
 top: 1rem;
 left: 1rem;
}
small code {
 font-family: courier;
 font-size: 1.2rem;
 background: rgba(255, 255, 255, 0.33);
}
small a {
 color: white;
}
small a:hover {
 color: rgba(255, 255, 255, 0.8);
}

 </style>
</head>
<body>
 <h1>Welcome to C# Programming</h1>
 

<main>
 <h1>Choose a theme</h1>
 <menu>
  <label for="color-mode">Options:</label>
  <select name="color-mode" id="color-mode">
   <option value="pony">Pony</option>
   <option value="wine">Wine</option>
   <option value="molly">Molly</option>
   <option value="zeldman">Zeldman</option>
  </select>
 </menu>
</main>

<small>This demo <a href="https://caniuse.com/css-has" target="_blank">requires a browser that supports <code>:has()</code></a>.</small>

</body>
</html>

Output:

Input Required

This code uses input(). Please provide values below: