CSS can be deceptively complex. And to most programmers, rightly so. We programmers tend to find patterns that help us relate the new information to what already exists within us, draw parallels and think of real world analogies. That is one reason learning your second or third programming language is much faster than learning the first if they share some paradigms. But with CSS, many of us have a memorize-first approach. While it works, it is more fun (and easier to debug) if we understands a little bit of the under-the-hood stuff.
In this post, let's try to demystify some of the aspects of CSS that we as engineers should've asked when we got started with CSS. Better late than never, right?
Understanding The Browser
Starting with something basic: When you request a webpage, and it is downloaded to your system (computer, mobile phone etc), it is in the form of HTML code (at the application layer, that is). The browser then parses the html, line by line, downloading any external resources that it finds with separate HTTP requests. The HTML that is parsed is structured as a DOM tree, which can be thought of like a family tree, but with HTML elements. DOM defines the structure of the page; what goes where and what information does each node have.
On the side, the browser (in that, the rendering engine) is also processing the CSS files. CSS files are processed, and then styles for each node (HTML element, that is) is calculated, and applied to that node. The end result is 'painted' and rendered to the client's screen.
So as you can tell, it is pretty straightforward. What's interesting is the process of calculating the styles to be applied to each node, and that's what we'll be talking about for the rest of this article.
CSS Parsing
There are a couple of challenges when deciding what style applies to a particular element.
- The engine has to first parse the CSS and get all the values for each property.
- Then the engine has to decide what set of properties get applied to each element depending upon the specificity of the selectors, inherited and default values (since each element may have multiple rules that seem to style it).
- Then the selected styles are converted into pixel values (we may have used rems, ems, percentages or vh/vw in our CSS code) as that's what browsers understand.
In particular, the CSS engine looks for the following when dealing with a style and deciding if it really applies to a given node.
!important
If a property has !important
in the value, it is immediately selected for the final processing irrespective of the specificity and code order.
Specificity
To put it simply, specificity deals with how 'specific' is the selector (based on concrete rules). For example, if you have some list-items, each with a class selector and CSS background-color: red;
and one of them also has an ID selector with the CSS background-color: green;
, then which background-color do you think gets applied to that particular list-item? It is the style in the ID selector.
Similarly, if the ID selected list-item had an inline style, the inline style would take precedence. Formally the hierarchy is
Inline style > ID selector > Class selector > Element selector
Browsers maintain an internal tuple of the form (0,0,0,0) representing the counts of each selector hierarchy viz. (inline-style, ID, class, element). For the selector h1.heading#top-heading
, the tuple will look like (0,1,1,1), that is, one for ID, one for class and the last for element selector.
Suppose you had another selector h1.heading#top-heading#blue-color
. Now the tuple for this would look like (0,2,1,1) as there are two ID selectors. If the browser had to choose between the former and this, it would choose this one as it has a higher specificity.
Source Order
Now what happens if specificity of two selectors match? Simple, the last selector in the source code (even in case of multiple files) gets selected for application.
Cascade
Cascade (n): A process whereby something, typically information or knowledge, is successively passed on.
The term Cascading in Cascading Style Sheets says something about the priority scheme that's used to determine what style gets applied to an element when multiple rules match. In simpler words, if body has font-family: Arial;
specified and the h1 has font-family: Helvetica;
, Helvetica gets applied to the h1. However, if font-family on h1 wasn't explicitly declared, Arial would've been selected by inheritance. This is how inheritance works in CSS.
Not every property is inherited. And it doesn't make a lot of sense to inherit everything either. For example, setting margin: 0 10px;
on body doesn't automatically apply it to every child of body which doesn't have an explicitly declared margin property. The ones which don't have an explicitly declared margin get a default margin of 0px. This is how default properties work in CSS.
The obvious followup question is, how to tell if something will be inherited or default value will be selected? Honestly, I'm not sure. I usually just ask myself if it makes sense to have this property inherited or defaulted. More often than not, that's enough. In case you'd want to explicitly make a property to inherit or default a value, use inherit
and initial
keyword respectively.
In Closing
That's it for this little primer. I hope you found it useful. For a nice illustrated guide on this, check out this article on Mozilla. Thank you for reading.