With the advent of React Server Components – and the subsequent death of most CSS-in-JS libraries – I've been taking another look at the options for using CSS with React, and I've been pleasantly surprised (and a little shocked) at where we've landed. To be clear, I fully expect the React community to come up with some ✨ new and shinier ✨ techniques, and there are already some server-optimised CSS-in-JS-like options such as Pandas. Plus, the current tech influencer circuit is still deep in Tailwind worship, which works just fine (to its credit) in any context. But I've never found Tailwind to actually be useful at scale – great for proof of concept work and rapid ideation; bad for long-term code quality and flexibility – and I've grown accustomed to some of the DX bonuses of CSS-in-JS libraries like Styled Components, so I wanted to see if the tools we have today could be bent into the shape I prefer.
If you look online at Discord conversations, Reddit threads, and Hacker News, the overwhelming consensus seems to be: "just use CSS Modules". I found hundreds of comments all parroting the same talking points, which can be roughly summarised as "it takes some getting used to, but once you've cleared that hurdle, CSS Modules are quite good, actually". I'm not sure I fully agree, though.
CSS Modules are great for lots of things. I was a fan of them before I switched to Styled Components, and I remember during that period often thinking that CSS Modules worked a little better. However, they do lack quite a lot of the mod-cons of subsequent tool systems. I can just about get used to separating out styles and presentation layers again, and it is great that they come pre-packaged with just about every major React build environment (Next, Gatsby, Storybook, etc.), but within minutes of starting to use them again, I was missing certain functionality: nesting; JavaScript-driven props; function injection.
Now some of this I have taken a hard look at and realised there are better, more modern options available. For starters, losing the ease of passing props between React components and style layers has forced me to properly deep-dive into using modular CSS, using data attributes and CSS custom properties to create style APIs that are fully native to CSS and HTML, and I'm (probably unsurprisingly) completely sold. I love it. I've already been using ARIA attributes and HTML state attributes preferentially for my CSS selectors for years, so switching over to a fully-native API and prop system just feels right.
But in some other places, native solutions are just not quite there yet. They will be – and surprisingly soon – but right now you still need build tools to lend a helping hand. Of course, I'm talking about nesting. Now I could write an entire diatribe about how nesting is a double-edged sword, but overall I find it works really well and as long as you don't get too carried away, it makes CSS much easier to read. In particular, when working within a distributed team, the increased readability of nested code (again, if done in moderation) is hard to beat.
The current answer to this concern – and one that bundles in various other useful features – actually surprised me quite a bit: combine CSS Modules with Sass. I managed to completely skip the world's favourite CSS pre-processor, having dipped out of the front end world just as it was taking off, so I've never really played around with it. Of course, I'm aware of both the impact it had and the features it brings to the table, but I was still shocked to see that it's far-and-away the best solution to nesting outside of the JavaScript-only world.
Thankfully, getting CSS Modules and Sass to work alongside one another was almost trivial in terms of set up and, if combined with a dash of Post CSS (or some other post-processor, for performance streamlining and ensuring that browser-specific syntax is handled automatically), you lose almost nothing when compared with techniques like Styled Components. Actually, to the contrary, I think you gain an awful lot. Sure, styles are no longer co-located with the rest of the component code, relegated instead to a sidecar .scss
file, but that's never really bothered me too much (indeed, I know plenty of people who prefer this kind of setup), and the amount of benefits it brings are well worth that minor tradeoff.
The best part about it is that you can use nesting in Sass exactly like the native CSS spec will work, so if you avoid mixins and some of the more complex functionality, your Sass code can be converted to vanilla CSS in the future simply by changing the file extension! I'm someone who generally dislikes adding dependencies, no matter the benefits, but knowing that we're staying on a standards track that will let us just pull Sass out when the time is right – no refactoring needed – well, that pretty much erases any concerns over the long-term maintenance cost of the decision.
Overall, then, I've actually really enjoyed working with this "old" tech stack, cobbled together from the now frighteningly mature and competent tools which the industry has largely (in my experience, at least) moved away from. Based on my experience, I think they're well worth revisiting in any organisation. To be honest, I'm not sure I even want RSC-specific solutions to crop up; Sass and CSS Modules have everything I want already, and they're going to be able to maintain that ease-of-use with much more stability than any newcomers to the scene. It's not often that looking back truly helps you move forward, but I think that's exactly what's going on here, and that's pretty cool!