When I was a student I shared an apartment with one of my closest friends, who has a degree in city planning. Youâd never know it, but itâs amazing how many thunderous 4 AM speeches on why Brasilia sucks and how Jan Gehl is the second coming of Jesus Christ you can fit into a 3-year period when you combine alcohol with enthusiasm for the field of city planning.
Specifically, there is an outstanding TEDx-talk that we kept coming back to, in which the speaker, the aforementioned architect, slams the aforementioned capital by describing something he calls Brasilia syndrome:
âBrasilia is great. It looks fantastic from the airplane â itâs an eagle [âŠ] and the head of the eagle is the parliament. Also from the helicopter Brasilia is absolutely great. [âŠ] They have their various architectural monuments and they have their enormous parks. Great.
But what is not great in Brasilia is how people are treated, because nobody thought about that there would be people walking and moving about. [âŠ] They never thought about that there would be no money to give everyone a helicopter so they really could enjoy Brasilia.â
He is saying that the city planners of Brasilia, a city that was planned and built from scratch in the 50s, seemingly forgot that they needed to make a city that was actually pleasant to live in, not just a city that looks good from above. The city planning doesnât serve those who actually live and work there. If we look at a few pictures of Brasilia itâs easy to see what he means. Sure, it looks sick on a map, but if you zoom into a random street thereâs a good chance it will feel barren, lifeless and downright uncomfortable because of the huge open spaces, enormous sight lines, and massive roads.

Brasilia in theory versus Brasilia in practice
The reason I bring this up isnât only so you have a new topic to impress your dinner party guests with (or bore, if they donât all happen to be city planners), itâs because we programmers also have a tendency to get Brasilia syndrome. We imagine these grand designs, these beautiful, elegant abstractions that will let us expand our code with a flick of the wrist, but get tangled up in our own spider web of complexity and end up with something that feels awful to work with. We end up with huge streets and empty parks.
Youâre probably using CSS wrong
Iâd wager that I care more about CSS than the average developer, and this blog post is about how I believe we should be thinking about CSS in projects that meet the following three criteria:
- The project uses some sort of framework or library that lets you write reusable UI components, ie. React or Astro
- The project includes multiple developers
- At least one of the developers is not a multi-disciplinarian developer-designer who is good at (and interested in) building a design system
In other words, most of todayâs frontend projects.
Now, if you, upon reading the title of this section, rolled your eyes and went âand I bet you think you can show me the right way to use CSS?â then consider your expectations successfully subverted. On the contrary, the whole point is that using CSS right is a trap that most developers should try to avoid falling into. Itâs the frontend version of Brasilia syndrome, and we should actually be misusing the language even more than we are already doing. But before we get to that, letâs talk a bit more about the cascade.
The (genuinely) fantastic cornerstone of CSS
The idea behind the cascade is fantastic, it truly is. For those of you who primarily know the cascade as the first letter in CSS, a quick recap:
The cascade is the mechanism that decides which styles should be applied to each element. Styles clash in CSS all the time, and the browser needs to know how to prioritize one set of styles over another. Specificity is one part of the cascade, and probably the one we spend the most calories worrying about, but there are other parts too; cascade layers and the !important
flag, for instance.
The idea behind the cascade, and arguably the central idea behind CSS, is a really powerful one: To provide us with the tools to let us specify how our site should be styled, using a predictable and powerful mechanism that lets us conditionally prioritize styles from different sources based on the structure of the elements being styled. The implications of these capabilities should not be underestimated: It means we can have one stylesheet, apply it across our entire site, and provided we havenât messed up somewhere weâre free to add as much markup as we want, wherever we want, without having to also define the style for all of that markup. Or in other words, it enables a separation of concerns for our markup and styling, as this blog post repeatedly points out the importance of. As an aside, that post is actually what inspired me to write this one (sort of in the same way that an unnecessarily smelly lunch in the office inspires my eyes to roll backwards into my head).
This type of total separation is awesome in theory, but very difficult in practice, especially with todayâs frameworks and use cases making our web applications vastly more complex than those seen at the dawn of the World Wide Web.
Unless youâre someone whoâs good at (and interested in) CSS, and who at the same time wants to act as a maintainer of a design system, itâs really hard to create these universal stylesheets. Because you arenât really just writing CSS, youâre creating a design system. These are styles that are applied to all the markup in the project (or at least a significant part of it) and so any reusable classes or styling that uses element selectors has to work for all the places it could conceivably be used. My experience is that this task becomes nigh-impossible when you have multiple developers working on different parts of the application at the same time, while also leveraging reusable components (be it with React or something else).
In other words, I think that this genuinely awesome idea is the root of why CSS has a tendency to get painful as a project grows. The reusability and potential for modularity inherently built into CSS is our Brasilia eagle; something beautiful and tempting which in practice makes us forget to write code thatâs actually pleasant to live with in the years to come.
The Multi-Dimensional Dependency Matrix of Pain and Death
Successfully constructing and maintaining the eagle without frustrating current and future developers is difficult, but if you introduce reusable code components without scoping your CSS, you will almost inevitably construct The Multi-Dimensional Dependency Matrix of Pain and Death (TMDDMPD, really rolls off the tongue).
CSS, when not scoped, is a fully-featured reusability mechanism for your styling, it gives you everything you need to reuse your styles anywhere you might wish to do so. Letâs think of our interface as a set of âblocksâ, or sets of elements that belong together. These could be forms, modals, navigation panels, or what have you, the point is that youâd typically work on one block at a time, whether youâre making one from scratch or changing an existing one. Each block can use many CSS classes, and likewise, each class can be used in many blocks â or in entity-relation-speak, they have an N:N-relationship.

As an application grows large, these sets of dependencies can become quite the pain point, especially when you have to go back and change an existing part of the application. Now, lets introduce React components! On top of letting you create reusable modules of structure and functionality, they practically also serve as a reusability mechanism for your styling.
You probably see where this is going. Each block of functionality can contain several React components, and each React component can be used in several âblocksâ of functionality. In addition, React components and CSS classes naturally have the same relationship as our blocks and CSS classes had earlier. In other words, we have two sets of N:N-relationships stacked back-to-back. Yikes!

This Is Fine
Having complex dependency graphs in a codebase doesnât necessarily need to be untenable, especially if you have tooling that helps you navigate it. In our case though, sadly, we donât really have that â something which is especially noticable when youâre trying to make changes to existing components or styling in this sort of environment. My experience is that trying to figure out exactly which pieces of UI would be affected by changing something in this sort of multi-dimensional dependency hell quickly devolves into the ctrl-f
equivalent of a bar fight where your opponent is every developer who has ever touched the codebase (including past and future versions of yourself). The natural consequence of this is that everyone starts making small one-off-classes or unbelievably specific selectors instead of editing the generic styles, and at that point youâve already lost the advantages of having reusable, generic CSS classes. Youâve successfully managed to combine the tedium of scoped styling with the mental overhead of reusable styles. Does this sound familiar? (If this is you, know that I donât judge you. This has happened to me too, and like a boiling frog I didnât realize what was happening until everything was already very unpleasant.)
⊠And you should use it even more wrongly
âOkay smart-ass, so what do you figure the solution is?â
- You, probably
So far, the only constructive thing Iâve done has been to hint at the solution to this. The worst part (for you) is that even after enduring this almost-2000-word rant you wonât become privy to some grand secret. On the contrary, the idea thatâs gonna help us solve these problems is so popular and unoriginal that there are about a million tools and libraries we can use to implement it: Scoped CSS.
Using the full power of the cascade is tempting, because it creates a beautiful, minimalistic abstraction that minimizes duplication and maximizes reusability, but in my opinion itâs simply too costly to do it properly. By conceding that weâve lost this battle, that global stylesheets are dead, and that from here on out CSS is only to be used when scoped, we will end up with less complex code bases, spend less time fighting with CSS, and ship products faster.
I donât think the exact tool you use to scope your CSS matters very much, the point is that the CSS can only apply to the stuff in its immediate vicinity. The styling belonging to a component should only ever be able to affect elements that are a part of that component â nothing above and nothing below. Personally Iâm a big fan of using Tailwind for this, partially because its tendency of making you repeat yourself is a great way to force you to be conscious about exactly how you leverage components. Tailwind, being the most polarizing topic in all of frontend development, is a blog post for itself though, and Iâve had great experiences using CSS modules to achieve the same thing. Since CSS modules still use classes it means we as developers can still have a little bit of (contained) reusability, as a treat. Another benefit versus something like Tailwind is that you do not need to learn any new syntax since itâs just CSS, but scoped.

Bigger, yes, but much less stress-inducing
If we look at what this does to our dependency graph from earlier, itâs clear that while we have some code duplication, itâs a much less complex environment to make changes in. If itâs very important that the .class-2
should always be the same for React components 1 and 2, then it should probably be a React component instead. If itâs not that important, then it means we had the wrong abstraction (if you havenât read that Sandi Metz blog post, trust me when I say itâs impossible to regret spending 5 minutes reading it).
Going from a project with ânormal CSSâ to one where all CSS is scoped feels like going from JavaScript to TypeScript, seriously. You might not notice a very clear difference as you switch, but youâll sure as hell notice it if you have the misfortune of going back. This one change will remove heaps of mental overhead, make the act of changing existing styling feel safe and easy, and guarantee that you never have to worry about what to name a CSS class again. The only thing it will cost you is that youâll have to write the same CSS a bit more often. In a sense you can think of it as trading away a bit of convenience when writing code in order to gain a lot of convenience when changing and removing code, and thatâs a trade Iâm willing to make any day.
Itâs sort of a sad conclusion, because it really does feel like weâre giving up on something cool, useful and sleek and admitting that weâre not good enough to make it work, but I sure as hell know that I wouldnât wanna go back after having seen the dark side.
Letâs stop writing CSS that satisfies our inner 1950âs city planner, and letâs start writing CSS thatâs human scale instead.
Epilogue
This is obviously a nuanced topic. Iâm not saying you should never reuse any CSS, but I do think thereâs a good argument for saying that using tools that let you scope your CSS should be the default, especially in larger projects with multiple developers. I think that much of the frontend world is slowly realizing the same thing, which is probably why frameworks like Astro require you to explicitly opt-out of scoped styling, instead of the other way around. So, havenât tried Tailwind yet? Give it a shot. Tried it and still want to scoop out your eyes with a rusty spoon when you see a 200-character class attribute? Maybe go with CSS modules instead.