CSS: The Good, the Bad, and the Ugly.

Yeray David Rodriguez Domínguez
edataconsulting
Published in
12 min readApr 13, 2021

--

Let’s be honest: most software developers hate CSS. Maybe is because of the crazy interdependencies between rule values, the difficulty to deal with side effects or… well, because they think that it is just about aesthetics. Why should I care about the colors in my application if I do not care about the color of my shirt?

While it is true that domesticating all those rules and selectors can be sometimes hard, most of the time the real difficulty of dealing with CSS comes from a higher more abstract level: how we perceive and approach to this technology, in opposition to regular, imperative, programming languages. This usually derives from prejudices that make CSS an anathema for most programmers. But, like all prejudices, they disappear when you get to understand it better.

In this article, I am going to change the focus from particular rules and values (except some examples) and try to change our perception of the technology, checking already known practices that can be applied to CSS to turn it into maintainable and efficient code (the GOOD CSS), identifying the most typical pitfalls (the BAD CSS) and, well, speak about that smelly, ugly CSS, maybe the most difficult to deal with.

And you should care about the color of your shirt, also. At least when you have your webcam on.

Ok, let’s start settling a delicate subject:

Does CSS still matter?

Sometimes people stop me in the street to ask me: but Yeray, why are you so concerned about CSS? I use that wonderful WYSIWYG view editor that generates CSS for me, I don’t have to write it anymore.

Obviously, those applications are helpful, but code generated automatically is usually much worse than handcrafted, and a good coder must make decisions about structure, placement and naming that is difficult to fully delegate to an application. So yes, you probably are going to still know something about CSS.

But I do not care about the look of my wonderful application if it works.

If it does not look good, then it does not work well. The UX is an intrinsic part of an application. Good software must also look like good software. Much of the perception a user has of an application comes from the raw sensorial experience, and an inconsistent style, a bad-looking button, or a pause without feedback can make the low-grade product perception get installed in his/her mind.

Ok, now that we know that good old CSS still matters, let’s see how we can improve our stylesheets quality.

The GOOD CSS

We have mastered the secrets of good clean software handcraft, we refactor, we do automated tests, our modules are so legible that could win a Pulitzer prize, but still… our stylesheets are a huge mess of tangled rules and side effects about to explode. How can it be possible?

This is because we do not consider CSS code as a first-class citizen.

Many people still do not even consider CSS as code and think about it more like JSON or XML passive documents. But CSS is written for a specific and well-defined purpose, they follow established rules not only of formatting, but also validity, scope, precedence, (sort of) inheritance, and using preprocessors, composition, variable operators, and even some flow control. How you define the rules affect processing performance, and they can be refactored for better legibility, scalability, and reusability. This sounds like code to me, maybe not the strict imperative sequential code, but well, in software development as well in other things in life, it’s good to be open-minded to difference.

And if is (some kind of) code, let’s treat it with care, following some well-known clean code principles that can be applied to our CSS rules, and doing so, we will make them become the nice guy in our project.

DRY: Don’t repeat yourself

A basic one. In fact, some specialists consider that almost all the problems in code are caused by code redundancy, and in CSS is easy to spread sets of rules that can be unified easily with a fast refactor:

Duplicated code extraction using a new class in which the duplicated code is to be stored
A simple CSS refactor to remove code duplication. Elements now will use both alert and alert subtype classes

KISS: Keep it simple, sweety

(Actually, the acronym is keep it simple, stupid, but I prefer a nicer form of it)

This traditional clean code principle that advocates for simple, clean structures and names, is easy to apply in CSS: at the end, a typical stylesheet is composed of selectors, which can be explicitly labeled with classes and/or IDs, and groups of simple property-value pairs.

However, it is easy to see unnecessarily complex selectors made up of dozens of chained classes, elements, pseudo-classes, and combinators when easy alternatives can be used instead. We will check them a moment later when we talk about Ugly CSS.

YAGNI

Finally, the You Are Not Gonna Need It principle. In this case, we move to the other end of the spectrum: sometimes we begin writing lots of nice CSS rules for components that we still do not know if we are going to need them. CSS, like regular imperative code, must be written just when is needed to, not before.

The BAD CSS

Few things in life are completely good or bad, and some must be evaluated in the particular social context and time in which it arises. But for the sake of keeping with the cool title of this article, I will consider a particular definition of bad CSS:

Bad CSS is the one that does NOT respect the final user.

More precisely, the CSS that that unnecessarily impacts the capacity of users (especially users with limitations) to access and use the website, and/or frustrates the reasonable expectations of them. Those are disrespectful applications of this technology, and therefore is bad CSS.

Let’s put two examples for this:

:focus { outline: none; }

This simple piece of CSS is, to say it gently, obscene.

The :focus pseudo-class is used to select an element that has received focus. By default, the browsers highlight this element with a blue outline.

If we apply the preceding rule, the focused element will not be distinguishable from an unfocused element. This has a terrible impact on people that must navigate through the elements of a website using the keyboard or voice recognition (like many persons with disabilities), or people with poor eyesight (like senior users). With that simple line, we can even prevent a blind person to interact with our site. We limit his/her freedom and make the digital breach even wider with a line whose only justification, probably, is I don’t like that blue line.

This innocent looking CSS is also a dramatic failure of CSS:

.header-level1 {…}  <span class=”header-level1">Title of the page</span>

In this case, we are forcing a regular text to behave like a header instead of relying on the DOM elements we have at our disposal: the good old h1, h2, and so on.

The use of DOM elements is not only a matter of HTML/CSS legibility: it is crucial for accessibility. Many screen readers (the devices with which the blind or vision impaired people browser websites) rely on those DOM elements to understand the website structure and describe it to the user. If we disguise the headers in other elements, the screen reader is not going to discover them, and therefore, will present an incomplete or incorrect page structure to the user.

The last example is about expectations and falls more into the UX design area, but a CSS developer (actually, all front-end developers) must have some basic background about accessible UI, which is closely related to stylesheets.

Let’s analyze this:

.save-button { background: red }
.discard-changes-button { background: green }

There are plenty of visual communications standards already settled we all know and follow. Hyperlinks are usually underlined, bolder, or with a different color than the regular text. Buttons are usually rectangular, background-colored shapes. And certain colors have certain meanings assumed by practically any user in the world. If we violate those well-established patterns in our CSS, we will not be respectful with the expectations of the user and will frustrate him/her.

In the previous example, we can infer that the save (document or state) button of our website is red and the discard changes is green. Although we do not have the whole picture, the user will be probably confused by this color schema, as the secure options (like saving our work) are usually marked in green and the dangerous (like discarding it) in red. By ignoring his/her expectations, with that style definition the possibility that the user discards hours of work will be huge.

Although there is plenty of information about CSS and accessibility worth reading, as well as established elements design standards, the golden rule here is to be empathic: after we finish implementing the CSS of a view, double-check it with the eyes of the final user.

The UGLY CSS

There are also some usual practices that, not being incorrect, make the code dramatically more error-prone and less scalable.

Let’s make a brief overview through those little sins and see how we can avoid them:

Obfuscated rules

Legibility is one of the pinnacles of Clean Code, the corpus of good practices established by legendary software craftsman Robert C. Martin. And we still see this kind of selectors (and much, much worse) in the stylesheets of professional sites:

body .main-content .section:not(.toc)::first-child p::last-child {…}

This selector seems to be used to match the first section (not being the Table of Content) in a document (probably a preface) and the last paragraph in it. Let’s suppose that is a disclaimer or an erratum. In this case, maybe the code could have its legibility improved by replacing it with a simple, expressive, and easy to read class like

.preface-disclaimer {…}

And applying this class to the corresponding paragraph in the HTML code. This way the stylesheet gets more expressive and readable, and when the designer tells us to change the style of the erratum 5 years after writing it, we won’t waste time locating the particular rule that matches it.

This simple improvement (replacing obscure selectors by classes) can be overlooked by programmers by simple laziness or by something more sinister: the rejection of expressive, semantic rules in favor of the pragmatic CSS style that is becoming popular in some niches by utility-first frameworks. But I will talk about it later (I don’t want to make enemies so soon)

Bad naming

Related also with legibility, it is fascinating to see that lots of people prefer to sacrifice it completely to gain two or three microseconds at typing, skipping two or three characters.

So, as an example, it is usual to see classes like

.wnBox_bg

Instead of

.warning-box-big

As can be seen, the second form takes painful eons to type compared to the first one. But, when time passes and we have to refactor (or just reuse) that class, if we skipped a few characters, we probably won’t be able to tell if that name stands for “big warning box”, “window box background”, “win box black border” or “would not boxing be great (?)”. We will waste pretty much time thinking about it than the time we saved skipping those characters.

Inconsistent CSS

And, of course, whatever naming pattern we choose, is important to stick to it in all the stylesheets. This implies using the same capitalization and words separation method (kebab-case is the most usual in stylesheets, but is not the only one), same prefixing standard, and entities level order.

There are some naming methods out there, and one worth looking at is BEM, in which the class names are constructed with a simple schema:

Block-Element-Modifier

For instance: sidebar-menu-item-disabled

Using this naming schema (or any other we decide to use) whenever is possible may help to keep a consistent look in all the components definitions of our CSS code.

Untested CSS

Software testing is now an inherent part of modern software implementation techniques. The test coverage (amount of code under control of automated test) is considered a code quality measurement, but still, is very strange to see CSS code under automated test control, even in larger and professionally crafted products. But when stylesheets grow and the number of views gets higher and more complex, untested CSS code is a hazard to the stability and scalability of the project.

But how it is possible to add automated tests to a code that produces a (primarily) visual output?

The answer is visual regression tests. Those are tools that take snapshots of views and elements of the applications and compare them with a baseline, or expected, snapshots set. The amount of difference between the images is calculated, and, if is over a certain threshold, sets the test as failed. Some even generate a heat map with which the user can check the view areas that have changed, probably by undesired CSS side-effects.

Utility classes and why they are really dangerous.

A recent trend in CSS is the use of utility classes. They encapsulate a few CSS declarations (sometimes, only one) that produces a specific representational, non-semantic effect (like centering, setting margins, or border style). They can be seen almost as CSS macros.

This is a typical HTML element that uses utility classes:

<button class=”flex-none flex items-center justify-center w-9 h-9 rounded-md text-gray-400 border border-gray-300" type=”button” aria-label=”like”>

It may seem that I have looked for an especially pathologic example, but it is one of the see how cool is this examples that can be seen on the home page of the most popular utility classes framework, Tailwind.

All those classes are utility classes. justify-center, for instance, is an example of a utility class that is equivalent to the justify:center rule definition.

Although there is no doubt that in certain cases they can be useful, in my personal opinion, they must be used with extreme care and always combined with semantic and higher-level classes. They should be a complement to components and layout elements definitions, never the foundational stones of a front-end implementation.

Let’s see why:

  • As reusable semantic classes are replaced with bunches of inline utility classes, style definition is repeated all through the code, violating the DRY principle. This results in less maintainable code, as changes in the design are extremely more difficult to apply and error-prone: when something changes in the UI, you must go element by element checking if the change is applicable and perform the change in each one of them. With semantic element style definitions, you only have to change the corresponding class. Of course, that component extraction can be done with utility classes, but heavy code repetition is present in almost all examples of styling based on utility classes and is a natural side effect of this approach.
  • They tend to obfuscate the HTML/CSS, as they remove expressive class names from the code and replace them with long arrays of abbreviated (or even codified) class names. The resulting code is less legible, and therefore, less comprehensible to new developers. Maintenance is, again, damaged.
  • With the generalized use of utility classes, there is no clear separation between the structure of the page (layout) and its appearance (theme), making the site less easy to customize. Some utility-first frameworks offer theming features using prefixes, but each new theme adds even more noise to already huge class definitions.

And as many of the utility classes works are a mere wrapper for a CSS definition to be used inline, I honestly do not see lots of advantages using them. I don’t think that they speed up the development, only in that ideal, inexistent world in which all websites are written only once and they don’t need any maintenance or further improvement.

Putting all together

A website application is usually composed of interacting modules and technologies, and every piece must have the same amount of robustness, scalability, and maintainability. A chain is as strong as the weakest link. And if this weakest link is involved in the UX and accessibility of the website, we have a problem in our product that sooner or later will explode.

The good news is that, according to what we have seen in this article, good CSS is not about mastering weird selector combinations that sound like demonic invocations; or memorizing all the property-value effects and interactions. If we know how to write good code, we know how to write good CSS. Just stick to an extremely simple layout, take your time to do things properly, and we will have the solid rock foundations your website needs.

In summary, it is good to follow those cute acronym principles and good practices, but what turns us the good guy in the movie is keeping focused on how what we code will affect the final users and how other programmers will continue our legacy. It is not about knowledge or expertise: it is about honesty.

--

--