![]() |
From CSS in Depth by Keith J. Grant In this article we’ll begin with the “C” in “CSS”, the cascade. I’ll articulate how it works, then show you how to work with it practically. |
Save 37% on CSS in Depth. Just enter code learncss into the discount code box at checkout at manning.com.
What holds developers back from reaching “the next level” of CSS? There are many answers to that question, but one of the big ones, I think, is having only a basic understanding of the fundamentals of the language.
Most web developers know about cascade and the box model. They know about pixel units and may have heard that they “should use ems instead.” But the truth is, there’s a lot to these topics, and a cursory understanding of them won’t get you far. If you’re ever to master CSS, you must first know these fundamentals, and know them deeply.
We’ll be going back to these fundamentals, we’ll quickly review the basics, which you’re likely already familiar with, and then dive deep into the topic. My aim is to strengthen the foundation upon which the rest of your CSS is built.
Basically, it’s all about applying the styles you want, to the elements you want. There are a lot of “gotchas” in here that trip up developers. A better understanding of these topics will give you better control over making CSS do what you want it to do. With any luck, this will also help you better appreciate and even enjoy working with CSS.
A quick review of terminology
Depending on where you learned CSS, you might be familiar with all the names of the various parts of CSS syntax. I won’t belabor the point, but because I’ll be using these terms throughout the book, it’s best to be clear what they mean.
Below is a line of CSS. This is called a declaration. Each CSS declaration is made up of a property (“color”) and a value (“black”).
color: black;
Properties aren’t to be confused with attributes, which are part of the HTML syntax—in the element <a href=”/”>
, for example, the “href” is an attribute of the a tag.
A group of declarations inside curly braces are called a declaration block. A declaration block should be preceded by a selector (“body”, below).
body { color: black; font-family: Helvetica; }
Altogether, the selector and declaration block are called a ruleset. A ruleset is also called a rule—though it is my observation that “rule” is rarely used precisely, and is usually used in the plural to refer to a broader set of styles.
Finally, an at-rule is the name for language constructs beginning with an “at” symbol, such as @import rules or @media queries.
The cascade
CSS is fundamentally about declaring rules –we want certain things to happen under various conditions. If this class is added to that element, apply these styles. If element X is a child of element Y, apply those styles. The browser takes these rules, figures out which ones apply where, and uses them to render the page.
When you look at small examples, this is usually straightforward, but as your stylesheet grows, or the number of pages you apply it to grows, this can become complex surprisingly quickly. There are often several ways to accomplish the same thing in CSS. Depending on which solution you use, you may find wildly different results when the structure of the HTML changes, or when the styles are applied to different pages. A big part of mastering CSS comes down writing rules in such a way that they’re predictable.
The first step toward this is to understand exactly how the browser makes sense of your rules. Each rule may be straightforward on its own, but what happens when two rules provide conflicting information about how to style an element? You may find that one of your rules doesn’t do what you expect, because another rule conflicts with it. Predicting how they will behave requires an understanding of the cascade.
When two or more rules target the same element on the page, they may provide conflicting declarations. For a simple example of this, see listing 1. This shows three rulesets, each of which specifies a different text color. Create an empty stylesheet named styles.css and add these to it.
Listing 1 Conflicting declarations
h1 { ❶ color: blue; } #page-title { ❷ color: green; } .title { ❸ color: red; }
❶ Tag (or type) selector
❷ ID selector
❸ Class selector
These could appear one after the other, or they could be scattered throughout your stylesheet. Either way, given the right HTML on your page, they could all target the same element. This would look like figure 1; the text color in this case is green.
Figure 1 Green is applied; selected instead of the declarations for blue and red
This rendering is from the HTML shown in listing 1. Create a new HTML document and insert this code into it, which will link to your stylesheet. The three selectors will all target the same <h1>
element.
Listing 2 Page header
<!doctype html> <head> <link href="styles.css" rel="stylesheet" type="text/css" /> </head> <body> <header class="page-header"> <h1 id="page-title" class="title">Wombat Coffee Roasters</h1> </header> </body>
All three rulesets are attempting to set a different color to this heading, but which one will win? To determine this, the browser follows a set of rules, and the result is predictable. In this case, the rules dictate that the title will be green.
The cascade is the name for this set of rules. It determines how these conflicts are resolved, and it’s a fundamental part of how the language works. Most experienced developers have a general sense of the cascade, but there are parts that are sometimes misunderstood. Some parts of the cascade have even been slightly re-defined as the CSS specification has evolved.
Let’s unpack the cascade. In order to resolve conflicts between declarations, the cascade considers four things: stylesheet origin; scope; selector specificity; and source order. Stylesheet origin refers to where the styles come from; your styles are applied in conjunction with the browser’s default styles. Scope sorts inline styles out from those applied with a selector. Specificity determines which selectors take precedence over others. Source order refers to the order in which styles are declared in the stylesheet. These are considered in the given order. Figure 2 shows how these are applied at a high level.
Figure 2 A high-level flow chart of the cascade
These rules allow browsers to behave predictably to resolve any ambiguity in the CSS. Let’s go through them one at a time.
Stylesheet origin
The stylesheets you add to your webpage aren’t the only ones the browser applies. Different types, or origins, of stylesheet exist. Yours are called author styles, but there are also user agent styles, which are the browser’s default styles. User agent styles have low priority, though, and yours override them – except for styles you don’t specify.
Note Some browsers allow users to define a user stylesheet. This is considered a third origin, with a priority between user agent and author styles. These are rarely used and beyond your control, and I’ve left them out for simplicity.
User agent styles are the browser’s own set of default styles. These vary slightly from browser to browser, but they generally do the same common things: headings (<h1>
through <h6>
) and <p>
are given a top and bottom margin; lists (<ol>
and <ul>
) are given a left padding; and link colors and default font sizes are set.
Then the browser applies your styles (the author styles). This allows properties you specify to override those set by the user agent stylesheet. If you link to several stylesheets on the page, they’ll have the same origin: the author.
Thankfully, the user agent styles set things we typically want, and they don’t do anything entirely unexpected. When you don’t like what they’ve done to a certain property, you have to set your own value in your stylesheet.
Importance
There is an exception to the style origin rules: declarations that are marked as important. A declaration can be marked important
by adding !important to the end, before the semi-colon:
Listing 3 A declaration with the !important annotation
.title { color: red !important; }
Any declarations that are marked “important” are treated as a higher-priority origin. The overall order of preference, in decreasing order, is this:
- Author Important
- Author
- User Agent
The cascade resolves conflicts for every property of every element on the page independently. If you set a bold font on a paragraph, for instance, the top and bottom margin from the user agent stylesheet will still apply (unless you explicitly override it).
The concept of style origin will come into play more with transitions and animations, as they add more to the behavior of this list.
The !important annotation is an interesting quirk of CSS, which we’ll come back to again shortly.
Scope
Newer versions of the specification have introduced scope to the cascade. The concept here is that a set of styles may be scoped only to an element and its children. If conflicting declarations can’t be resolved based on their origin, then the scope is considered. In truth, scoped styles have been stuck in limbo for several years, and have yet to get off the ground.
There is only one broadly-supported situation: inline styles. If you apply a declaration directly to an element via the HTML style attribute, these are applied only to that element (listing 4). These are “scoped” declarations that’ll override those applied from a higher-level scope: namely, your stylesheet or <style>
tags.
Listing 4 Inline styles will override those applied elsewhere
<p style="color: red;">
To override these from your stylesheet, you would have to add an !important
to the declaration, shifting it to a higher-priority origin. If the inline styles are marked important, then nothing can override them.
Before scope was introduced to the cascade, inline styles were defined as part of specificity. The net result is the same, but it’s worth knowing about scope in case the ability to add other scoped styles ever takes off.
Specificity
If conflicting declarations can’t be resolved based on their origin or scope, then the browser will try to resolve them by looking at their specificity. Understanding specificity is essential. You can go a long way without an understanding of stylesheet origin, because 99% of the styles on your website will be coming from the same origin. But if you don’t understand specificity, it’ll come back to bite you. Sadly, it’s often a missed concept.
Specificity is determined by the selectors. For instance, a selector with two class names has a higher specificity than a selector with only one. If one declaration sets a font to green, but another with higher specificity sets it to red, the red color will be applied. This is shown in figure 3.
Figure 3 The title becomes red because that selector has a higher specificity
The code for this is shown in listing 5. Change your stylesheet to match.
Listing 5 Selectors of different specificity
.page-header .title { ❶ color: red; } .title { color: green; ❷ text-decoration: underline; }
❶ Higher specificity selector
❷ Green color declaration will be overridden by the red color due to selector specificity
Because the first selector has two class names, it has a higher specificity than the second. The red color from this ruleset overrides the green color from the less-specific one. The underline from the second ruleset applies because the first doesn’t specify any value for that property.
There is more to this than seeing which selector is longer. Different types of selector also have different specificities. An id selector has a higher specificity than a class selector (this is because ids are unique on the page). In fact, a single id has a higher specificity than a selector with any number of classes. Similarly, a class selector has a higher specificity than a tag selector.
This is why the title was green earlier (listings 1 and 2); the id selector (#page-title
) was the most specific, and its green color was applied over the blue of the tag selector (h1
) and the red of the class selector (.title
).
The exact rules of specificity are as follows: If a style has more ids, it wins (i.e. it’s more specific). If that results in a tie, the selector with the most classes wins. If that results in a tie, the selector with the most tag names wins. Change your CSS to match listing 6 for an example.
Listing 6 Selectors with increasing specificities
html body header h1 { ❶ color: blue; } body header.page-header h1 { ❷ color: orange; } .page-header .title { ❸ color: green; } #page-title { ❹ color: red; }
❶ Four tags
❷ Three tags and one class
❸ Two classes
❹ One id
These are written in order of increasing specificity. The most specific selector here is #4, with an id, thus its color declaration of red will be applied to the title. The next most specific is #3, with two class names. This would be applied if the id selector in #4 were absent. #3 has a higher specificity than #2, despite the selector’s length: two classes are more specific than one class. Finally, #1 is the least specific, with four tag names but no ids or classes.
Note Pseudo-class selectors (e.g. :hover
) and attribute selectors (e.g. [type="input"]
) each have the same specificity as a class selector. The universal selector (*) and combinators (>, +, ~) have no effect on specificity.
If you add a declaration to your CSS and it seems to have no effect, it’s often because another, more specific rule is overriding it. Often, developers write selectors using ids, without realizing that this creates a high specificity, which is hard to override later. If you need to override an applied style using an id, it means that you’re going to have to use another id.
It’s a simple concept, but if you don’t understand specificity, you can drive yourself mad trying to figure out why one rule works and another doesn’t.
A notation for specificity
A common way to indicate specificity is in number form, often with commas between each number. For example, “1,2,2” indicates a specificity of one id, two classes, and two tags. Ids, having the highest priority, are listed first, followed by classes, then tags.
The selector #page-header
#page-title
has two ids, no classes, and no tags. We can say this has a specificity of 2,0,0. The selector ul li, with two tags but no ids or classes, has a specificity of 0,0,2.
The selectors from listing 7 are indicated as follows:
html body header h1
: (0,0,4)body header.page-header h1
: (0,3,1).page-header .title
: (0,2,0)#page-title
: (1,0,0)
It’s a simple matter of comparing the numbers to determine which selector is more specific. A specificity of 1,0,0 wins over a specificity of 0,2,2, and even over 0,10,0 (though I don’t recommend ever writing selectors with 10 classes) because the first number—ids—is of a higher priority.
Source Order
The fourth and final step to resolving the cascade is source order. If the origin, the scope, and the specificity are all the same, then the declaration that appears later in the stylesheet—or appears in a stylesheet included later—wins.
When you began studying CSS, you might have learned that your selectors for styling links should go in a certain order. This is because of this part of the cascade. Listing 7 shows styles for links on a page in the “correct” order.
Listing 7 Link styles
a:link { color: blue; text-decoration: none; } a:visited { color: purple; } a:hover { text-decoration: underline; } a:active { color: red; }
The reason this order matters is because of the cascade – given the same specificity, later styles override earlier styles. If two or more of these states are true of one element at the same time, the last one can override the others. If the user hovers over a visited link, the hover styles will take precedence. If the user activates the link (e.g. clicks it) while hovering over it, the active styles take precedence.
A helpful acronym to remember this order is “LoVe/HAte”: link; visited; hover; then active. Note that if you change one of the selectors to have a different specificity than the others, this will break down and you may get unexpected results.
Definition Cascaded value: a value for a property applied to an element due to the cascade.
These four steps—origin, scope, specificity, source order—are followed to resolve every property for every element on the page. A declaration that “wins” the cascade is called a cascaded value. At most, there’s one cascaded value per property per element. A paragraph on the page can have a top margin and a bottom margin, but it can’t have two different top margins or two different bottom margins. If the CSS specifies several different values for one property, the cascade will only choose one to use in rendering the element. This is the cascaded value.
If a property is never specified for an element, it has no cascaded value for that property. A paragraph, for instance, might not have a border or padding specified.
Now that we’ve walked through the function of the cascade, check out the whole book on liveBook here and see this Slideshare presentation.