Understanding CSS, The cascade and inheritance.
Rodney Wormsbecher
Software architect II, JavaScript/Typescript Developer, International Speaker, Trainer, Inspirator and Cloud Enthusiast
Welcome to my “Consciously styling with style” blog. In this series I will try to demystify how Cascading Style Sheets works under the hood, and we’ll go through samples that will explain the common pitfalls that many developers get caught up with. In contrary to other programming languages CSS is easy to pick up, but it’s very hard to master; this is due 2 issues:
1. Different browsers implement the official CSS specification in different ways.
2. CSS doesn’t prescribe a certain pattern to solve a problem, which leads to many possibilities to solve any given issue. This can both be a blessing and a curse.
So I hear you asking: “Who are you?”. I am a front-end programmer from the Netherlands currently working a large consultancy firm called ilionX. I have been engaged with frontend development since 2003 and I literally dedicate around half of my spare time to achieve a higher level of understanding in the front-end field. I first came across CSS in 2004 and it changed the way of developing for the web. It meant that for the first time we were able to separate content and layout, unlike table layouts which were prevalent at that time.
In this series we’ll look at following topics (Note that this is not an exhaustive list):
1. Understanding CSS, the cascade and inheritance
2. Understanding the box model.
3. Layout technique 1: The document flow
4. Layout technique 2: Floats till it sinks the boat (layout).
5. Layout technique 3: Flexbox to the rescue.
6. Layout technique 4: Grid layout, the future.
7. Responsive CSS, preparing for mobile first.
8. CSS pseudo selectors
9. Organizing CSS for large code bases
10. CSS preprocessors.
11. CSS form mayhem.
I designed this blog series for intermediate level CSS developers having one to three years of professional experience. If you have less experience you might need to search some supporting documentation to understand the concepts. If you have more experience, it will surely help you to reinforce your CSS knowledge!
1: Understanding CSS
This first entry will be about understanding the core of CSS, even though this will mainly be a theoretical text explaining how CSS works under the hood, it is necessary prior diving in the “fun” parts of CSS. In this entry I’ll cover the following topics:
1. The cascade
2. Inheritance
1.1: The cascade
CSS is a simplistic language which’s syntax can be picked up fairly quickly. In essence it’s all about declaring rulesets describing how the markup of the webpage should behave, in terms of responsive design you can add certain conditions, but nonetheless it’s very straight forward. We create a selector which consists of one or multiple declarations each containing properties and property values. To make sure we are on the same page, I have included a graphic describing the anatomy of a CSS ruleset (1).
The CSS parser will run through the rulesets and evaluate them in a linear fashion resulting in a stylesheet that describe how each element on the page should be rendered. Sounds easy, however you’ll soon find out that scaling CSS can be a real challenge. If we have 20 different rulesets that describe our webpage this might be easy. But imagine we have 2000 different rulesets of several different stylesheets, how does CSS determine which style goes first? What will happened if any two or more styles conflict with each other? This will be the focus of this article.
To be able to demonstrate the core concepts of CSS, you’ll need a code editor which allows you to write HTML and CSS. I personally use Microsoft Visual Studio Code, but feel free to use any code editor that has your preference.
We’ll start out with a basic HTML 5 webpage that has a header section and a nav section. The <header> tag contains a <h1> and <h2> tag. And the <nav> tag contains an unordered list of links. We’ll also link an external stylesheet with the name styles.css in the same directory.
index.html
styles.css
As shown above we have three rulesets that each have a unique declaration for its font-family and background-color properties. If we look closely, we can see that the <h1> header receives two rulesets (styles) from two different selectors, namely #page-header and h1. Which background color do you think that the <h1> tag will have? I can tell you it’s green, but why did the CSS parser come to this conclusion? Well it’s the Cascade, let’s look how the CSS parser resolves this in the graphic below.
During the first step the CSS parser will check the origins of the rulesets. In general there are three different Stylesheet origins, however only two are commonly used. The parser looks at the origins in the following order:
1. Author stylesheets: the stylesheets we write for the webapp.
2. User stylesheets: The CSS the user can set to overwrite styles on websites
3. User agent stylesheets: The native styles from the browser.
In general we expect the user not to use custom User Stylesheets, but if they do it’s their own responsibility to make sure websites don’t break, since it’s outside of our control. This feature of browsers is usually only used by people with a disability.
Our stylesheets always have precedence over the User and User Agent’s Stylesheets unless there is an !important behind the property value in a conflicting declaration . Different browsers have slightly different User agent Stylesheets to start with, hence for real-world applications it’s always a wise idea to use a CSS reset (2). This will ensure that the user experience is guaranteed no matter which browser they are using, because we define our styles in the Author stylesheet instead falling back to the User Agent's stylesheet.
If you open our web page you can see that the User Agent’s stylesheet has added some margin around our <ul> resulting in stacking the <li> tags in a vertical manner. We can easily overwrite this behavior in the Author Stylesheet to create the look and feel we want. Change styles.css into the following code:
We’ve overwritten the User Agent’s declarations for padding and margin by specifying a ruleset with the same selector in our Author stylesheet. Sometimes we might use third party packages and notice that even though we have overwritten the ruleset in our Author Stylesheet no change happens. This can happens due to 2 reasons:
1. The third-party package gets loaded after our stylesheet.
2. The third-party package has included an !important rule in its CSS.
The first case is easy to solve, just adjust the order or stylesheet imports. A general rule of thumb is to load our stylesheet after all third-party stylesheets. The second problem is peskier to fix. It means we have to use “The root of all evil” adding an !important for the declaration we need to overwrite. This is dangerous as it changed the order in which the CSS parser checks the origin. If we consider includes, the CSS parser will use the following sequence.
1. Author !important styles
2. User !important styles
3. Author stylesheets
4. User stylesheets
5. User Agent’s stylesheets.
* note: User Agent’s do not have !important statements, if they would we’d need to use !important to overwrite them.
Even though the !important operator is often the quickest fix to a problem of origin or specificity (We’ll cover this next), it is usually a bad fix. Think about what happens if you want to overwrite the setting later. Or even worse when you distribute the CSS code and the consumer cannot override the CSS without using !important. My rule of thumb is to only use !important if there is no other way of overwriting third party package CSS rules! Next we are going to look at CSS specificity, this is the part that often confuses even experienced developers.
1.2 Making sense of specificity
If conflicting styles cannot be resolved by origin, it will continue to look at its specificity. If you want to develop robust CSS that scales, this is the one feature that is of paramount importance. Sadly most courses do not even mention specificity and this is the feature that often makes developer feel that CSS is a buggy language. Let’s look at how it works. The CSS parser divides CSS styles up into two parts:
1. Inline styles
2. Styles applied via a selector (this includes: IDs, classes, tags, pseudo selectors etc..)
Inline styles are styles that are specified directly on the element and will override any declarations that come from a <style> tag, external stylesheet or User Agent’s stylesheet. Let’s have a look at what inline styles look like in code:
Change index.js as follows:
On the <h1> tag we have now also added an inline style containing two declarations. It overwrites the styles defined in our stylesheet and the only way the stylesheet can take precedence now is by using an !important statement behind the property value, which is generally a bad idea to use in the first place. If everything worked out correctly we now have a purple background with white text for out <h1> tag.
The second part of specificity are styles that are applied via selectors, these are all selectors that are specified in a stylesheet (Ids, classes, tags, pseudo-classes, media queries etc.). The more selectors of the same selector a selector has will win. Now I know I just said selector three times in one sentence! God what does it even mean? Let’s take a look:
If we have 2 class selectors “.nav .item .link” and “.item--inline” then the first class selector will win because it contains 3 classes instead of only 1 class.
If we have 2 id selectors “#nav #item ” and “#item--inline” then the first id selector will win because it contains 2 ids instead of only 1 id.
When a selector contains more entries of the same type, that selector will win. You can remember this as “The more the merrier”. Our next question: what will happen if a selector contains classes, id’s and tags all in 1 selector. Which One will win? The CSS parser will handle the selectors in the following order:
1. If a selector has more Ids, it wins (period)
2. If that results in a tie, the selector with the most classes wins.
3. If that results in a tie, the selector with the most tag names wins.
Let’s take a look at some examples
It is important to note that if a selector has one id it will always take precedence over selectors that have no IDs, even if the other selector has 1000 classes on it. So what happens if the specificity is equal? Simple, the declaration last declared in the stylesheet wins.
We can use the notation above and make it workable for both inline styles as well as selector styles. Earlier I pointed out that inline styles always win except when there is an !important operator in the stylesheet. So we can use the following convention:
( 0, 0, 0, 0) == (inline style, Ids, Classes, Tags).
Before we wrap up the section about the cascade, I want to give you the following word of advice to make your life as CSS developer a lot easier (Trust me I learned this the hard way…):
1. Never use !important when it’s not strictly necessary.
2. Refrain from using Id’s for styling. It’s okay to use them for <input /> tags and for JavaScript targeting (getElementById()), but save yourself the headache and do not use them for styling.
1.3 Inheritence
We have looked at the origin and specificity which matter to decide which declarations will win. However there is another concept that is important to understand. If an element has no cascaded value it might still need a value to render something. Before diving in the nitty gritty of how this works let’s look at an example.
Change the code in styles.css into the code below:
This will result in:
At first sight there isn’t much to see as it renders the body font-size as 14 pixels and the <h1> tag as 32 pixels. But wait, how did it know to render the <a> tags as 14 pixels as well? Well this effect is called inheritance. If an element does not have a value, it will look at its parent to determine whether it has a value, if not it refers to the parent’s parent and so on.
This concept makes it possible to set a font-size and font-family on the <body> tag and make it cascade all the way down unless specified differently. Imagine this was not included in CSS, it would mean that for every declaration we would need to set the font-family and font-size (among others) individually. I bet we would all go crazy! Imagine a codebase containing 20,000 lines of CSS and your product owner tells you to change the font-size from 14 to 13 pixels. Oh gosh... we’ll have to add in extra time this weekend. Let’s see inheritance in action:
The diagram above represents the code we have written in our stylesheet. We declare the font-size once in our CSS at the body selector and it cascades down the DOM tree. Once we give a declaration a specific font-size it will override the inherited value and change the font-size only for all elements that match that selector.
1.4 Keywords inherit and initial
CSS has two special keywords to operate the flow of inheritance, namely inherit and initial. Let’s first take a look at inherit. When we specify inherit on a CSS declaration, it will traverse up to the parent nodes until it finds the first match for the specified declaration and apply it on our current selector. Let’s look at this in action. We will first remove the font-size declaration from the h1 selector. When looking at the result we can see that we see the User Agent’s stylesheet as fallback. In Chrome this equals 28px;
Now let’s change the font-size in the h1 selector to inherit. In this case it should traverse to its parent rulesets and find that we specified a font-size declaration on the body selector. It will take the property value (in our case 14px) and applies this to our h1 selector.
This is very useful when creating reusable component styles. The font-size or border-radius can be controlled by the parent node and thus gives a little bit more flexibility. Now I hear you thinking, if the body element in the above example didn’t have a font-size declaration, which font-size the h1 selector get? Well we already know the answer but let’s go through it.
1. The h1 selector has a font-size set to inherit.
2. The Author’s stylesheet has no ruleset defined with the font-size declaration in the parent nodes of the h1 selector
3. The User Agent’s stylesheet will thus be used as a fallback. In Chrome this will default to 16px.
Initial
Now let’s have a look at the other keyword: initial. This keyword is used to reset a value of a property back to its default. This is often used to reset opacity or border property values. Even though initial is a handy feature I do not use it that often because of two reasons.
1. It is not supported in any version of Internet Explorer (3).
2. It has some weird bugs, e.g. if we set the display property value of a <div> element to initial, we would expect to get block as a property value, but instead it evaluates to inline.
Well that’s all for the first entry. I hope this article has helped you to get a better understanding about the internals of the CSS parser. In the next section we’ll cover the CSS box model. Stay tuned!
References:
1. https://developer.mozilla.org/en-US/docs/Learn/Getting_started_with_the_web/CSS_basics
2. https://meyerweb.com/eric/tools/css/reset/
3. https://caniuse.com/#feat=css-initial-value
4. Banner: https://www.pexels.com/nl-nl/foto/beeld-close-up-code-coderen-160107/