Thursday, 31 January 2013

Better Semantics with CSS Combinators & Selectors

I’m going to begin with a provocative claim: I believe CSS is one of the most difficult-​​to-​​master computer languages we have. It doesn... thumbnail 1 summary

I’m going to begin with a provocative claim: I believe CSS is one of the most difficult-​​to-​​master computer languages we have. It doesn’t have a complex syntax and you certainly don’t need a doctorate in IT to understand it. However, it’s one of the only popular languages that isn’t “logical” — and I mean that in the most literal sense.
Unlike other familiar web development languages such as JavaScript, PHP or even SQL, problems aren’t worked out via common logic. Spoken algorithms like “if X then do Y, otherwise do Z” or “select all of Y then do X with them” don’t translate to a language like CSS. Simply put; it’s a styling language. A language for designers, not developers. Some of the most experienced programmers I’ve worked with struggle to comprehend CSS for this very reason.
The Cascade is a metaphorical term for the syntax behind CSS. It calculates elements such as origin, specificity, order and importance by using special glyphs, known as combinators, to target elements (and pseudo-​​elements) and style them accordingly. No single article could do CSS syntax the justice it deserves — that’s what W3 Specs and Dan are for. However, assuming you already know a thing or two about CSS, let’s examine some lesser-​​known combinators and not how, but when to use them.
At design school we were all taught about classes and IDs, using . and # respectively, to directly target elements. That’s enough control to build a functional website — but it’s not flexible enough to handle a complete design shift. It also creates a lot more work than needed by using presentational values within markup. Let’s take a look at an alternative approach to targeting those difficult-​​to-​​get-​​to elements.

The Adjacent Sibling Combinator

We’ll kick things off with a selector that’s nice to use in subtle situations — employing the adjacent sibling combinator. The adjacent sibling combinator is denoted by connecting two elements with a + symbol:
1h1 + p
This will select all p elements that appear directly after an h1 element in the DOM. Typographic theory suggests that we should indent paragraphs in body copy, but only if they succeed another paragraph. A practical use for this selector, then, is to add text-indent values to paragraphs without targeting the first in a tree, like so;
1p + p {
2    text-indent: 1em;
3    }
That beats styling all paragraphs with text-indent and zeroing out the first one with class="first" any day. Three lines, no classes and solid browser support. If you’re nesting your content-​​level img tags inside your p tags (and you should be) then we can simply pull their left margins back with a negative value of -1em:
1p + p img {
2    margin-left: -1em;
3    }
Simple enough, right? What if we wanted to style the first line of all paragraphs that directly follow a heading, without affecting any other paragraphs? Once again, we can refrain from using a presentational class to do this. A simple selector made up of the adjacent sibling combinator and a pseudo-​​element will do the trick:
1h1 + p::first-line {
2    font-variant: small-caps;
3    }
Note: while :first-line is a CSS 2.1 approved pseudo-​​element, the :: notation has been introduced at CSS level 3 in order to establish a discrimination between pseudo-​​classes and pseudo-​​elements.

The Child Combinator

A common markup protocol is to wrap your top-​​level sections in an element named something along the lines of #page or #wrap:
1<div id="page">
2    <header></header>
3 
4    <article>
5        <section id="main"></section>
6        <aside></aside>
7    </article>
8    <footer></footer>
9 
10</div>
Regardless of whether you’re using HTML 5 or XHTML 1.1 syntax, this basic format should look familiar to you. If you’re running a fixed-​​width of 960px and aligning your document to the centre with each element horizontally filling the wrapper, your CSS likely resembles:
1#page {
2    width: 960px;
3    margin: 0 auto;
4    }
5 
6header,
7article,
8footer { width: 100%; }
Or perhaps you’re being a bit more specific and prefixing with the #page parent to avoid hitting them when/​if outside of this selection:
1#page header,
2#page article,
3#page footer { width: 100%; }
There’s a better way. We’ve all seen the universal element selector; *, likely through a browser reset or similar. When we combine this with the child selector, we can target all elements that are direct descendants of #page without hitting their grandchildren or beyond:
1#page > * { width: 100%; }
This will future-​​proof our document if we ever want to add or withdraw elements from the top-​​level structure. Referring back to our original markup scheme, this will hit the header, article and footer elements without touching #main and aside within article.

String and Substring Attribute Selectors

Attribute selectors are one of the most powerful we have. They too have been around to some degree since CSS 2.1 and are commonly found in the form of input[type="text"] or a[href="#top"]. However, CSS3 introduces a deeper level of control in the form of strings and substrings.
Note: up until this point, everything we’ve discussed has been CSS 2.1 standard, but we’re now stepping into CSS3 territory. We’ll leave it at the presentational layer, so it’s OK to use these right now.
We have four primary attribute string selectors available to us, where ‘v’ = value and ‘a’ = attribute:
  • v is one of a list of whitespace-​​separated values: element[a~="v"]
  • a begins exactly with v: element[a^="v"]
  • a ends exactly with v: element[a$="v"]
  • a contains value: element[a*="v"]
The potential for attribute string selectors is almost endless, but a perfect example is iconography. Perhaps you have an unordered list of links to your social media profiles:
1<ul id="social">
2    <li><a href="http://facebook.com/designfestival">Like on Facebook</a></li>
3    <li><a href="http://twitter.com/designfestival">Follow on Twitter</a></li>
4 
5    <li><a href="http://feeds.feedburner.com/designfestival">RSS</a></li>
6</ul>
Styling these is as simple as running a substring query through their href attribute to find a keyword. We can then progressively enhance these links, like so:
1#social li a::before {
2    content: '';
3    background: left 50% no-repeat;
4    width: 16px;
5    height: 16px;
6    }
7 
8#social li a[href*="facebook"]::before {
9    background-image: url(images/icon-facebook.png);
10    }
11 
12#social li a[href*="twitter"]::before {
13    background-image: url(images/icon-twitter.png);
14    }
15 
16#social li a[href*="feedburner"]::before {
17    background-image: url(images/icon-feedburner.png);
18    }
Similarly, we can target all links to PDF documents with the suffix substring selector:
1a[href$=".pdf"]::before {
2    background-image: url(images/icon-pdf.png);
3    }
Browsers that don’t support CSS3 attribute substrings won’t display these icons, but that’s OK — they’re not essential to functionality, they’re just a “nice-​​to-​​have”.

Structural Pseudo-​​Classes

Lastly, I want to outline the benefits of structural pseudo-​​classes, not to be confused with pseudo-​​elements or link and state pseudo-​​classes. We can use these to target elements based on their position within the DOM, rather than their contents. A fine example of when to use a structural pseudo-​​class can be to target the first (or last) element in a tree of elements, or to alternate between odd and even elements:
1<ul>
2 
3    <li>List Item 1</li>
4    <li>List Item 2</li>
5    <li>List Item 3</li>
6    <li>List Item 4</li>
7 
8    <li>List Item 5</li>
9    <li>List Item 6</li>
10</ul>
11 
12ul li { border-top: 1px solid #DDD; }
13ul li:last-child { border-bottom: 1px solid #DDD; }
14ul li:nth-child(even) { background: #EEE; }
Note: :first-child is the only pseudo-​​element available in the CSS 2.1 spec. All other pseudo-​​elements, including :last-child, are CSS3 standards.
The key to structural pseudo-​​elements, however, is when not to use them. They should be strictly reserved for when selectors relate to the position of an element and not its contents. If an element must be styled in a certain way regardless of its position in the DOM, that’s when you should be using a more meaningful, semantic selector, such as a class, ID or string.

No comments

Post a Comment