How CSS Works Under the Hood

Updated Mar 29, 2021#css#internals

The CSS language syntax is simple to learn in matter of hours, newbies can copy tons of CSS snippets and create a delightful website, even designers can write CSS. Things go well until you start to use many !important or inline styles to achieve expected styles.

People tend to learn general programming language like JavaScript more deeply than HTML/CSS which often considered as already perfectly working ignorable stuff.

Besides CSS architecture, solid understanding of specificity and inheritance will help you write less code in a predictable style. The real power of CSS lies in the fact that it allows you specify styles from multiple sources and automatically calculates final styles.

The aim of this post is to develop your understanding of some of the most fundamental concepts of CSS ā€”cascading, specificity, inheritanceā€” which control how CSS is applied to HTML and how conflicts are resolved.

The actual process of calculating final CSS property for a given HTML element is the result of very complicated multiple steps:

1. Collecting

At this step, all style declarations for a given element are collected from multiple sources like user agents, authors or users.

These declarations must go through all kinds of filtering and validating to see if they are from stylesheets that currently applies to this document and syntactically valid.

2. Cascading

This is the core concept of CSS lies in its name Cascading Style Sheets. Solid understanding this step is a must because it is the only step which hugely influenced by developers as author origin.

This step takes an unordered list of declarations from previous step collecting, sorts them according to following criteria, in descending order of priority:

Origin and Importance: based on combination of declaration origins (user agent, user, author, transition, animation) and !important annotation. Using !important as pros and cons depending on circumstances, use it sparingly.

- Transition
- User agent + `!important`
- User + `!important`
- Author + `!important`
- Animation
- Author
- User
- User agent

Specificity: based on selectorā€™s specificity (The declaration with highest specificity wins) calculated for a given element as follows:

- Inline styles (1000)
- ID selectors (100)
- Class selectors, attributes selectors, and pseudo-classes (10)
- Type selectors and pseudo-elements (1)
- Universal selectors (0)

Order of Appearance: the last declaration in document wins in case of equal specificity.

- Imported style sheets are substituted in place
- Linked style sheets are concatenated in linking order
- Inline styles are place after all style sheets

3. Defaulting

This step comes in to place when there are no declarations try to set value for a CSS property of an element. There are several ways of defaulting:

Initial value: this initial value of a CSS property is defined in its definition table, the usage of initial value depends on whether it is inherited or not.

Inheritance: inherited property get value from parent element, root inherited property get initial value, non-inherited property gets initial value all the time.

Explicit defaulting: instead of setting custom value for a property, you can explicitly use initial keyword for resetting a property, inherit for explicit inheritance and unset for erasing all declarations.

4. Resolving

Weā€™re using many relative units (auto, em, rem, vh), relative URLs, percentages, or some human readable keywords (small, normal, bold) to gain maximum flexibility in responsive design. This step will try to absolutize all values as follows:

  • Relative units are made absolute by multiplying with appropriate preference size
  • Some keywords are replaced according to their definitions
  • Percentages are multiplied by a reference value
  • Relative URLs are resolved to become absolute

This step will try to resolve property value as far as possible without laying the document, resolving network requests, or retrieving values from somewhere excepts its parent.

5. Formatting

This step will format the whole document and finish remaining works from previous step why trying to calculate the absolute theoretical value used in the layout of the document.

This step especially focuses on cases like relative coordination between elements, auto layout, or flex layout. It requires a lot of calculations and yield almost perfectly usable absolute values for browsers to use.

6. Transforming

This final step will make some adjustments based on a browsing environment like browser engine, media types, device pixel density, or operating systems before actually drawing.

Some common adjustments are rounding float values to integer values or font size based on available fonts.

This post focuses on an abstract way how to parse CSS styles from multiple sources in a predictable ways. Understanding this flow will help us write more elegant and maintainable code. It also help us debug CSS styling easier with confidence.

The actual process how a browser applying CSS is different. It has many phases related loading CSS, parsing CSS, creating CSSOM tree and attaching style to DOM nodes. Those steps weā€™ve just read implemented in CSS parsing phase.