Style systems are essential for development and design teams who need to scale, improve efficiency, and create consistent components.
You can easily imagine different teams working on multiple parts of a project for a long time. How do we bring order to the style chaos that ensues — stopping inconsistencies from creeping into the product and technical debt from spiraling out of control?
To do that, we need a consistent style system, and that's what we’ll look at next.
A Brief History
In order to understand style systems, let’s look at a brief history of CSS.
CSS was first proposed in the '90s and was essentially one large style sheet. It was great, but it didn’t take much to turn it into a disorganized mess since the cascading structure didn't lend itself well to consistent patterns and repeating styles.
SASS transformed CSS into a decent programming language in the form of a preprocessing engine that implements nesting, variables, mixins, extends, and logic into style sheets.
Awesome — it was a powerful tool that opened up a lot of different avenues. However, we quickly became unaware of what the preprocessor was doing under the hood as we relied on lazily nesting to conquer the specificity battle. Hence, compiled style sheets could explode in size.
Until BEM came along with its component-based thinking and modular design. BEM initially was a breath of fresh air that made us think more about reusability and components. It basically brought semanticity to a new level, allowing us to ensure a named class was unique — thus reducing the risk of a specificity clash by using a block-element-modifier convention.
This was a great solution, and leaders in the field, such as Brad Frost, further refined this convention with the idea of atomic design and the introduction of design systems to help us manage our increasingly complicated styling patterns.
So Why Change?
CSS sounds like a pretty robust and battle-hardened way of programming styles. And so it is …
So why change from that? Well, there were problems that continued to exist, making it difficult to manage.
- Global namespace — still dealing with the global namespace problem.
- Markup — markup was bloated and hard to police with all those long class names. Class-name selection became a tedious task.
- Dependencies — working with dependencies in CSS is hazy. Most of the time, we just include the entire script directly into the page, whether they’re being used or not.
- Dead-code elimination — often while building apps, we have styling that is redundant or no longer in use. Although they can be small at first, over time, it builds up and we have rogue and unused CSS everywhere — which can cause unnecessarily large file sizes.
- Conditionals — sometimes you need your CSS class to style in a certain way in one section and in a different way in another section. In this case, it’s usually better to have the CSS class variable modifiable in the component.
What’s the Solution?
And that mon amie is why some super smart people decided to play around with CSS modules and CSS in JS.
Both these offer new solutions that can be used to improve our existing paradigms and solve some of the problems we’ve mentioned previously.
- Local scope— CSS-in-JS and CSS modules are scoped locally by default. Class names and dependencies become much easier to manage and police as a result of this. Specificity conflicts are largely eliminated since we’re dealing with styles on a component basis. We don't need to introduce complex conventions that are hard to understand, such as BEM.
- Composition — both approaches offer composition as a solution for styling reuse.
- Conditional styling — changing styles based on the state and other conditions is another good use case for composition. Both these solutions allow us to do this with cleaner markup and less code.
- Dead-code elimination/refactoring — with complex CSS selectors, it’s often hard to know if and where some styles are used. We’re working at the component level, where we can easily determine which styles to apply to the component. We want to be fully confident when removing unused styles, and there are a lot of great add-ons available for your build tools to help this.
- Testing — CSS in JS can make testing styles a lot simpler — particularly, if you utilize styled components that already have Jest integration and can complement your existing testing frameworks.
- Frameworks — CSS in JS can be used with frameworks such as styled components, which allows us to write the CSS we all know and have been doing for years, while still utilizing the advantages of JS.
You’re going to read many different views on all of these wonderful ways of styling. I’ve seen arguments between front-end devs over which approach to use and why. It’s got to be one of the most highly debated topics in the front-end community — with everybody throwing in their opinion.
So here’s mine: They’re all good. SCSS can be manipulated to do much of what CSS-in-JS and CSS modules can do. If you have a preference and familiarity with one over the other, maybe you’re better off going with what you’re familiar and productive with. What you create will be just as wonderful. Like many major architectural decisions, it often depends on your unique situation.
My decision-making process
My team works with React across a suite of products, and we’re striving to create reusable components. We’re building fast, and we’re building at scale at the same time — we don’t have unlimited resources to maintain beautiful BEM-based styled systems.
When scaling our codebase, our styling method is the key to balancing modularity and avoiding common scaling issues that can compromise our development velocity. And styling components on a modular basis make sense for this.
And that’s why, for me, in our particular situation, CSS in JS, whilst utilizing the styled-component library, is the solution I would choose to go with.
If you’d like to read more about scaling your components, this piece is only one of a series where I looked at the tools and processes I’ve used to build components at scale.