Why we chose Styled Components over Sass and CSS Modules
Deciding on a styling system can be tricky–here's what went into our thinking
Our problem
Once upon a time, there was one way to write CSS. You had your main stylesheet file, linked it in your head
, and used a carefully crafted cascade to style your markup. But with the rise of file bundlers like Webpack and CSS-in-JS, there’s been an explosion in different techniques and approaches to styling a modern web application. Every solution has its own set of upsides and downsides, and over time new solutions come along that make you rethink your entire approach. How can you choose the right one?
Trust us, we know. As TakeShape has grown over the years, we’ve tried out and implemented numerous different styling solutions. When we would upgrade to a new solution, we would leave our existing solution in place. After all, if it ain’t broke don’t fix it.
But, as time went on it became harder to maintain our older styles. And as our team has grown, so to have the number of opinions on the best way to apply styling. Finally, a few weeks ago we sat down together (over a video call!) to discuss our grown predicament. We weighed multiple solutions, some of which we’d previously employed and some that were new to us. In the end, here’s why we made the decision to commit ourselves to Styled Components.
Our criteria
First, we established the qualities we were looking for in an ideal solution:
- It should be ubiquitous. This makes it easier to onboard new team members and reduces the risk of it losing support in the future.
- It should afford a great developer experience. An ideal solution should help us out while we’re writing styles. It should have discoverable properties, have styles that are as self-documenting as possible, and promote greater reuse between components.
- It should have robust theme support. With a theme to define our type scale and hierarchy, colors, spacing and grids, and Z-space layering, the solution should eliminate fine-grained decision making when writing styles.
- It should be interoperable. We needed a solution that worked with our existing stack and didn’t require any refactoring to implement.
- It should be performant. There’s plenty of potential for performance pitfalls when you’re styling a large application like TakeShape. We needed a tool that would help us avoid these with support for techniques like code splitting.
Next, we looked at the solutions we were using currently and had used in the past:
- In the beginning, we imported Sass modules into our JS files, then extracted them using Webpack, and compiled them to CSS.
- Later on, we adopted Emotion for styling our React components.
- Since we adopted the Material UI framework as the basis for our UI, we've been using a combination of their
withStyles
higher-order component and theirstyled
API to create styled components.
That’s a lot to consider! After looking at our options, we realized that the decision really came down to either using styled components or CSS modules. After surveying the landscape of styled component libraries, we went with the standard Styled Components library over less ubiquitous choices like Emotion or Material UI’s API.
Our decision: Sass Modules or Styled Components?
That meant it became a showdown between Styled Components and SASS modules. There’s plenty of great arguments to use either one, and here’s how they fared against our criteria:
Ubiquity: Tie
While it appears CSS modules is the most ubiquitous approach, since it's the same CSS that would exist in any other project, using Sass on top of it does increase the amount knowledge required to get started. This tied it with Styled Components in our mind, which also requires extra knowledge to get started. And both of them are popular, with each having over 1 million weekly downloads on NPM.
Developer Experience: Styled Components
Both solutions have syntax highlighting and autocomplete support in most IDEs and code editors, but where Styled Components pulls ahead is in its ability to predefine variables in TypeScript. By defining a TypeScript interface for our variables, we get benefits that Sass variables can't match. Strict typing ensures theme implementors provide complete themes and use expected values, which ensures that a theme is valid before its used. Storing variables stored in a nested theme object also allow for better organization and discoverability through autocomplete of the available properties. While nested maps are possible in SASS, they're inconvenient to use.
Robust Theming: Styled Components
First, themes are easy to implement with Styled Components by using the standard, built-in ThemeProvider
. To add theming to CSS modules, we would need an additional tool like react-css-themr
to provide an additional higher-order component to get the same functionality that's available in Styled Components.
Second, our main UI component library, Material UI, provides a typed theme with typography, colors, spacing, layering, and more that can be used directly with the ThemeProvider
from Styled Components. If we went with Sass modules, we'd need a script to convert the TypeScript theme to Sass variables.
Interoperability: Tie
Both tools are easy to install and use with React and Material UI. Either way, they'd fit in with our existing tools.
Performance: CSS Modules
CSS modules require no runtime compilation, since it's just CSS. Depending on the flavor of Styled Components we used, we'd need to install a Babel plugin to help us close the performance gap.
Our Verdict: Styled Components
In total, we reached a consensus on using Styled Components in our application moving forward and slowly working to port over, with the help of extensive storybook testing, older styling to Styled Components when it makes sense.
Fortunately, it was trivial to upgrade our existing use of Emotion and @material-ui/styled
to use Styled Components, and once that was completed we could make use of the babel-plugin-styled-components
library to extract our styles and split our code into performant slices.
This was the right decision for our team and our code base, but we hope that sharing our thinking might help you decide what to use in your own application. More than anything, we’re satisfied at how we made the decision together as a team, hearing everyone’s arguments and concerns along the way.