Mike Ekkel site logo

CSS Combinators: explained and visualized

First published: Alt text

Selectors help us target a specific HTML element we want to apply styling to on our page. There are many selectors available to us, allowing for very precise control over styling. The combinator is a type of selector that combines different selectors and gives them a useful relationship to each other.

Sometimes we want to style based on the relationship between two elements. On my blog, for example, paragraphs have different styling when it immediately follows a heading. This is exactly what combinators allow us to do.

In this post, we're going to take a closer look at each of the four combinators: descendent, child, general sibling, and adjacent sibling. We'll look at example code as well as visualizations of how the combinators work.

Descendent combinator

The descendent combinator — represented by a ‎‏‏‎ ‎‎ (space) — will select all elements that are descendants of the first element. This is useful for when you want to change the styling of a component for a specific part of the page but not anywhere else on the page.

Example

An example of this would be div.content span will match all <span> elements inside the <div class='content'>. Let's say we have the following CSS:

span {
  background-color: red;
}

div.content span {
  background-color: green;
}

The <span> element has a red background in the above example. However, when a <span> element is inside of a <div class='content'> it will have a green background. To give you a better understanding of this, I've visualized it:

Visualization of the descendant combinator

It doesn't matter if a <span> element is a direct descendant of <div.content> or not. As long as a parent, somewhere up the DOM tree, is <div.content> it will have a green background color. This is not the case for our next combinator, though.

Child combinator

The child combinator — represented by a > — will select all elements that are direct descendants, known as children, of the parent element. This is perfect for when you want to style, for example, a list within a list. Perhaps to-do items need to be bold and nested to-do items need to be a normal weight.

Example

Going back to our previous example, the descendant combinator, we can easily highlight the behavior by changing the combinator! So let's use the exact same code, but this time we're going to use the child combinator.

span {
  background-color: red;
}

div.content > span {
  background-color: green;
}

Just like before, the <span> element has a red background. When a <span> element is a child of a <div class='content'> it will have a green background. However, if the <span> element is nested deeper it will still be red. Let's visualize that:

Visualization of the child combinator

As soon as the <span> element is separated from its parent by being nested a level deeper, it will not be selected by the child combinator.

General sibling combinator

The general sibling combinator — represented by a ~ — will select all siblings that follow the first element. The sibling element doesn't have to directly follow the first element but they need to share the same parent element.

An example of this could be styling the paragraphs differently after using the first <h2> element of an article. Usually, the content before that first <h2> element is an introduction and you may want to have that in italics.

Example

Let's apply different styling to the <p> elements after using the first <h2> element.

p {
  background-color: red;
}

h2 ~ p {
  background-color: green;
}

Any <p> element after <h2>, as a sibling, will have a green background. All other <p> elements will have a red background. Just like before, it's easier to see what's going on by visualizing it:

Visualization of the general sibling combinator

There's not much else to it. All <p> elements after the <h2> element, provided they share the same <article> element as a parent, will have a green background. If a <p> element is used before, or if it's nested, it will have a red background.

What if we just want to style the first <p> element that follows the <h2> element? Well, that's where our final combinator comes into play.

Adjacent sibling combinator

The adjacent sibling combinator — represented by a + — will only select the direct sibling of the first element. This means that, unlike the general sibling combinator, the sibling needs to immediately follow the first element and share the same parent element.

A use case for this is when you want to change the margin-top of the first <p> element following an <h2> element. On my blog, for example, the first paragraph following a heading has less margin at the top than the others. This allows the <h2> element to sit more closely to the content it's the heading of.

Example

Using the previous example of <p> elements following an <h2> element, let's apply a different style to the <p> that immediately follows an <h2>.

p {
  background-color: red;
}

h2 + p {
  background-color: green;
}

All <p> elements will be red, except for the ones that immediately follow an <h2>. Again, to visualize this:

Visualization of the adjacent sibling combinator

As you can see, the final <p> element returns to the default red background.

The adjacent sibling combinator is probably my favorite of the four combinators outlined in this post. My blog converts my markdown written posts into HTML, which means all content elements are siblings of one another. The adjacent sibling combinator allows for fine control over styling in specific scenarios related to my content.

Combinators & CSS Specificity

Combinators have no impact on the specificity of your styling.

That's it... that's all there is to know about combinators in relation to CSS specificity. Not much of an exciting ending. I feel it's important to know that it doesn't, though. At first glance, using combinators appears to make your CSS more specific. While this is definitely true for you, the writer/reader of the code, this isn't true for calculating CSS specificity.

If you want to know more about CSS specificity and what does, or doesn't, have an impact, read this amazing post about CSS Specificity by Emma Bostian.

Final thoughts

Sometimes code snippets don't help me figure out what's going on. This is when I start visualizing the behavior of my code. Visualizing it helps me figure out how it works and hopefully it's been helpful to you too!

I hope you've enjoyed reading this post. Until next time 😊

© 2020 · Mike Ekkel · All Rights Reserved.

Illustrations from Undraw.co by Katerina Limpitsouni