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:
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;
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
:
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:
2 | font-variant : small-caps ; |
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
:
5 | < section id = "main" ></ section > |
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:
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:
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:
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:
3 | background : left 50% no-repeat ; |
8 | #social li a[href*= "facebook" ]::before { |
9 | background-image : url (images/icon-facebook.png); |
12 | #social li a[href*= "twitter" ]::before { |
13 | background-image : url (images/icon-twitter.png); |
16 | #social li a[href*= "feedburner" ]::before { |
17 | background-image : url (images/icon-feedburner.png); |
Similarly, we can target all links to PDF documents with the suffix substring selector:
1 | a[href$= ".pdf" ]::before { |
2 | background-image : url (images/icon-pdf.png); |
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:
12 | ul li { border-top: 1px solid #DDD; } |
13 | ul li:last-child { border-bottom: 1px solid #DDD; } |
14 | ul 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.
Summary
You may already be using some or all of these combinators and selectors today — perhaps in the correct way, perhaps not — but it doesn’t hurt to have a reminder of when you can be using them instead of applying a class or ID to an element. Because that’s something that even the best of us are guilty of.
No comments
Post a Comment