css child selectors

Since the early days, css has taken a serious interest in identifying immediate dom-children. Through a combination of combinators (+ and ~) and pseudo-selectors (:first-child and :last-child) it became possible to target specific elements simply based on the dom's structure. Now css3 is giving us a whole new range of options, but the question remains whether they'll suffice, despite their relative complexity.

First of all, this article is not about styling elements purely based on their position in the dom. Some time ago there was a small hype pushing for less classes and more direct targeting through css. I've always seen it as a direct attack on css flexibility and common sense, even though I appreciate a clean html setup just as much as the next front-ender. The new css3 pseudo-selectors do give you more freedom to expand on this concept, but I won't be the one telling you how.

This article will focus on their use in floated multi-row blocks, more in particular floated list elements that span multiple lines. One of the trademark elements to push people to start using liquid designs and css layouts.

fixing spacing

For all the specifics, examples and tiny loopholes, you can check the w3C specifications yourself. The grid we'll be using for our example is a 3x3 list item grid. The code below gives us a basic layout:

1/ ul {padding:1em;} 2/ ul li {float:left; margin:0em 1em 1em 0em;} 3/ ul li:nth-child(3n) {margin-right:0;} 4/ ul li:nth-last-child(-n+3) {margin-bottom:0;}

The main issue has always been with the spacing of the individual elements related to their parent. Since the block items are floated margins won't be collapsing. If you're aiming for an evenly spaced grid layout, the second line of css above will leave you with too much spacing on the right and at the bottom of the parent element. What we need to do is remove the right margin on each third element and remove the bottom margin on all three bottom elements.

With the third css statement we can target each third list item. The 'n' takes on increasing integer values and matches it against the items in the dom. The fourth css statement does a similar thing for the three last list items. The syntax is a little less transparent but works all the same.

If the horizontal dimension of the grid changes you simply have to adapt the number 3 in these css selectors. Not ideal since we'll need to adapt multiple values for one single effect, but at least it works.

adding corners

Adding some complexity to our little setup, we now want rounded corners on each corner point of the grid. On our 3x3 grid this means adding a corner on the first, 3rd, 7th and last list item. The following code does just that:

1/ ul li:first-child {border-top-left-radius:10px;} 2/ ul li:last-child {border-bottom-right-radius:10px;} 3/ ul li:nth-child(3) {border-top-right-radius:10px;} 4/ ul li:nth-last-child(3) {border-bottom-left-radius:10px;}

Easy enough, though a little rigid and lengthy. And of course, for it to work in Safari, Chrome and Firefox you need to add a whole lot of browser vendor crap.

Check the first example on the test page to see it working.

what's not to like

Even though this setup works remarkably well across the latest selection of browsers, the whole idea just crashes when one list item is removed. You can add or remove full rows without any trouble, and the solution for fixing the margins works in all cases, but the rounded corners will end up on the wrong elements since we're targeting them through :last-child variants.

What we're lacking here is the concept of rows. The "seventh" child in our example really is the "first element of the last row". This cannot be expressed in css, so we still have to resort to workarounds. I have no idea how difficult this could be to implement (though I suppose it shouldn't be that hard, as the browsers needs to know it has to break to another line anyway), but for cases like this it would prove extremely useful.

Another requirement for our solution is that the elements within the grid size relative to their parent. If the list items have a fixed width within an em-layout, zooming the page will break our css once again.

a smarter solution

There is one more solution, but it only works in theory (and in some fucked up Webkit way). Rather than set the rounded corners on the list items, we could place them on the wrapper around it. This way, we'll be sure that they will hit the four corners of our grid.

1/ ul {overflow:hidden; border-radius:10px; } 2/ ul li {float:left;} 3/ ul li:nth-child(3n) {margin-right:0;} 4/ ul li:nth-last-child(-n+3) {margin-bottom:0;}

Sadly, remembering my earlier article on the trouble with rounded corners, there is no way to keep the visual styling of the list items from spilling over the rounded corners defined on the ul element. Only in Webkit, by using the horribly misused overflow:hidden property, can you fix this behavior.

So while in theory this solution is way more robust and flexible, it simply doesn't work like it should. Where are the times we still believed that css3 would fix all our troubles ...

Check the second example on the test page to see it working (in Safari/Chrome).

conclusion

While css3 will give us a broader set of tools to work with, enabling us to do way more with a limited amount of html, it is still far from perfect. Even common patterns prove difficult to accomplish in flexible, best practice worthy ways. The more I play around with the possibilities of css3, the more I keep coming back to the same tired old concept of workaround, fixes and alternative methods. It's a somewhat depressing realization.