In Search of a Stack That Monitors the Quality and Complexity of CSS

Avatar of Bart Veneman
Bart Veneman on (Updated on )

Many developers write about how to maintain a CSS codebase, yet not a lot of them write about how they measure the quality of that codebase. Sure, we have excellent linters like StyleLint and CSSLint, but they only help at preventing mistakes at a micro level. Using a wrong color notation, adding a vendor prefix when you’re already using Autoprefixer, writing a selector in an inconsistent way… that kind of thing.

We’re constantly looking for ways to improve the way we write CSS: OOCSS, BEM, SMACSS, ITCSS, utility-first and more. But where other development communities seem to have progressed from just linters to tools like SonarQube and PHP Mess Detector, the CSS community still lacks tooling for deeper inspection than shallow lint rules. For that reason I have created Project Wallace, a suite of tools for inspecting and enforcing CSS quality.

What is Project Wallace?

At the core, Project Wallace is a group of tools that includes a command line interface, linter, analysis, and reporting

Here’s a quick rundown of those tools.

Command Line Interface

This lets you run CSS analytics on the command line and get statistics for any CSS that you feed it.

Example output for projectwallace.com

Constyble Linter

This is a linter designed specifically for CSS. Based on the analytics that Wallace generates, you can set thresholds that should not be exceeded. For example, a single CSS rule should not contain more than 10 selectors, or the average selector complexity should not be higher than three.

Analysis

Extract-CSS does exactly what the name says: Extract all the CSS from a webpage, so we can send it over to projectwallace.com for analysis.

Reporting

All analysis from Extract CSS is sent over to projectwallace.com where a dashboard contains all of the reporting of that data. It’s similar to CSS Stats, but it tracks more metrics and stores the results over time and shows them in a dashboard. It also shows the differences between to points in time, and many, many more features.

A complexity analysis generated by projectwallace.com

Analyzing CSS complexity

There aren’t many articles about CSS complexity but the one that Harry Roberts (csswizardry) wrote got stuck in my brain. The gist of it is that every CSS selector is basically a bunch of if-statements, which reminded me of taking computer science courses where I had to manually calculate cyclomatic complexity for methods. Harry’s article made perfect sense to me in the sense that we can write a module that calculates the complexity of a CSS selector — not to be confused with specificity, of course, because that’s a whole different can of worms when it comes to complexity.

Basically, complexity in CSS can appear in many forms, but here are the ones that I pay closest attention to when auditing a codebase:

The cyclomatic complexity of CSS selectors

Every part of a selector means another if-statement for the browser. Longer selectors are more complex than shorter ones. They are harder to debug, slower to parse for the browser and harder to override.

.my-selector {} /* 1 identifier */
.my #super [complex^="selector"] > with ~ many :identifiers {} /* 6 identifiers */

Declarations per ruleset (cohesion)

A ruleset with many declarations is more complex than a ruleset with a few declarations. The popularity of functional CSS frameworks like Tailwind and Tachyons is probably due to the relative “simplicity” of the CSS itself.

/* 1 rule, 1 declaration => cohesion = 1 */
.text-center {
  text-align: center;
}

/* 1 rule, 8 declarations => cohesion = (1 / 8) = 0.125 */
.button {
  background-color: blue;
  color: white;
  padding: 1em;
  border: 1px solid;
  display: inline-block;
  font-size: normal;
  font-weight: bold;
  text-decoration: none;
}

The number of source code lines

More code means more complexity. Every line of code that is written needs to be maintained and, as such, is included in the reporting.

Average selectors per rule

A rule usually contains 1 selector, but sometimes there are more. That makes it hard to delete certain parts of the CSS, making it more complex.


All of these metrics can be linted with Constyble, the CSS complexity linter that Project Wallace uses in its stack. After you’ve defined a baseline for your metrics, it’s a matter of installing Constyble and setting up a config file. Here’s an example of a config file that I’ve pulled directly from the Constyble readme file:

{
  // Do not exceed 4095 selectors, otherwise IE9 will drop any subsequent rules
  "selectors.total": 4095,
  // We don't want ID selectors
  "selectors.id.total": 0,
  // If any other color than these appears, report an error!
  "values.colors.unique": ["#fff", "#000"]
}

The cool part is that Constyble runs on your final CSS, so it does its thing only after all of your preprocessed work from Sass, Less, PostCSS or whatever you use. That way, we can do smart checks for the total amount of selectors or average selector complexity — and just like any linter, you can make this part of a build step where your build fails if there are any issues.

Takeaways from using Project Wallace

After using Project Wallace for a while now, I’ve found that it’s great for tracking complexity. But while it is mainly designed to do that, it’s also a great way to find subtle bugs in your CSS that linters may not find because of they’re checking preprocessed code. Here’s a couple of interesting things that I found:

  • I’ve stopped counting the amount of user stories in our sprints where we had to fix inconsistent colors on a website. Projects that are several years old and people entering and leaving the company: it’s a recipe for getting each and every brand color wrong on a website. Luckily, we implemented Constyble and Project Wallace to get stakeholder buy-in, because we were able to proof that the branding for our customer was spot on for newer projects. Constyble stops us from adding colors that are not in the styleguide.
    A color graph proving that our color game is spot on. Only a handful of colors and only those that originate from the client’s styleguide or in the codebase.
  • I have installed Project Wallace webhooks at all the projects that I worked on at one of my former employers. Any time that new CSS is added to a project, it sends the CSS over to projectwallace.com and it’s immediately visible in the projects’ dashboard. This makes it pretty easy to spot when a particular selector of media query was added to the CSS.
    “Hey, where did that orange go?” An example diff from projectwallace.com.
  • The CSS-Tricks redesign earlier this year meant a massive drop in complexity and filesize. Redesigns are awesome to analyze. It gives you the opportunity to take a small peek behind the scenes and figure out what and how the authors changed their CSS. Seeing what parts didn’t work for the site and new parts that do might teach you a thing or two about how rapidly CSS is moving forward.
  • A large international company based in the Netherlands once had more than 4,095 selectors in a single CSS file. I knew that they were growing aggressively in upcoming markets and that they had to support Internet Explorer 8+. IE9 stops reading all CSS after 4,095 selectors and so a good chunk of their CSS wasn’t applied in old IE browsers. I sent them an email and they verified the issue and fixed it right away by splitting the CSS into two files.
  • GitLab currently uses more than 70 unique font sizes. I’m pretty sure their typography system is complex, but this seems a little overly ambitious. Maybe it is because of some third party CSS, but that’s hard to tell.
    A subset of the 70+ unique font-sizes used at GitLab.
  • When inheriting a project from other developers, I take a look at the CSS analytics just to get a feel about the difficult bits of the project. Did they use !important a lot? Is the average ruleset size comprehensible, or did they throw 20+ declarations at each one of them? What is the average selector length, will they be hard to override? Not having to resort to .complex-selector-override\[class\][class][class]...[class] would be nice.
  • A neat trick for checking that your minification works is to let Constyble check that the Lines of Code metric is not larger than 1. CSS minification means that all CSS is put on a single line, so the Lines of Code should be equal to 1!
  • A thing that kept happening in another project of mine was that the minification broke down. I had no idea, until a Project Wallace diff showed me how a bunch of colors were suddenly written like #aaaaaa instead of #aaa. This isn’t a bad thing necessarily, but it happened for so many colors at the same time, that something had to be out of order. A quick investigation showed me that I made a mistake in the minification.
  • StackOverflow has four unique ways of writing the color white. This isn’t necessarily a bad thing, but it may be an indication of a broken CSS minifier or inconsistencies in the design system.
  • Facebook.com has more than 650 unique colors) in their CSS. A broken design system is starting to sound like a possibility for them, too.
  • A project for a former employer of mine showed input[type=checkbox]:checked+.label input[type=radio]+label:focus:after as the most complex selector. After inspecting carefully, we saw that this selector targets an input nested in another input. That’s not possible to do in HTML, and we figured that we must have forgotten a comma in our CSS. No linter warned us there.
  • Nesting in CSS preprocessors is cool, but can lead to buggy things, like @media (max-width: 670px) and (max-width: 670px), as I found in Syntax.fm.

This is the tip of the iceberg when it comes to Project Wallace. There is so much more to learn and discover once you start analyzing your CSS. Don’t just look at your own statistics, but also look at what others are doing.

I have used my Constyble configs as a conversation piece with less experienced developers to explain why their build failed on complex chunks of CSS. Talking with other developers about why we’re avoiding or promoting certain ways of writing CSS is helpful in transferring knowledge. And it helps me keep my feet on the ground too. Having to explain something that I’ve been doing for years to a PHP developer who just wanted to help out makes me re-think why I’m doing things the way I do.

My goal is not to tell anyone what is right or what is wrong in CSS, but to create the tools so that you can verify what works for you and your peers. Project Wallace is here to help us make sense of the CSS that we write.