Catalin Ciubotaru avatar

CSS tidbits - How to style the parent based on its children

For those times when you have a DOM element, but its children are responsible for how it looks ✨.

The Problem

As an example of this, we’ll work on a radio-button group. The challenge is that each option has quite a bit of information ℹ️ inside, and based on that information, the radio-button should look different. Hopefully, this will make more sense with a peek 👀 at the final result.

Good to know

Not much, this post relies solely on HTML and CSS knowledge 📚.

The Solution

We’ll split this into a couple of steps:

  1. Build the HTML of the component 🏗️
  2. Add some basic CSS styling 🎨
  3. The magic touch 🧙‍♂️

This time, I’ll try a different approach. Instead of showing big chunks of code 🧑‍💻 and explaining what all of it does, I will go bit by bit, with small, incremental changes. Please let me know what you think 🤔(you can use Twitter for example 👇).

Let’s go!

Part 1 - Build the HTML of the component 🏗️

Like I mentioned, this is a radio-button group, so let’s start with that.

<div class='audience-option'>
<input type='radio' name='audience' id='humans' value='humans' />
<label class='option-description' for='humans'>Humans</label>
</div>
<div class='audience-option'>
<input type='radio' name='audience' id='robots' value='robots' />
<label class='option-description' for='robots'>Robots</label>
</div>
<div class='audience-option'>
<input type='radio' name='audience' id='aliens' value='aliens' />
<label class='option-description' for='aliens'>Aliens</label>
</div>

Here we have exactly that. Three radio-buttons, all of them using the same name, meaning they all set/use the same value: audience. They have unique IDs and unique values.

Each of them also has a label that allows the user to click 👆 on the label OR the radio-button to select the value.

All this results in:


Now, let’s add more details inside our label.

<div class='audience-option'>
<input type='radio' name='audience' id='humans' value='humans' />
<label class='option-description' for='humans'>
<p>🧑</p>
<h6>Humans</h6>
<p>Select this if your intended audience is native organisms of this planet</p>
</label>
</div>
<div class='audience-option'>
<input type='radio' name='audience' id='robots' value='robots' />
<label class='option-description' for='robots'>
<p>🤖</p>
<h6>Robots</h6>
<p>Select this if your intended audience is cybernetic beings</p>
</label>
</div>
<div class='audience-option'>
<input type='radio' name='audience' id='aliens' value='aliens' />
<label class='option-description' for='aliens'>
<p>👽</p>
<h6>Aliens</h6>
<p>Select this if your intended audience is organisms from other planets</p>
</label>
</div>

Now, each label has an icon, a title, and a description. Nothing special here. It’s ugly for now, but don’t worry about that yet 😉.

Here’s the updated result


Finally, to make this a bit more self-contained, let’s add a wrapper all around everything, and let’s add a label for this audience 📣 field.

<section>
<label for='audience'>Select audience: </label>
<div class='audience-options'>
<div class='audience-option'>
<input type='radio' name='audience' id='humans' value='humans' />
<label class='option-description' for='humans'>
<p>🧑</p>
<h6>Humans</h6>
<p>Select this if your intended audience is native organisms of this planet</p>
</label>
</div>
<div class='audience-option'>
<input type='radio' name='audience' id='robots' value='robots' />
<label class='option-description' for='robots'>
<p>🤖</p>
<h6>Robots</h6>
<p>Select this if your intended audience is cybernetic beings</p>
</label>
</div>
<div class='audience-option'>
<input type='radio' name='audience' id='aliens' value='aliens' />
<label class='option-description' for='aliens'>
<p>👽</p>
<h6>Aliens</h6>
<p>Select this if your intended audience is organisms from other planets</p>
</label>
</div>
</div>
</section>

Now it’s all complete. HTML wise, at least. We have a fully encapsulated field, that has a label and has a radio-group input for its value.

Here’s the final HTML result.

Part 2 - Add some CSS for styling 🎨

Ok, time to make this a bit prettier.

First, the wrapper. Let’s make sure the label and the radio-button group are side by side. Not a challenge 💪 for a flexbox.

section {
display: flex;
flex-direction: row;
gap: 12px;
}

Pretty straightforward. This will make the wrapper a flexbox, and put the 2 children side by side, with a space of 12px in between.


Now, for the audience-options. Let’s make the whole element prettier ✨ by adding some space.

.audience-options {
display: flex;
flex-direction: column;
gap: 8px;

& h6 {
font-size: 1rem;
margin: 0;
}
}

Besides the flexbox here, we’re also using some nested selectors. For your mental model 🧠, you can replace the & with the selector that it sits inside of. So here, every & is equivalent to .audience-options. This way, & h6 becomes .audience-options h6, which translates to a h6 inside an element with a class of audience-options.

I hope this is clear. In this nested selector we adjust the spacing of the title.

Neeeext! 😁


Finally, let’s make each option-description prettier ✨. Here’s what I ended up doing.

.option-description {
display: grid;
grid-template-columns: auto 200px;
gap: 12px;
border: 1px solid darkgrey;
border-radius: 12px;
padding: 0.5em;

& p {
margin: 0;
}

& p:first-of-type {
grid-row: 1/3;
font-size: 2rem;
}

& p:last-of-type {
font-size: 0.75rem;
}
}

It’s not spectacular, but it’s better. Same thing here with the nesting. That should make sense now. The last thing that’s a bit different here is the :first-of-type and :last-of-type selector. They do exactly that. Select the first p element in this case inside the .option-description (because of the &), and the last p element inside the .option-description.

Here’s another way of putting this:

  • & p:first-of-type ➡️ .option-description p:first-of-type ➡️ select the first p element inside an element with the option-description class.

  • & p:last-of-type ➡️ .option-description p:last-of-type ➡️ select the last p element inside an element with the option-description class.

With that out of the way, what we’re doing is using CSS Grid here to style each box. We want to make sure the emoji takes 2 rows, so it will be on the left side, and the title and the description on the right side.

This looks like a nice radio-button group.


Part 3 - The magic touch 🧙‍♂️

Ok, now we want to actually show within the box, which box is selected, and not using the radio-button circle thingy. And here lies the problem. We want to change the background of the option if, inside it, there’s a checked input 🤔.

✌️ :has to the rescue! ⚔️

Turns out this is simple to achieve.

.audience-option:has(input:checked) {
background-color: LightYellow;
}

What do we have here? Well, we’re selecting the elements that have the audience-option class, and inside, they HAVE an input that is checked. Makes sense?


Now, if we look at our result, we can see that the selected box has a yellow background. With that achieved, we can now hide the radio-button thingy.

.audience-options {
.....
& input {
display: none;
}
}

We added the hiding of the input here.

This looks delightful ✨ now!


Let’s do more things here. Let’s change cursors 👆, and let’s style the disabled inputs as well. Here’s what I ended up with:

.audience-option {
& label {
cursor: pointer;
}

&:has(input:checked) {
background-color: LightYellow;
}

&:has(input:disabled) {
background-color: WhiteSmoke;
color: Gray;
}

&:has(input:disabled) label {
cursor: not-allowed;
}
}

Nothing new here, just more usage of the :has selector. Isn’t this 🆒?

Well, that’s about it. Go wild with this knowledge 📚 now!

Furthermore, here's a link to a working CodePen.

Congrats 🎉

You made it yet through another article. Thanks for spending the time 🕰️ reading this, and hope it provided some sort of value, since time is a limited resource. Use it wisely. 🙏

Want more?

If you want to know more about this, or something doesn't make any sense, please let me know.

Also, if you have questions, you know where to find me… On the internet!

Be kind to each other! 🧡

Over and out

My Twitter avatar
Catalin Ciubotaru 🚀

📢 Check out my latest article on CSS tidbits! Discover how to style the parent element based on its children, unlocking endless possibilities for dynamic web design 🚀: https://catalincodes.com/posts/css-tidbits-how-to-style-parent-based-on-children #HTML #CSS #UI

Dec 10, 2023
24 people are talking about this

Wanna read more?

Updates delivered to your inbox!

A periodic update about my life, recent blog posts, how-tos, and discoveries.

No spam - unsubscribe at any time!