The subject of scaling CSS came up a lot in a recent ShopTalk Show with Ben Frain. Ben has put a lot of thought into the subject, even writing a complete book on it, Enduring CSS, which is centered around a whole ECSS methodology.
He talked about how there are essentially two solutions for styling at scale:
- Total isolation
- Total abstraction
Total isolation is some version of writing styles scoped to some boundary that you’ve set up (like a component) in which those styles don’t leak in or out.
Total abstraction is some version of writing styles that are global, yet so generic and re-usable, that they have no unintended side effects.
Total isolation might come from <style scoped>
in a .vue
file, CSS modules in which CSS class selectors and HTML class attributes are dynamically generated gibberish, or a CSS-in-JS project, like glamerous. Even strictly-followed naming conventions like BEM can be a form of total isolation.
Total abstraction might come from a project, like Tachyons, that gives you a fixed set of class names to use for styling (Tailwind is like a configurable version of that), or a programmatic tool (like Atomizer) that turns specially named HTML class attributes into a stylesheet with exactly what it needs.
It’s the middle ground that has problems. It’s using a naming methodology, but not holding strictly to it. It’s using some styles in components, but also having a global stylesheet that does random other things. Or, it’s having lots of developers contributing to a styling system that has no strict rules and mixes global and scoped styles. Any stylesheet that grows and grows and grows. Fighting it by removing some unused styles isn’t a real solution (and here’s why).
Note that the web is a big place and not all projects need a scaling solution. A huge codebase with hundreds of developers that needs to be maintained for decades absolutely does. My personal site does not. I’ve had my fair share of styling problems, but I’ve never been so crippled by them that I’ve needed to implement something as strict as Atomic CSS (et al.) to get work done. Nor at at any job I’ve had so far. I see the benefits though.
Imagine the scale of Twitter.com over a decade! Nicolas has a great thread where he compares Twitter’s PWA against Twitter’s legacy desktop website.
The legacy site’s CSS is what happens when hundreds of people directly write CSS over many years. Specificity wars, redundancy, a house of cards that can’t be fixed. The result is extremely inefficient and error-prone styling that punishes users and developers alike.
I love the article and the topic in general (it’s been the one most on my mind in CSS lately). But
I’ve really enjoyed this middle ground a lot. I think it’s the best!
Ive always wondered why there wasnt a specific css code word that stopped the cascading…so we dont have to write over the cascading styles that are negatively affecting the element.
I’ve started reading through the Enduring CSS book they mention. While a lot of what the author says goes against the grain for me (using a lot of redundant code), I see his reasoning, which seems to make this approach appropriate for large projects which will be actively maintained. It’s sort of a massive hybrid/cherry-picking of lots of different approaches. In some ways it’s DRY, in some ways it’s not.
I took much time to understand and finally i got.
Thanks for sharing this posts keep up the good work.
I think we need to define styling in a mutli-layered approach.
global
brand
app
feature
module (compound component)
component
The question then becomes how does each layer assert itself on the layer under it (the cascade).
It would be nice to have an easy mechanism for defining the scope of variables across these layers and be able to override them within a specific context.
Another issue is how you keep your components, modules and features decoupled from where they are implemented. If your component has styling rules for how it should display when presented in a sidebar then it’s not necessarily as independent as it could be. I think having that styling customization within the parent module/feature makes more sense since the parent is naturally inclusive/aware of its children. The problem with that is that when the child component’s style contract changes you have to track down those customization in parents.
The broad challenge is that as we make css try to fit into a more object oriented models we create abstractions that are difficult to debug and maintain or just plain unintuitive. It might seem elegant to write a bunch of mixins that can generate repetative classes or define namespaces automatically or any number of other things, but if you have to hand that code off to developers you can run into problems.
I look at some solutions and think “that’s great, I could definitely use that” and then I think “how are the 50 offshore developers, the interns, and the new associate developers, and the C developer making a career switch into UI, all with limited CSS knowledge going to understand this?” At some point you have to balance elegance and code reuse with readability and intuitiveness. Not that we shouldn’t be trying to bring everyone up in their level of knowledge, but sometimes we can get into abstraction hell and slow everyone down because the rock-stars at the top think it’s a great idea.
In the end it would be nice to figure out a variety of tools and techniques that apply to different scopes of a product and the product lifecycle. I can see utility classes being great for quick prototype and troubleshooting, but I wouldn’t want to use them for production. I have loved using CSS Modules but they’re very slow to compile and there are some challenges leveraging them outside of an SPA/React environment. SASS is great and I love the structure and organization it can bring to your CSS, but it can get out of hand pretty quickly and turn into a convoluted mess. BEM runs into the same problem as always – naming things is hard. CSS-in-JS styles can’t be shared in non-SPA projects… etc.
I HAVE A GREAT IDEA! Let’s use plain old CSS! (pause for effect…only hear crickets .)
N-no, I am serious, guys! We are always looking for that greener grass, and so a good many of us (myself included…easily proved by my near- fanaticism with the no-longer-CSS-but-not-quite-BOOTSTRAP, W3CSS ) search for–and find–that next best thing. And if you think about it for a moment, you’ll realize that, with all of the xSSes (excess-ess-es, pun not originally intended, lol), and thisSS, and USS, MESS, GPSS, and probably even LMNOPSSes on top of all of the .js pre, post, and I think even post-partum-processors, well… shoot! With that many flavors, of course we’re all going to eventually find the perfect patch of “greener grass!”
Oh, but then we have to learn more stuff (and here you thought CSS was getting too big), like mixins, moxins, boxins, and toxins. Add some relearning of the CSS formats, and…ugh! Yeah, I know: but don’t TL;DR me just yet. I am nearly done. But the point was to make you feel a bit exhausted. I mean, you have to admit: there are a lot of the CSS/JS-alternatives out there.
And most, if not all of them really only help you do what you could already do with the original CSS, but a bit faster or less code, which is also one and the same.
There are constant updates and revisions to CSS. Actually, even with my love of W3CSS, I am returning to my roots, so to speak… OUR ROOTS, actually. Back to the OGCSS? Okay, okay…too much. Back to the original CSS. And honestly? I truly like what I am seeing. Try it out again. You may be, like I was, pleasantly surprised.
Take care, and
Happy Coding!
Michael C