Combinators in CSS

As we saw above, a combinator is a character sequence that expresses a relationship between the selectors on either side of it. Using a combinator creates a complex selector. Using complex selectors can, in some cases, be the most concise way to define styles.

In the previous section, we listed the four combinators: descendant (via whitespace), child ( > ), adjacent sibling ( + ), and general sibling ( ~ ).

Let’s illustrate each of these combinators. We’ll use them to add styles to the HTML form shown below.

The form pictured above was created using the following chunk of HTML:

<form method=”GET” action=”/processor”>

<h1>Buy Tickets to the Web Developer Gala</h1>

<p>Tickets are $10 each. Dinner packages are an extra $5. All

→ fields are required.</p>

<fieldset>

<legend>Tickets and Add-ons</legend>

<p>

<label for=”quantity”>Number of Tickets</label>

<span class=”help”>Limit 8</span>

<input type=”number” value=”1″ name=”quantity”

id=”quantity” step=”1″ min=”1″ max=”8″>

</p>

<p>

<label for=”quantity”>Dinner Packages</label>

<span class=”help”>Serves 2</span>

<input type=”number” value=”1″ name=”quantity”

id=”quantity” step=”1″ min=”1″ max=”8″>

</p>

</fieldset>

<fieldset>

<legend>Payment</legend>

<P>

<label for”ccn”>Credit card number</label>

<span class=”help”>No spaces or dashes, please.</span>

<input type=”text” id=”ccn” name=”ccn” placeholder

→”372000000000008″ maxlength=”16″ size=”16″>

</p>

<p>

<label for=”expiration”>Expiration date</label>

<span class=,,help”><abbr title=”Two-digit month”>MM

</abbr>/<abbr title=”Four-digit Year”>YYYY</abbr></span>

<input type=”text” id=”expiration” name=”expiration” 

placeholder,,01/2018″ maxlength=”7″ size=”7″>

</p>

</fieldset>

<fieldset>

<legend>Billing Address</legend>

<p>

<label for=”name”>Name</label>

<input type=”text” id=”name” name=”name” placeholder

“ex: John Q. Public” size=”40″>

</p>

<p>

<label for=”street_address”>Street Address</label>

<input type=”text” id=”name” name=”name” placeholder=

“ex: 12345 Main Street, Apt 23″ size=”40”>

</p>

<p>

<label for=”city”>City</label>

<input type=”text” id=”city” name=”city” placeholder=

“ex: Anytown”>

</p>

<p>

<label for=”state”>State</label>

<input type=”text” id=”state” name=”state” placeholder=

“CA” maxlength=”2″ pattern=”[A-W]{2}” size=”2″>

</p>

<p>

<label for=”zip”>ZIP</label>

<input type=”text” id=”zip” name=”zip” placeholder= 

“12345” maxlength=”5″ pattern=”0-9{5}” size=”5″>

</p>

</fieldset>

<button type=”submit”>Buy Tickets!</button>

</form>

1. The Descendant Combinator

You’re probably quite familiar with the descendant combinator. It’s been around since the early days of CSS (though it lacked a proper name until CSS2.1). It’s widely used and widely supported.

The descendant combinator is simply a whitespace character. It separates the parent selector from its descendant, following the pattern a b , where b is an element contained by a .

Let’s add some CSS to our markup from above and see how this works:

form h1 {

color: hsl(231, 48%, 48%);

}

We’ve just changed the color of our form title, the result of which can be seen below.

Let’s add some more CSS, this time to increase the size of our pricing message (“Tickets are $10 each”). We’ll also make it hot pink:

form p {

font-size: 36px;

color: #c2185b;

}

There’s a problem with this selector, however, as you can see in the image below. Our selector is too broad.

We’ve actually increased the size of the text in all of our form’s paragraphs, which isn’t what we want. How can we fix this? Let’s try the child combinator.

2. The Child Combinator

In contrast to the descendant combinator, the child combinator ( > ) selects only the immediate children of an element. It follows the pattern A > B , matching any element B where A is the immediate ancestor.

If elements were people, to use an analogy, the child combinator would match the child of the mother element. But the descendant combinator would also match her grandchildren, and great-grandchildren. Let’s modify our previous selector to use the child combinator:

form > p {

font-size: 36px;

}

Now only the direct children of form are affected, as shown in the image below.

3. The Adjacent Sibling Combinator

With the adjacent sibling combinator ( + ), we can select elements that follow each other and have the same parent. It uses the pattern A + B . Styles are applied to B elements that are immediately preceded by A elements.

Let’s go back to our example. Notice that, in the Billing Address section, our labels and inputs sit next to each other. That means we can use the adjacent sibling combinator to make them sit on separate lines:

label + input {

display: block;

clear: both;

}

You can see the results in the image below.

You can see in the image above that some of our labels remain on the same line as their input fields. In those instances, there’s a <span> element between the <label> and <input> elements, meaning they’re not adjacent siblings. To match sibling elements that aren’t adjacent, we’ll have to use the general sibling combinator (as we’ll see in the next section).

Let’s look at another example that combines the universal selector ( * ) with a type selector:

* + fieldset {

margin: 5em 0;

}

This example adds a 5em margin to the top and bottom of every <fieldset> element, as shown in the image below.

Since we’re using the universal selector, there’s no need to worry about whether the previous element is another <fieLdset> or <p> element.

4. The General Sibling Combinator

With the general sibling combinator ( ~ ) we can select elements that share the same parent without considering whether they’re adjacent. Given the pattern A ~ B , this selector matches all b elements that are preceded by an a element.

Let’s look at the Number of Tickets field again. Its markup looks like this:

<p>

<label for=”quantity”>Number of Tickets</label>

<span class=”help”>Limit 8</span>

<input type=”number” value=”1″ name=”quantity” id=”quantity”

→ step=”1″ min=”1″ max=”8″>

</p>

Our <input> element follows the <Label> element, but there’s a <span> element in between. The adjacent sibling combinator will fail to work here. Let’s change our adjacent sibling combinator to a general sibling combinator:

label ~ input {

display: block;

}

Now all of our <input> elements sit on a separate line from their <Label> elements, as seen in the following image.

Because the general sibling combinator matches any subsequent sibling, you’ll want to use it judiciously. Consider the markup and CSS below:

<!DOCTYPE html>

<html lang=”en-US”>

<head>

<meta charset=”utf-8″>

<title>In This Essay, I Will</title>

<style> hi ~ p {

background: yellow

}

h2 + p {

outline: 5px dotted #009;

}

</style>

</head>

<body>

<h1>In This Essay, I Will</hi>

<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce odio leo, sollicitudin vel mattis eget….</p>

<p>Nulla sit amet neque eleifend diam aliquam rhoncus. Donec id congue est. Aliquam sagittis euismod tristique….</p>

<h2>Show how the general sibling combinator works</h2>

<p>Proin condimentum elit sapien, ut tempor nisl porta quis. …</p>

</body>

</html>

Here we’ve used the general sibling combinator with an <h1> element. As a result, every paragraph element that follows an <h1> element has a yellow background. This includes the paragraph that follows the <h2> heading, as shown below.

If you have control over the document’s markup, I’d recommend using a class selector instead of the general sibling combinator. The general sibling combinator makes it too easy to accidentally style more elements than you intended to.

Source: Brown Tiffany B (2021), CSS , SitePoint; 3rd edition.

Leave a Reply

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