the missing css combinator/a plea for help

January 13, 2010 / 12:55

Sometimes a long-lasting, nagging problem can take concrete form in the weirdest of circumstances. Something that's been bothering you for a very long time at a rather subconscious level suddenly leaps forward and calls out for immediate attention. More concretely, this article will be about a css combinator oddly lacking from our list of options, not even mentioned in the latest css3 specs (as far as I know).

the problem

First of all, let's take some time to define the problem. If you're serious about web design and css you've probably learned how to think web components rather than web pages by now. Style a component, style possible variants, use them everywhere and style them according to a changing context. This is a very clean way of working, but effectively not truly supported in css yet. Not even in the most experimental of specs.

<div class="focusBlock"> <header>...</header> <section>...</section> <footer>...</footer> </div>

Consider the html code above. A simple focus block (a somewhat generic name for a block that can be used for whatever content promotion - can be used in main content, contextual column and even navigation column). I've used some html5 elements to make the function of the nested elements a little clearer. The difficulty here is that just about anything can be nested within the section part of our focus block. For all you know (and care), they nest an article with its own header and footer section inside. That's where our styling problems start.

/* focus block ....................... */ .focusBlock>header

The little piece of css above shows the best way to style the focus block as a component. The > combinator makes sure that nested components don't inherit unnecessary styling rules belonging to the focus block. This method can already be used in all browsers (except for IE6). Sadly there's no real IE6 alternative to make it work well, and leaving it as is leads to rather ungraceful degradation. Annoying, but not the core issue right now.

The child combinator (as it is called) is a really helpful tool, but when taking a closer look it doesn't really provide a one on one match for our problem. There are two situations where it will fail horribly. First of all adding wrappers (usually divs) inside the focus block (and around the header, section and footer) will break the css completely. I know these elements are considered a "temporary setback" among the powers that be, but even then extra structural elements (with structural relevance, not added for mere graphical trickery) could mess up the css. Not good.

Even if you think the first reason is not strong enough, there's always a couple of functional tags that could equally break the css. What if you need to add a form or link tag (we can do that now!) around the header/section/footer? It will again break the css you've written before, even though the elements you've added have little relevance to content and style. It's a shame that every time such changes are made we have to go back to the css to fix things.

the solution

What we're missing is a combinator with a functionality that lies between the space combinator and the child combinator. A combinator that expresses that a selector can be anywhere below its parent, but only the first (level) it encounters will receive the css rules. What this combinator should be named or which symbol should be used is not something I'd like to worry about, but his functionality is painfully missing from our css today (and tomorrow). It's the only way to express the style of component in a truly flexible and logical way.

conclusion

I can respect the relative complexity of implementing such a combinator, but I believe it's an important functionality that would finally enable us to translate a design to css the same way we conceive it with our minds. I find it a little strange that such a combinator is still missing, so I assume there were good reasons to neglect such a combinator in the past. That said, I hope this article presents some solid arguments to possibly re-open an older discussion.

blog archive

All my articles are neatly filed inside the archive. Search and filter your way to the article you like:

contact me

If you want to leave me a quick message or you have any questions, drop me a note.

Comment author
8 comments in total
January 15, 2010 16:41

Oh you mean instead of for example:

.a .b {/do/} .a .b .b {/undo/}

Yeah, I hate that pattern. I'd suggest .a .b(0) {/do/} for syntax. Don't know why. Perhaps .a .b[0] can also be added suggest selecting only the first .b inside .a at any level.

January 16, 2010 10:41

Yups, that's exactly what I was referring too. Syntax looks like a good extension to allow for even more control.

I proposed the idea to the w3c css working group. They seemed to like the idea, though as always it's somewhat difficult to get them focused :)

January 19, 2010 11:19

true this makes sense, particlarly when styling nested lists for things like navigation. it would be nice to not have to reset what you have just declared without using loads of class's

id back this for sure!

January 19, 2010 11:23

A proper syntax would be the one used by Sizzle (jQuery) for the :eq() selector : http://api.jquery.com/eq-selector/

January 19, 2010 11:30

When you say "but only the first (level) it encounters will receive the css rules", what first do you mean? Depth or structure?

Because, if you had:

<div class="focusBlock"> <div> <a> <header>...</header> </a> </div> <section>...</section> <footer>...</footer> </div>

And someone used your component to put another <header> directly below the <section>, then this second header would be only two levels below the top-most div and your header would be three.

Couldn't you instead create some better rules, either say that the header you're looking for either direct-child or child of first sub-element?

.focusBlock>header, .focusBlock>*:first-child header {}

Or say that you want direct child headers as well as any header not within the direct child named section?

.focusBlock>header, .focusBlock>*:not(section) header {}

I believe your thinking is a bit narrow, and you could in most cases easily write yourself around it without having to spray classes all over everything (which would of course work, but is annoying).

Guilherme
January 19, 2010 17:43

The commenter above missed the point of this article. The problem is that you can't have a css rules that matches one hierarchy level below a dom node (or two, or three). Rules you defined will take effect recursively.

The nightmare that is using CSS today is that you always miss the recursion of the rules you defined and after 500 lines of code, one rule interferes in another. Making the whole thing almost unpredictable.

How many times for each layout you were creating, after loading your html, you noticed that you didn't get the result you expected because another rule you don't even remember were there merged with the one you just defined? Even css gurus will agreed that it happens quite often.

The ability to define how many levels that rule will be worth is essential from my point of view and can't be replaced by nothing available today.

khs4473
February 15, 2010 19:30

Easy:

.focusBlock header
{
    /* Whatever */
}

.focusBlock header header
{
    /* Cancel Previous Rule */
}
February 16, 2010 00:08

But you can't always "cancel" a previous rule. It's okay if you can reset it to its base value (and even then it's messy), it becomes harder when a value was set earlier on in the css. Like so:

.article header {margin:1em 0em;}  
.focusblock header {margin:2em 0em;}

.focusblock header .article header {margin:1em 0em;}

The horror starts when you want to adapt the margin of the article header. Bad bad coding if you work like this.

* required fields

Leave your data
Leave a comment