CSS Preprocessing in 2022

Tags: webpage, development, react, CSS, SASS

New Year, new Stack?

It’s the ever-present question of our age, “What technology do I need to learn to keep up with the state-of-the-art?” I’m not going to address all of that in one post, but I’ll cover what I’ve learned and experienced about the internet’s favorites specification system. It’s built-in to your browser, it’s been getting love and care since it was created sometime in the dark past, and it’s loved as much as JavaScript.

So CSS is old and broken and that’s why we need Preprocessors right?

TL;DR

This article goes on a bit about why I’m not a fan of plain CSS, so in short:

  • Default to CSS-in-JS for React / Angular / Vue projects
  • Use a preprocessor if you can’t or don’t want to use CSS-in-JS
  • if you hate preprocessors, or love hand-writing CSS then sure don’t use one

Cascading Styles for everyone

When was CSS first released - or was specified since the internet was super different in 1996 - it’s first “key characteristic,” was

CSS allows both authors and end users to specify style sheets and control presentation characteristics, such as font and color.

And it aimed to reduce bandwidth because, apparently:

On the Web today, it’s common to make images of text in order to control fonts and colors.

The web was simply different then and it was built to meet a weirder jankier web. Today, if you try to push text content in an image without alt text, you’ll get dinged on SEO because of outright hostility to accessibility. CSS1’s spec is not dissimilar to the everyday styling we work with today. Class selectors, id selectors, inheritance, “contextual selectors,” or combinators, along with some pseudo-elements. It’s all there - well maybe not all of it - but the vast majority has always been there.

I got to reading up on why CSS is the way it is, and I really went down a rabbit hole. I’ll have another article that goes into that later, but for now my point is that CSS has a lot of old stuff tacked on to it, and it’s gotten pretty good in spite of that.

If CSS is so Good, Why Do I Need a Preprocessor Anyway?

SCSS, LESS, Stylus, and PostCSS still exist today even after the latest spec of CSS. SASS gets 6 million weekly downloads on NPM today (React gets 13 million for comparison). If that’s not a heavily used library then something is very wrong. CSS has most of he features that these preprocessors do, including variables, so why bother with these extra build steps and adding parts to your pipelines which could break? What are all these developers doing if we have good CSS ready to go?

CSS Misses a Few Things

Or a few things miss it, maybe.

If you’re developing for mobile, accept a preprocessor and avoid a prefixing headache. The galaxy brains at Apple decided to “ruin the mobile web”, by adding the webkit prefix. If you’re making something web-adjacent, and you want it to be available on mobile you have to carry the burden as well. There’s not really an option.

Most people in the US have smartphones, and around half of all traffic is from mobile devices. Usually half of that traffic will be on Apple devices, so for a typical website, around 25%-40% of your traffic will need you to remember webkit-specific tricks. Or you can go the sensible route and let your tooling to the hard work for you.

Even then, adding a preprocessor won’t make all of your fruit-based woes go away. In some cases you’ll need to just buckle down and write out -webkit-. Sorry.

Variables aren’t that good in CSS

CSS Variables are a relatively new thing for the language; on its 16th birthday the CSS spec got variables to some controversy. And they didn’t fundamentally shake the world of CSS. Maybe that was because Microsoft’s implementation of the Edge browser at the time also didn’t support them.

What bugs me about the CSS implementation of variables stems from the same problem I have with the rest of CSS. Scoping in CSS easily becomes a problem, and most of the styling bugs I make for myself are a result of the easily tangled web of inheritance rules I’ve inadvertently set up for myself. Unless I’m working on a small enough project where I can keep track of all of the variables, there will eventually come the time where I get lost and spend an hour fixing a presentation bug.

Sometimes You Just Need More

Variables and inheritance are the sore spot for large CSS projects. If you’re lucky enough or have the forethought to plan out your CSS you can avoid this. But more often than not things grow and your projects become difficult to manage. SASS-y preprocessors also provide additional tools, like color calculators and opacity blending, and have for a good while longer than CSS. Plus, while you can modify CSS variables on the fly, you’ll need to make sure that redraw times don’t chew into your presentation.

With later additions to the CSS, maybe we’ll see the same kind of programmatic systems we get from SASS and LESS. Maybe you’ll be able to use mixins to group pieces of style together. But as far as I’m aware, that’s not today, and it’s not in the immediate future. CSS is tied up by a whole group of people who’s job it is to not only make new features for the language, but not break too much of the jank they had before. Preprocessors aren’t always beholden to such problems.

The bottom line is preprocessors can be more powerful than CSS for a simple reason. Preprocessors build on top of CSS, so in essence anything that CSS can do your preprocessor can do. By the end of your build, you’re working with CSS! Plus, you get the benefits of using preprocessors, which gives you cross-browser compatibility and programmatic styling.

CSS preprocessing Isn’t a Silver Bullet

I said that CSS variables don’t fix a lot of problems with CSS. Well, the problem of CSS is still prevalent in SCSS and LESS, but typically variables are constrained to the global scope or modules, and these modules have defined boundaries that are a bit easier to work with because you can separate them. Preprocessors don’t fix this issue entirely; it still takes vigilance to avoid the pitfalls of unexpected inheritance, and spaghettification among several modules. Bad code can be written in the best language.

The big drawback to preprocessors that you can’t avoid is the extra build step. The second drawback to any of these systems is that you need to learn another language on top of HTML, JS, and CSS. If you’re using a frontend framework or library then you’ll also need to know that. No one said being a web developer was easy.

Honestly, it isn’t too much extra work because the big CSS preprocessors all follow the conventions of CSS while adding syntactic sugar on top of that for things like mixins and variable declarations. However, if you want to reduce the total number of tools in your application then there’s the 2-in-1 option of CSS-in-JS, which I’m a personal fan of.

CSS-in-JS

CSS-in-JS libraries are much like CSS preprocessors in that there’s an additional build step that compiles to CSS, but it also condenses the number of tools you use, potentially down to only JavaScript (or TypeScript). You can leverage the same tooling as the rest of your code and collocate styling with the components that need those styles. I love the upshot of added organization: I’m never too far away from the code that makes the component happen - it’s all in one spot.

It may not be because of global style sheets, but I feel that CSS encourages centralization in one god sheet. What follows is convolutions brought on by CSS’s inheritance system. It never fails that I get lost sifting through styling on projects with more traditional style sheets, because I have to trace the lines back from div to main and check which class added what.

CSS-in-JS brings its own hazards: collocating styles with a large amount of elements and components means there’s a high risk of fragmenting your styles and introducing inconsistencies. CSS-in-JS is also another new tool, and one that many popular libraries like Bootstrap and Material don’t make use of. I haven’t tried my hand at it, but I can’t imagine using stlyed-components and Bootstrap in the same project would be a trivial task. There’s a CSS Tricks article that talks about how CSS-in-JS isn’t a pass to learning CSS or making good styles, but I think CSS-in-JS makes for a great tool in any Web Developer’s toolkit.

It’s Your Toolkit

Use CSS preprocessors when it makes sense, which is most of the time. Here’s the decision list I’m using:

  • CSS-in-JS doesn’t fix all problems, but it pairs the power of JavaScript with CSS
  • if you don’t need that and you’re good at CSS, still use a preprocessor
  • if you want consistency and don’t want to tie in to a JS library, pre-processors are your friend
  • if you hate preprocessors, or love hand-writing CSS then sure don’t use one

Most detractors have valid points, but I don’t agree with them. If this viewpoint convinces you otherwise, then by all means go ahead and don’t use these tools.

For me, these tools often tell me if I’m over-complicating or muddying things. Ideally, you can create a design that will work of predictable and repeatable elements. Maybe this gets into the same problems that object-orientation and classing or interfacing things gets into, where the classifications and interfaces become their own burden. However, I’ve seen the opposite happen, where designs are allowed to spiral into complexity and mud because they’re not able to be abstracted, either because they use too many colors or have consistent one-off spacings or because fonts get picked from a hat.

Preprocessors ask of you “before you add that style, is it actually consistent with anything else?” And if the answer is “no,” you may conclude that perhaps it shouldn’t be that way. Unfortunately, they don’t do the work of telling you how to get that consistency, or how to make things coherent. I guess we’ll wait for the day our AI overlords can design!