August 29, 2020
• 10 min readEver wonder what existed before CSS and what made styling web pages so cumbersome? Let’s dive into the history and evolution of CSS.
In 1994, Håkon Wium Lie recognized the need for a language that would allow for the visual styling of web documents. The main idea was to separate content from presentation. This led to the creation of CSS, developed by Lie and other authors, and eventually adopted by all browsers. After HTML, CSS became the second standardized language for W3 (the World Wide Web). However, before we jump ahead, let’s see how it all began.
As you can see below, browsers like Netscape and Mosaic displayed the same HTML differently. For example, Mosaic developers added a top margin to unordered list, which was seen as an error by others who wanted more control over their content’s appearance.
An Archived Email Showing Different Interpretations of HTML – source: World Wide Web History Center.
In the mid-90s, as web pages started to emerge, altering default browser settings was tricky. When cascading styles were still being tested, the only way to adjust the appearance was through HTML attributes. Early browsers like ViolaWWW barely allowed for visual customization.
ViolaWWW — source: Wikipedie
Web Design Before CSS
Gradually, website creators started experimenting with what we know today as web design. A classic example is the Space Jam website from 1996.
Space Jam, 1996 — source: spacejam.com
This website clearly shows how text and link color differentiation and layout design were approached during that time.
Back then, colors and layout were defined directly within the HTML using attributes like bgcolor
, text
, link
, vlink
, and alink
.
<body
bgcolor="#000000"
text="#ff0000"
link="#ff4c4c"
vlink="#ff4c4c"
alink="#ff4c4c"
>
Another interesting example from this era is the layout of the website. Before, layouts were crafted using nested tables. This technique was the go-to method for web design for several years. Initially, designs like the circular layout on the Space Jam site were popular, followed by the Holy Grail Layout, which dominated web design trends for a long time.
Table-based design is still recommended for creating newsletters and HTML-generated PDFs because many email clients haven’t updated their systems.
Code newsletters like it’s still 1999.
This example shows how designers created the popular circular layout of the time. Companies used these early attempts at visual customization to stand out from the competition and move away from default settings.
An example of the circular layout — source: spacejam.com
Images were placed within the circular layout using table attributes like align
and valign
, along with HTML tags like <br>
.
<table width=500 border=0>
<tr>
<td colspan=5 align=right valign=top></td>
</tr>
<tr>
<td colspan=2 align=right valign=middle>
<br>
<br>
<br>
<center>
<a href="cmp/pressbox/pressboxframes.html">
<img
src="img/p-pressbox.gif"
height=56
width=131
alt="Press Box Shuttle"
border=0
>
</a>
</center>
</td>
</tr>
</table>
For several years, this was the standard method for web design, although progressive developers who experimented with CSS adopted new techniques sooner.
The Rise of CSS
CSS was a game-changer back then. In 1998, Eric Meyer and other developers started documenting how to implement and use CSS styles. Over time, CSS made its way onto the first websites and became a staple for web design. Here’s an example of how it was used in the past:
<STYLE type=”text/css”>
BODY {
font-family: serif;
background-color: silver;
}
H1 {
font: x-large Verdana, sans-serif;
color: olive;
border-bottom: thin black solid;
}
.sidebar UL LI {
list-style-type: none;
margin-left: 0;
margin-right: 0.5em;
}
.sidebar UL LI A {
color: #ffcccc;
}
.body {
background: white;
}
IMG.icon {
border: outset gray 3px;
padding: 0;
}
</STYLE>
As more declarations, properties, and values were added, CSS became an essential language for web development.
Initially, it was revolutionary. Instead of changing attribute values on every page to alter a headline color, a single tweak in the CSS file would apply changes across the entire site.
Developers quickly adopted CSS, but nobody initially focused on organizing or maintaining styles. Writing CSS without any structure or sustainability plan soon proved inadequate. There were no best practices for maintaining stylesheets or dealing with the often inconsistent designs of large websites, leading to various complications…
Two CSS properties walk into a bar. A barstool in a completely different bar falls over.
Issues with CSS
What complications can arise when writing CSS? For beginners, styling can be particularly challenging. On one hand, CSS is straightforward and easy to understand. On the other hand, maintaining, modifying, and structuring styles can be quite complex. Various problems can crop up along the way.
Frustrated programming — source: giphy
Deep Nesting of Selectors and High Specificity
Let’s explore this issue with an example. Imagine a developer wants to add an unordered list to an existing project, with links in blue and a font size of 20px
. They choose the selector .list
.
They add the HTML deep within the structure where it belongs:
<ul class="list">
<li><a href="first.html">Link One</a></li>
<li><a href="second.html">Link Two</a></li>
<li><a href="third.html">Link Three</a></li>
</ul>
And write CSS like this:
.list li a {
font-size: 20px;
color: blue;
}
Oops! The links are red, the font is 16px, and they’re bold. Checking DevTools reveals that another selector with higher specificity is overriding their styles:
body #content .page ul li a {
font-size: 16px;
color: red;
font-weight: bold;
}
So, what can the developer do? They can either increase the specificity of the new selector, adjust the existing one, or use !important
. However, each solution has its drawbacks and underscores the ongoing struggle with CSS specificity.
Typically, developers avoid modifying existing selectors or using !important
. Instead, they often increase the specificity by adding more to the selector, increasing the specificity by adding the list class to our unordered list selector:
body #content .page ul.seznam li a {
font-size: 20px;
color: blue;
}
This approach, however, creates a new element that can’t be reused elsewhere, requiring more code each time it’s needed. Keeping specificity low from the start is crucial for maintainable CSS. In short, keeping specificity low right from the first line of code is essential.
Calculating Specificity
In CSS, styles are applied based on their importance, with selectors assigned different values depending on their type.
The cascade assigns specificity to each rule; when two selectors target the same element, the one with higher specificity takes precedence.
An Example of Calculating CSS Specificity — source: www.makeschool.com
As developers, we sometimes make the mistake of targeting the same HTML element multiple times. We often increase specificity by adding or modifying selectors. While this works in the short term, it usually spirals out of control, leading to specificity issues.
Let’s look at an example to see how selector specificity increases.
Try selecting a navigation element in DevTools to see how other selectors are ignored.
Viewing CSS Specificity Directly in DevTools by Hovering Over a Selector
Nesting Hell
CSS developers used to miss out on many features offered by other programming languages, such as conditionals and loops. This gap led to the emergence of preprocessors like Sass and LESS around 2005, making CSS coding more efficient. However, these tools also introduced new challenges, one of the most notorious being “Nesting Hell.”
Preprocessors like Sass and LESS, and more recently native CSS as of December 2023, allow for selector nesting. This feature lets you write multi-level rules in a few lines, eliminating the need to repeat multi-level rules. However, developers often overlook what preprocessors compile into the final CSS. Features like @extend can generate a lot of extra code, slowing download times.
Deciphering and editing such code can be nearly impossible in some cases. The following code example might look scary, but it’s a common sight when refactoring CSS:
.Checkbox--toggle {
padding: $chekcbox-toggle-diameter / 10 0;
.Checkbox {
&-input {
&:checked {
& + .Checkbox-label {
@extend .Checkbox-toggle — active;
}
}
&:not(:checked) {
&:focus {
& + .Checkbox-label {
&::before {
background-color: $checkbox-toggle-active-handle-bg;
}
}
}
& + .Checkbox-label {
background-color: rgba($checkbox-toggle-bg, 0.46);
}
&[disabled], &[readonly] {
& + .Checkbox-label {
@extend .Checkbox-toggle — disabled;
}
}
}
&-label {
@extend .Checkbox-toggle;
}
}
Wondering what the compiled selector would look like? I didn’t dare share the final result of this particular selector, but the image below gives you a good idea of how complex these outputs can be.
A Compiled CSS File from One of My Client’s Projects
It’s best to avoid deep nesting of selectors. Aim to nest selectors no more than two levels deep, with rare exceptions for a third level. This keeps your code readable and saves you from endless headaches.
.button {
padding: 12px;
background: blue;
@include breakpoint(tablet) {
padding: 8px;
}
&:hover {
background: cyan;
}
&.is-active {
color: red;
}
&__icon {
max-width: 16px;
}
&__label {
font-size: 0.875rem;
}
}
The code is clear and easy to understand, allowing developers to quickly grasp and extend it. I dare say this is now the standard for writing CSS, unless you’re using modern frameworks and libraries, which, of course, offer other options.
Cascading: Order of Rules and File Structure
CSS stands for Cascading Style Sheets, and the cascade plays a crucial role in styling. It’s important to pay attention to the order in which selectors are written due to the cascade mechanism. The cascade is an algorithm that browsers use to decide which styles to apply, determining the order and resolving conflicts between multiple rules.
The Cascade, Metaphorically Speaking — source: giphy
The cascade is one of the most powerful aspects of CSS, but it can also be incredibly frustrating. It’s why many of us are tempted to use !important in our styles. I think it’s one of the trickiest algorithms to grasp, yet it’s fundamental to writing effective CSS.
When we talk about the cascade, we’re referring to a combination of interconnected rules. It’s crucial to understand their relationships. Here are the four main principles:
- Order and Position of Rules - If there are conflicting values for a property with selectors of the same specificity (and origin with the same priority), the last declaration in the stylesheet is applied.
- Specificity of Rules (selectors) - The algorithm determines which CSS selector to use based on specificity scores. The declaration with higher specificity wins.
- Importance of Rules (selectors) - Some CSS rules carry more weight than others, especially those using
!important
. - Origin - The cascade considers the origin of the CSS. This includes browser default styles (user-agent), styles introduced by browser extensions or the operating system, and the author’s styles on the web.
Look carefully at the following example:
Let’s break down the previous example into key points:
- If two selectors have the same weight (specificity), the browser will apply the one defined later in the code.
- The order of CSS declarations matters.
- The order of classes in HTML does not affect specificity.
!important
overrides all other selectors.
This extreme dependence on HTML structure makes the code particularly fragile. Even if the code is clean, a simple mistake can break everything. That’s why the community started developing best practices to avoid these issues. But more on that later.
Summary
CSS has come a long way and continues to evolve thanks to the ongoing improvements made by industry experts. These enhancements have expanded its functionalities and capabilities, ensuring CSS remains a cornerstone in the development of web documents and applications.