CSS is often belittled because it’s naturally global—which we‘ve been trained to avoid—but doing so betrays a failure to understand CSS’ basic premise and greatest strength.
Anybody who has struggled with Cascading Style Sheets (CSS) has seen the memes mocking the experience, like the famous Family Guy GIF or this popular Twitter joke. When all CSS styles are globally scoped, the joke writes itself—try to do one simple thing with CSS and everything goes to hell. How can anybody work like this?!?
As Keith J. Grant puts it: “Just because you expect CSS to be easy, doesn’t mean the language is broken when you find it is not.”
For decades now, global variables have been recognized as difficult to reason about and a source of bugs. So, we avoid them! Today, we take this for granted—often forgetting that it’s not an absolute truth.
In the case of CSS, use of global scope isn’t a bug, it’s a feature! The reason for this is subtle and can present a learning curve to newcomers, but it’s important to understand that it was a deliberate, intentional choice.
A Philosophical Challenge, Not A Technical One
Let’s think about what CSS truly does. Its mission is unique. On the web, how do we universally design a document or an app? Not just for different browsers, but devices like mobile, laptops, and even TVs or smartwatches. Not just for screens, but maybe a printed page.
As Miriam Suzanne explains in this enlightening video, we’re designing for an “unknown infinite canvas.” Decades ago, CSS’ creators showed remarkable foresight in making the language platform agnostic and contextual.
CSS isn’t opinionated about where our styles come from. They can happily live together in global scope provided we can get the correct visual result for the context in effect at any given time. This mechanism is called the cascade.
Sorting Out The Cascade
The broadest interpretation of the cascade is that it’s a sorting operation—albeit a massive one, covering every style declaration as it applies to each and every element in our HTML. Our global stew of styles will certainly have multiple declarations that target the same given element, and such conflicts need to be resolved. We sort on multiple dimensions to decide which declaration with the highest precedence “wins” and gets applied to the element.
These dimensions, in order of precedence, are: origin/importance, selector specificity, and order of appearance. Other CSS concepts, like inheritance and initial values, are also important to understand.
Understanding the cascade is an ongoing and in depth lesson that’s beyond the scope of this essay—related concepts like specificity deserve one or more essays of their own!
Some suggested reading includes the absolutely delightful overview “The CSS Cascade”, by Amelia Wattenberger, or the detailed “Cascading Style Sheets/Inheritance Rules” on Wikibooks, which elaborates on the concept of the cascade as a sorting table. There‘s also Mozilla‘s video “Inspecting the CSS Cascade using Firefox DevTools”, in which Miriam Suzanne explains the cascade in the context of Firefox’s DevTools.
As we learn about the cascade two lessons should become clear:
- The end result of the cascade is that every element gets completely styled, whether we wrote a dozen CSS declarations, just one, or none at all.
- The outcome of the cascade won’t always follow intuitively from our code as it was originally written.
The consequence is that we can easily complicate and over-engineer our CSS. Liberal use of !important
, overly complex selectors, selectors that apply too broadly, or styles defined out of order and in obscure places would indicate a developer (or team) that doesn’t understand how the cascade works.
A prudent, surgical touch is essential. Our goal is to diligently limit the amount of CSS that might ultimately be ignored by the cascade. The only dead code we introduce should be modest, deliberate, and conditional—styles that hibernate until “activated” by a particular context.
Now, the given context could be anything: a one-off button, the size of the user’s screen, a live interaction from the user, the app’s current state, an OS-level accessibility setting on the user’s device, the user’s chosen device or browser, the time of day, and so on.
When we understand this principle and leverage the cascade, we do less work. We write less code.
Unique Benefits Of Global Scope And The Cascade
Writing less code is admirable enough, but there are other benefits.
First, there are many styles (e.g., typography, color, spacing) that we hope are consistent and predictable. Styling components piecemeal means we’ll inevitably diverge from the original design, bloat our codebase, and create bugs. Design patterns are easily reinforced by proper use of global scope and the cascade, which makes designers and developers happy.
Second, global styles are automatic, making them easy to reuse or extend. Any new feature or page we create can be instantly styled with minimal effort, and then any specific styles can safely expect those global styles to be handled already.
Lastly, CSS obligates us to think holistically. Typical software architecture solves for complexity with encapsulation and modular code. CSS is contextual, so that kind of isolation isn’t always practical—we can’t entirely ignore how anything surrounding our component is styled. This is in fact closer to how designers approach their work. Here’s Keith J. Grant again:
If an engineer designs a bridge, you can’t just look at the blueprint and say, “this is all good except this one beam here; go ahead and take that out”. Removing that beam has ramifications on the structural integrity of the whole thing. Similarly, changing one part of a design can have ramifications on how other items on the screen are perceived.
Why Do We Need To Know This?
Some readers might wonder why any of this matters, given everything that modern CSS offers. We can use convention (BEM), make globals opt-in (CSS Modules), adopt serious tooling (CSS-in-JS), or even heartily embrace global scope (functional CSS). Do we really need to think about the cascade?
Never forget that such tooling may very well be the ultimate leaky abstraction. The user always receives CSS, no matter what fancy tricks we employ to avoid thinking about it ourselves.
So, just as we learn to do algebra by hand before using a calculator in high school, we ought to know how CSS works!
For one thing, every developer at some point will encounter a legacy codebase. Maybe we’re fixing a bug in CSS a stranger wrote years ago, reskinning an app’s design, or migrating to something more modern. This knowledge helps us confidently navigate the existing code and then make it better.
For another, even when our styles are locally scoped, the cascade and issues of scope are still present. Local scope doesn’t magically make our CSS safer. Moreover, we can still employ aspects of the cascade like inheritance or specificity to keep a component’s CSS clean and efficient.
Finally, for web apps, the CSS is our most direct, tangible connection to users—a good user experience will ensure they remember our app fondly. It’s our responsibility to master this technology, accommodate our users’ contexts, and provide the best experience possible.
In Summary
CSS is far more nuanced than a typical programming language. Most developers aren’t comfortable with nuance and ambiguity, so some degree of apprehension isn’t surprising.
CSS resides somewhere in the space between design and programming. It’s contextual and unassuming, and therefore more philosophically demanding of developers who’d hope to use it wisely. The global nature of CSS isn’t something we can ridicule, grudgingly tolerate, or blunder through.
CSS has its quirks, to be sure, but understanding how the cascade works can benefit both developers and users alike! It’s a sophisticated and proven technology uniquely suited to the challenge of building user interfaces for an unpredictable web.
Top comments (0)