Update: In the original post I got my terminology all wrong, including in the title. Changing the title would move the post and lose the comments, so I've left the title unchanged. The title of this post should actually always have been:

A multi-class intersection CSS selector

Several times while working on the styling of this blog I've wanted to style elements differently when the complete combination or set intersection of css classes are used on the same element. If the css classes are applied separately the style should not apply. Until today I didn't realise that this is, in fact, both possible and easy to achieve.

Here's the html I want to be able to write:

<div class="urgent">something urgent</div>
<div class="important">something important</div>
<div class="urgent important">urgent & important</div>

And this is how that should look:

something urgent
something important
urgent & important

The urgent & important text should be bold, red and italic in your browser. Notice that neither the "urgent" or the "important" are italic - that only comes when both css classes combine on the same element.

Here are three ways you can achieve the effect. Make sure you get to option-3, even if you have to skip options 1 and 2 (hint: they both suck).

Option 1: specific, separate css class

<style>
  .urgent { color:red; }
  .important { font-weight:bold; }
  .both   { font-style:italic; }
</style>

<div class="urgent">Red Text</div>
<div class="important">Bold Red Text</div>
<div class="urgent important both">Bold Italic Red Text</div>

This is nasty. I want to define my css classes to be as orthogonal and meaningful as possible, and while creating my html markup I do not want to be "mixing concerns" that should be kept separate.

Option 2: nested html tags and ancestry selectors

<style>
  .urgent { color:red; }
  .important { font-weight:bold; }
  .urgent .important { font-style:italic }
</style>

<div class="urgent">Red Text</div>
<div class="important">Bold Red Text</div>
<div class="urgent">
  <div class="important">Bold Italic Red Text</div>
</div>

Double nasty, take it away! Now I've had to change my html markup to nest .important under .urgent, just so I can style things as I want them. It also doesn't work if I nest them the other way around. This is really stupid and annoying: styling should be separate from markup.

OK, so here's the right way to do it, and I just can't believe it took me so long to feel the pain hard enough to go looking for a solution...

Option 3: use the sub-set* selector

<style>
  .urgent { color:red; }
  .important { font-weight:bold; }
  .urgent.important { font-style:italic; }
</style>

<div class="urgent">Red Text</div>
<div class="important">Bold Text</div>
<div class="urgent important">Bold Italic Red Text</div>

The difference in the css declaration between option-2 and option-3 is so small I'll point it out, just in case you missed it:

  • the selector uses both class names without the space separating them - like this .urgent.important.
  • whereas the example in option-2 .urgent .important separates the two classes with a space, and matches on an element with .important descending from an element with .urgent.

CSS can be a thumping great pain, but just occasionally it comes up trumps.

* I can't find an "official" name for this type of selector. The section of the css spec that covers this doesn't name it other than referring to matching sub-sets. "Intersection Selector" seems like a good name to me, although "Dude McManus" in the comments calls it an "Exclusion Selector".

blog comments powered by Disqus