Solving problems with CSS

Writing CSS is, much like the rest of web development, about solving problems. There’s an idea for laying out content, the front-end developer comes up with a way to get the content marked up, and she writes some CSS rules to turn the marked up content into the lay-out as it was intended. Simple enough, until we decide to outsource our abstraction needs.

TL;DR: I think there is a tendency to add solutions to code bases before carefully considering the problems they solve. Abstraction and the tooling that make them possible are awesome, but they do not come without some problems.

When going from paper, Photoshop, Illustrator or whatever into the browser, we use HTML, CSS and JavaScript to turn ideas about content, design and behaviour into some sort of system. We solve our problems by making abstractions. We don’t style specific page elements, we style classes of page elements. Some abstract more from there on, and generate HTML with Haml templates, write CSS through Sass or LESS or use CoffeeScript to write JavaScript.

I work at agencies a lot, so in my daily work I see problems solved in lots of different ways. Some choose to enhance their workflow with preprocessors and build tools, some don’t. All fine, many roads lead to Rome. Often, specifics like grids or media query management (apparently that is a thing) are outsourced to a mixin or Sass function. With so many well-documented libraries, plug-ins and frameworks available, there’s a lot of choice.

What tools can do

Libraries like Bourbon for Sass have lots of utilities that are solutions to solve specific problems. Their mixins can do things like:

@linear-gradient($colour,$colour,$fallback,$fallback_colour) 

which is transformed into a linear-gradient preceded by a -webkit prefixed version and a fallback background colour. Someone found it would be good to outsource typing the fallback background colour, the prefixed one and then the actual one. So they came up with a solution that involves typing the gradient colours and the fallback colour as arguments of a mixin.

This kind of abstraction is great, because it can ensure all linear-gradient values in the project are composed in the same, consistent way. Magically, it does all our linear gradienting for us. This has many advantages and it has become a common practice in recent years.

So, let’s import all the abstractions into our project! Or shouldn’t we?

Outsourcing automation can do harm

Outsourcing the automation of our CSS code can be harmful, as it introduces new vocabulary, potentially imposes solutions on our codebase that solve problems we may not have, and makes complex rules look deceivably simple.

It introduces a new vocabulary

The first problem is that we are replacing a vocabulary that all front-end developers know (CSS) with one that is specific to our project or a framework. We need to learn a new way to write linear-gradient. And, more importantly, a new way to write all the other CSS properties that the library contains a mixin for (Bourbon comes with 24 at the time of writing). Quite simple syntax in most cases, but it is new nonetheless. Sometimes it looks like its CSS counterpart, but it accepts different arguments. The more mixin frameworks in a project, the more new syntax to learn. Do we want to raise the requirement from ‘know CSS’ to ‘know CSS and the list of project-related functions to generate it’? This can escalate quickly. New vocabulary is potentially harmful, as it requires new team members to learn it.

It solves problems we may not have

A great advantage of abstracting things like linear-gradient into a linear-gradient mixin, is that you only need to make changes once. One change in the abstraction, and all linear-gradients throughout the code will be outputted differently. Whilst this is true, we should not forget to consider which problem this solves. Will we need to reasonably often change our linear-gradient outputs? It is quite unlikely the W3C decides linear-gradient is to be renamed to linear-grodient. And if this were to happen, would I be crazy if I suggested Find and Replace to do this one time change? Should we insist on having abstractions to deal with changes that are unlikely to happen? Admittedly, heavy fluctuation in naming has happened before (looking at you, Flexbox, but I would call this an exception, not something of enough worry to justify an extra abstraction layer.

Doesn’t abstracting the addition of CSS properties like linear-gradient qualify as overengineering?

It makes complex CSS rules look simple

Paraphrasing something I’ve overheard a couple of times in the past year: “if we use this mixin [from a library], our problem is solved automatically”. But if we are honest, there is no such thing.

Imagine a mathematician shows us this example of his add-function: add(5,2). We would need no argument over the internals of the function, to understand it will yield 7 (unless we are Saul Kripke). Adding 5 + 2 yields 7.

Now imagine a front-end developer showing us their grid function: grid(700,10,5,true). As a curious fellow front-end person, I would have lots of questions about the function’s internals: are we floating, inline-blocking, do we use percentages, min-widths, max-widths, which box model have we set, what’s happening?

Until CSS Grids are well supported, we can’t do grids in CSS. Yet we can, we have many ways to organise content grid-wise by using floats, display modes, tables or flexible boxes. Technically they are all ‘hacks’, and they have their issues: floats need clearing, inline-block elements come with spaces, tables aren’t meant for lay-out, etc. There is no good or bad, really. An experienced front-end developer will be able to tell which solution has which impact, and that is an important part of the job.

CSS problems are often solved with clever combinations of CSS properties. Putting these in a black box can make complex things look simple, but it will not make the actual combination of CSS properties being used less complex. The solution will still be the same complex mix of CSS properties. And solving bugs requires knowledge of that mix.

Solve the problem first

When we use a mixin, we abstract the pros and cons of one solution into a thing that can just do its magic once it is included. Every time the problem exists in the project, the same magic is applied to it. Given a nice and general function name is used, we can then adjust what the magic is whenever we like. This is potentially super powerful.

All I’m saying is: I think we add abstractions to our projects too soon, and make things more complex than necessary. We often overengineer CSS solutions. My proposal would be to solve a problem first, then think about whether to abstract the solution and then about whether to use a third-party abstraction. To solve CSS problems, it is much more important to understand the spec and how browsers implemented it, than which abstraction to use (see also Confessions of a CSS expert). An abstraction can be helpful and powerful, but it is just a tool.

Comments, likes & shares (9)

This was about Sass/mixin, tools which I think had nice and friendly communities, today's tools seem to have much more agressive and less reasonable communities, with more toxic hero worshipping.
@hdv Does this also apply to CSS-obstructions like Tailwind and Bootstrap?
@kdekooter yeah I think it can, I see both of those be helpful, powerful etc. But also that they are worshipped as being more than just a tool, which I think isn't particularly healthy
@hdv Is not the whole point of blogging that in the long run you end up quoting yourself? I love it when somebody quotes me. :-)