CSS methodology part II: Abstraction or isolation

Following on from my post setting out a direction for my CSS methodology, I want to go into a little more detail about how I am arriving at my solution.

Fundamentally, I am, as I mentioned in that post, in pursuit of a way to write CSS that is easily maintained and scaled. This is an issue that I think most people who write CSS have felt at some point. If you’ve ever had someone tack a style onto the end of your style sheet, because they didn’t know where else to put it, or were afraid of breaking something, then you are one of those people. This is also an issue that has been addressed, and probably fair to say, solved, multiple times. So why reinvent the wheel? There are broadly two answers to that. One is a philosophical one, about wanting agency in the way I work. The other is more pragmatic. I’m going to focus on the latter here.

If you go back maybe ten years, CSS was a much less scrutinised language. It was a place where some magic happened, and people would do, in that CSS document, whatever it took to make that magic happen. The endless possibilities that came with this were, and are incredibly exciting to me. They also resulted in documents that only the author could understand (and even they would probably struggle if they came back to it a few months later). Two concepts emerged that sought to bring some sanity to style sheets. These map to the methods (or ideologies) that I identified in the previous post: CSS determined by HTML, and HTML determined by CSS.

One solution is abstraction (HTML determined by CSS). When you follow this method, you abstract your styles to the point that they become highly reusable, and generic enough to avoid unpredictable or unintended side effects. Essentially the idea here is that you write less CSS. You instead offload the complexity into the HTML, and create the desired visual effect there. This is the essence of a CSS framework, such as Bootstrap. Tachyons however, would be a better example of a framework that takes this approach to its logical extreme.

Abstract selectors example:

<button class="background-red hover-background-dark-red text-white font-size-6 font-bold paddingY-2 paddingX-4">
  Remove item from shopping cart
</button>

The other solution takes the opposite approach. Instead of aiming for reusability, it aims for isolation. Styles only effect specific, targeted elements (CSS determined by HTML); there is minimal inheritance. The aim here is to prevent any “leaking” of styles, meaning that someone editing the code can do so with the confidence that nothing but the specific element they want to effect will be effected. Equally, finding the right line of code to edit should be more intuitive. This approach relies upon some form of scoping. Perhaps the most simple form is a naming convention for the selectors. BEM is a popular CSS naming convention, and can serve this purpose, however it is not specifically geared towards isolation, and could be used with abstract class names, to some extent.

Isolated selector example:

<button class="shopping-cart__remove-button">
  Remove item from shopping cart
</button>

The extent of abstraction is a key point when weighing up these approaches. It can be tempting to chart a course somewhere between the two approaches. Some moderate abstraction here, a little bit of isolation there. There’s a danger here though of not preventing the problems that these approaches are designed to prevent. Someone who has spent a lot of time thinking about this stuff is Ben Frain, author of Enduring CSS. He would seemingly agree with this assessment. On the Shoptalk Show podcast he described a line with abstraction at one end, and isolation at the other; and suggested that the approaches that end up working are at either end of that line. Going so far as to say “There’s nothing in the world that makes me believe you can mix the two and have a successful approach”.

In Enduring CSS Ben describes his method of working with CSS, which sits at isolation end of the line. At the other end of the line is Adam Wathan, the author of utility first framework Tailwind. He makes the case for abstraction in the brilliant blog post CSS Utility Classes and “Separation of Concerns”. The post details Adam’s journey from semantic classes to utility classes. While this didn’t convince me that abstraction was the approach for me, it did give additional weight to the argument for choosing an extreme.

Of these two approaches, isolation is the better fit for the way I like to work, and for the types of projects I tend to work on. I could conceive of a scenario where abstraction would feel like a better option, but to my mind it would be rare that a project would benefit enough to justify the downside. The downside, as I see it, is that abstraction has to sacrifice a long standing benefit of CSS. That is the separation of style and content. Being able to restyle a page, without changing the HTML, was once a kind of gold standard for CSS. This idea was at the heart of the once popular CSS Zen Garden. It was also presumably at least part of the thinking behind this W3C guideline:

[…] authors are encouraged to use values that describe the nature of the content, rather than values that describe the desired presentation of the content.

Over time, this idea has become less valued, and apparently easy to abandon. Given the benefits of abstraction, this is understandable. In reality, the ability to redesign a website, and not touch the markup has been of limited use. You might ask “Who actually does that?”. I’d like to offer two examples of where this hypothetical CSS only redesign is actually useful.

Scenario one

I used to work for an organisation that spent a lot of time, essentially re-skinning a particular web application, for clients. This software had no real concept of “themes”, so re-skinning was actually the type of redesign that Zen Garden prophesied. There was not the zen-like separation of content and style here though (the software was built on Bootstrap). This meant that when applying my new designs I had two options. Option one, apply styles contrary to their selectors; for example tell a class named span9 to be full width (yuck). Option two, edit all the templates, to change the classes. Option two is the saner option here, but it still felt pretty crazy. Not only did it add complexity to what should have been simple changes, it also added complexity to the code base. Adding plugins became more complex, as they introduced their own template modifications (which might follow the same naming conventions). Updating the software was potentially more complex as there were now loads of files that differed from the core. Sure, the way that this software handles customisation could be improved, but the reality was that in this not-for-profit environment, there was not the time/money available to make these improvements. Much like the hypothetical redesign, hypothetical complexity gets trumped by the quickest path to getting the thing shipped.

Scenario two

If you read scenario one and thought “yeah but, that’s an edge case”, scenario two is for you. There are micro redesigns happening to millions of website right now. We don’t call them redesigns though, we call them responsive designs. If you are reading this on a large screen, and resize the browser, you’ll likely see the page get a new layout, to better make use of the available space. This practice is ubiquitous, and I would argue, at odds with the presentational or utility classes that abstraction requires.

Responsive design is obviously possible with abstraction. You can do something like this:

<button class="small-screen-font-size-6 medium-screen-font-size-8 large-screen-font-size-10">
  Remove item from shopping cart
</button>

While this exact example might not exit in the real world, I think it demonstrates what is required to make something responsive with utility classes. Even in this simplified example it’s already getting complicated to think about. Imagine you wanted this button to be different in many ways, at many different screen sizes. Imagine then you wanted to respond to other variables. Orientation, ambient lighting? This all points to a pretty undesirable scenario in my opinion.

This method either has the effect moving complexity from one place to another (undermining the objective of the exercise), or restricting design choices (which could be considered a good thing in some scenarios). I want a system (that I’m going to use for the majority of my work) to start from that fabled place of endless possibilities.