Introduction to CSS combinators
I’m no Zeldman, but I’m pretty good at CSS. I used to do some web development on the side and I made it a point to make all of my sites fully standards compliant and semantic. Ever since I started using WordPress.com, my CSS skills have been in decline, although my dragging and dropping skills are better than ever, but when I was using a less sophisticated blogging system, I was something of a CSS ninja (I could kill a man with a <div> tag).
But I never really understood the combinators. What are combinators? They are those characters (>,~,+) that you find in CSS selectors. They never made any sense to me. But the other day I noticed that there was a problem with some WordPress.com templates. They use:
ul {list-style-type: none;}
li:before {content: “»”;}
to get bulleted lists to use the guillemet character instead of the normal bullets. Everyone’s seen this, it’s used in the default template based on Kubrick, as well as White as Milk and a few others. The problem was if I had a bulleted list with a nested numbered list:
<ul>
<li>Thing
<ol>
<li>Numbered thing</li>
</ol>
</li>
</ul>
then the numbered items would get both a number and a bullet, which just looks wrong.
I wondered: how would you fix this? My first inclination was to change the CSS to say:
ul li:before {content: “»”}
ol li:before {content: “”}
But this created a new problem: a bulleted list within a numbered list within a bulleted list would have no bullet. So what to do?
The answer lies in understanding the difference between children and descendants in CSS:
- A descendent of element A is any element contained within A. For example, anything on this blog is a descendent of the <body> element
- A child of element A is an element that is directly contained by A. It has to look like this: <div class=”A”><div class=”B”>stuff</div></div> — B is a child of A
If you want any descendent of A to have a certain style, you can use this syntax:
.A .B {whatever}
If you want only children of A to have that style, you use:
.A>.B {whatever}
If you want any element that is preceded by A to have a style, you can use:
.A~.B
But if you want an element that is immediately preceded by A to be styled, you have to use:
.A+.B
Note: There is a difference between being contained in A and being preceded by A. In the following XHTML:
<div class=”A”>
<div class=”B”>
<div class=”C”>
stuff
</div>
</div>
</div>
<div class=”D”>stuff</div>
<div class=”E”>stuff</div>
- B is a child of A (use > as the combinator)
- C is a descended of A (use white space as the combinator)
- D is immediately preceded by A (use +)
- E is preceded by A (use ~)
I have an example of the markup above in use here.
So the answer to my bulleted/numbered list problem is this:
ul {list-style-type: none;}
ul li:before { content: “»”; }
ol>li:before {content: “”}
which ensures that any <li> that is a child of an <ol> does not have the guillemet character, but any <li> that is a descendent of a <ul> does. See.
Now you know, and knowing is half the battle. For the record, I never would have figured this out without reading the W3C spec.
Note: If you’re using Internet Explorer (version ≤ 6) then none of this is going to make any sense because your browser’s CSS support is deplorable.