The evolution of scalable CSS is a multi-part chronicle intended to document the progress of tools, practices and techniques that enable us to write maintainable CSS at scale.
- Introduction
- Part 1: CSS scalability issues
- Part 2: Good practices
- Part 3: CSS Processors
- Part 4: Methodologies and Semantics
- Part 5: Styles Encapsulation
- Part 6: Atomic CSS
- Part 7: CSS-in-JS
- Part 8: Type-safe CSS
- Epilogue
I wrote my first Cascading Style Sheet (CSS) in 2000. Most CSS rules that we've used at that time revolved around setting font-family
, font-size
, color
, and background
.
Searching my old archived folders, I've stumbled upon my first commissioned website. The entire .css file for all 5 pages contains only 26 lines of CSS code, including white space. Managing such tiny files was the least of our problems back then.
Besides the multitude of CSS features introduced during these past 2 decades, one thing I came to understand is that there's a significant difference between the CSS code written for small, primarily static websites and the one developed for large, highly dynamic applications. For example, single developers working on a project that will end in a couple of months can write CSS in any way they consider appropriate. Either way, the project's success most likely will not be affected.
On the other hand, when we think about teams with multiple developers working on the same code base, poorly written CSS can hugely influence the project's development over the course of several years. It's not a secret that writing CSS at scale is not trivial. We've read about it in articles and blog posts. We've heard it during conference talks. Not to mention that I've personally experienced it countless times. Most likely, it happened to you as well.
Before 2000, the World Wide Web Consortium (W3C) set the tone for CSS features, releasing standard specifications way before their actual implementations in browsers. But with the rise of Web 2.0, things began to flip. CSS standard features started to lag behind the needs of the community. Therefore, developers had to build their own tools to aid them in authoring CSS at scale.
You can view this series of articles as a chronicle, describing how CSS tools and techniques have evolved over time. The goal is to present an objective view of the current state of writing maintainable CSS at scale, highlighting the available tools and their applicability.
A chronicle is a recording of significant historical events in the order of their occurrence, as seen from the chronicler's perspective.
Target audience
This series will be appealing to you if:
- You are eager to discover potential maintainability problems when working with CSS at scale, even if you haven't encountered them yet.
- You have already experienced the struggles with CSS, and you're looking for solutions to overcome them.
- You've heard certain buzzwords such as specificity, semantics, scoping, encapsulation, Atomic CSS, CSS-in-JS, design tokens, or type-safety, but you don't fully grasp their meaning.
- You want to understand the CSS ecosystem as a whole, even if you master a few particular tools and techniques.
- You're simply curious to learn how CSS tooling has evolved over time or why specific techniques exist today.
However, the audience that would benefit most from the content include:
- UI developers or Front-End developers working either on client-facing interfaces or back-office applications;
- Full-Stack or Back-End developers getting their hands dirty with CSS and user interfaces;
- Technical leads or CTOs that have to make educated decisions in regards to their technological stack;
- Anyone working on projects longer than 3 months or within teams of at least 3 developers writting and maintaining CSS code.
Preface
Before diving into CSS-specific scalability problems, let's generalize what a scalability issue is.
Without a doubt, the code for a software project will continually grow as new features get implemented. Therefore, when we talk about software scalability, we look at the scaling factor of the maintainance effort when we significantly increase the size of the code.
Whenever the gravity of a problem increases proportionally with the size of the code, we have a scalability concern.
For example, we all know that we should avoid globals. But, would it matter if we use "a few" globals, in a small code base, for a personal project? Of course not.
However, using globals as a practice, within a team of several developers working on a long-term project that spans over many years, should be a scalability concern for the team, as it will undoubtedly become a substantial problem sooner or later.
Project sizeWe all have our own definition of "what a large project is" to determine when to address these scalability concerns. Based on my own experience, CSS scalability problems usually start to surface during:
- projects that span longer than 3 months;
- projects having at least 3 developers working on the UI.
Writing new code is only one of our daily activities as developers. Besides implementing new features, we'll also have to investigate issues, fix bugs in existing code, perform refactorings, remove unused code, optimize it, and update it as libraries and native platforms evolve. All these responsibilities comprise the maintenance part of the code.
There's a tight relationship between scalability and maintainability. That's why I'll often use these terms interchangeably throughout this series. Any scalability concern could potentially affect the maintainability of the code, ultimately leading to technical debt.
Software entropyWhenever we begin a fresh project, we start with a clean code. But with each new line of code added to the project, the complexity grows. The initial clean state will slowly begin to decay. Say hello to software entropy.
The more code we write, the faster the entropy will grow. Larger teams produce more code. Therefore, it's obvious that the difficulty of maintaining long-term projects is proportional to the team size.
To avoid the continuous rise of software entropy, we have to fight it constantly: refactor code, clean it up, remove dead code, analyze and re-design it. But we cannot fight this war with our bare hands. We need proper weapons and tactics, like tools and techniques.
This chronicle aims to dissect the weapons and tactics at our disposal in fighting against CSS entropy.
Chronicle overview
Similar to natural selection, the evolution of scalable CSS didn't take a linear path. Instead, developers had to try various approaches to figure out which ones would withstand the test of time. In addition, each project has its own challenges, and each team has its own preferences. This results in a wide range of solutions, even when addressing the same problem.
During this series, we'll analyze all the tools and techniques still applicable today regarding scalable and maintainable CSS code. Here's a short overview of what we'll cover.
In Part 1: CSS scalability issues, we'll explore the most significant problems that we're usually facing when writing large-scale CSS, namely selector duplication, naming collisions, specificity wars, source order precedence, implicit dependencies, zombie code, shared variables, and lack of type-safety.
Part 2: Good practices walks through some of the essential techniques introduced by various pioneers to avoid naming collisions and specificity wars. They provide the first line of defense in the battle against scalability problems.
Part 3: CSS Processors covers various tools that enriched CSS with new syntax, allowing us to write more maintainable source code by significantly reducing selector duplication. It was a small step forward but nevertheless a crucial one.
In Part 4: Methodologies and Semantics, we'll discuss two related topics. First, we'll look at various CSS methodologies that brought together essential practices in a concise and explicit set of rules. Second, we'll discuss the Semantic CSS approach, encouraged by the HTML5 specification, which most CSS methodologies and frameworks embrace.
Part 5: Styles Encapsulation focuses on the tools used for CSS scoping, addressing all the problems regarding naming collisions, specificity wars, source order precedence, and reducing the amount of zombie code. Styles encapsulation became an industry standard, adopted by all component-based JavaScript frameworks.
Part 6: Atomic CSS debates an alternative approach that contradicts and breaks all the principles of Semantic CSS. This shift gave birth to a whole new set of frameworks based solely on the paradigm of single-purpose utility classes.
Part 7: CSS-in-JS covers a novel approach to CSS, by moving style definitions to JavaScript files, resulting in countless benefits, such as explicit dependencies and shared variables. In addition, CSS-in-JS provides all the features of styles encapsulation, CSS preprocessors, Atomic CSS, and so much more.
In Part 8: Type-safe CSS, we'll explore the missing puzzle piece when aiming for a complete type-safe codebase. Combining TypeScript and CSS-in-JS provides type checking for CSS styles, enabling safe refactorings and typed interfaces when authoring UI components.
The Epilogue includes a review discussing the current state of scalable CSS. It debates the different paradigms to address CSS today, highlighting their strong points and main applicabilities.
As you can see, we have a lot to talk about. So please grab a cup of your favorite beverage and let our journey begin with Part 1: CSS scalability issues, as we cannot discuss the solutions unless we understand the problems they are trying to solve.