DEV Community

loading...

Relating SOLID principles to CSS and Tailwind

aaroncql profile image Aaron Choo ・8 min read

This is the second (and last) article in the series. If you haven't read the first one yet, which discussed basic ideas of how to apply OOP concepts to CSS, you might want to catch up on it first:

In this article, we start by introducing the SOLID principles into our CSS, and then relate everything back to Tailwind.

Single Responsibility Principle in CSS

The Single Responsibility Principle (SRP) states that every module, class or function should have responsibility over a single part of that program's functionality, which it should encapsulate. What it means is that every CSS class (or object as we have defined) we write should only have a single function or use, and it should do it well. Going back to our button example in the first article again, the CSS for .base-btn was as follows:

.base-btn {
  border: solid 2px cyan;
  color: white;
}
Enter fullscreen mode Exit fullscreen mode

This class actually violates SRP, because it has two different responsibilities: one to style the border, and another to style the text colour. In OOP fashion, we can say that the .base-btn class has more than a single reason to change (either to change the style of the border, or of the text colour). To try to remedy this, we can split the class up like so:

.btn-border {
  border: solid 2px cyan;
}

.white-text {
  color: white;
}
Enter fullscreen mode Exit fullscreen mode

However, one can say still that the .btn-border class violates SRP, since it actually contains styles for three different aspects of a border, and thus has more than a single responsibility. As such, we can split it up even further like so:

.solid-border {
  border-style: solid;
}

.width-2px-border {
  border-width: 2px;
}

.cyan-border {
  border-color: cyan;
}

.white-text {
  color: white;
}
Enter fullscreen mode Exit fullscreen mode

Now, each class truly has only a single responsibility and a single reason to change. However, the disadvantage is that what once took us 4 lines of CSS, now takes at least 12 lines (3 for each of the 4 classes), 3x more than what we started with! Note however, that this is a trivial example, and not representative of what is seen in practical settings. Since the classes defined here truly has a single responsibility, another person could also argue that you can use these classes in other parts of the HTML, without duplicating a single line of CSS, and therefore save more lines of CSS in the long run.

Remember that the purpose of SRP is so that the styles defined within a class is highly cohesive, thereby increasing the chance of reusing this class somewhere else. What this essentially means is the result of smaller classes, but many more classes, which may lead to more CSS than what we started with.

Always remember, that there is always the tradeoff between developer experience and code size, and always a certain overhead in adhering to any principle. For a small and trivial website, the benefits of adhering to SRP blindly might not be apparent, and may even hamper development.

Open-Closed Principle in CSS

The Open-Closed Principle (OCP) states that classes should be open for extension, but closed for modification. In CSS, it means that if we were to have base classes defined (eg, .white-text, .blue-border), we should try to avoid modifying it directly, but instead extend from it. Implicitly, this makes sense because any modification to the base .white-text class will most certainly affect all the HTML tags which use and depend on this class - something which we want to avoid.

In the previous article, we have already seen the idea of composition, which plays nicely together with OCP. Instead of modifying the base classes which already exist, we should instead compose them together to form our eventual style.

Of course, you could go the other route: use the base classes as defined, but override the undesirable rules using !important. However, this actually violates another one of the SOLID principles (discussed later, don't worry), not to mention the fragility of using !important to override your own CSS.

Liskov Substitution Principle in CSS

The Liskov Substitution Principle (LSP) states that objects of a superclass should be replaceable with objects of its subclasses without breaking the application. If you do not use a CSS processor like Less, Sass, or Stylus, this section might be less applicable to you. I'll admit, however, that this section isn't applicable to me either, not because I don't use CSS processors, but because I much prefer composition over inheritance.

I highly recommend this article, Should You Chain or Extend CSS Classes, which talks in great length about the advantages of composition over inheritance. Of course, this makes me sorely unqualified to talk about this section. Nevertheless, here is an article you can refer to regarding LSP in CSS if you aren't yet convinced about only using composition.

Interface Segregation Principle in CSS

The Interface Segregation Principle (ISP) states that no client should be forced to depend on methods it does not use. In terms of CSS, it can be easily rephrased to say that every block of CSS rule you write should not override any rules being composed.

Similar to previous examples, let's start with the base .base-btn class:

.base-btn {
  border: solid 2px cyan;
  color: white;
}
Enter fullscreen mode Exit fullscreen mode

Now, remember in the OCP discussion, we raised the possibility of overriding the base styles? Let's try that to style a button using base-btn, but with green text:

<button id="base-btn green-text">Press Me!</button>

<style>
.base-btn {
  border: solid 2px cyan;
  color: white;
}

.green-text {
  color: green !important;
}
</style>
Enter fullscreen mode Exit fullscreen mode

This clearly violates ISP, because the button depends on .base-btn, which has a defined rule for the text colour, but which the button actually doesn't want. By adhering to SRP and defining multiple (but very specific) base classes with a single responsibility only, this violation could have been easily avoided.

Again, it's a tradeoff between defining more specific base classes (high cohesion and loose coupling) versus defining one big general one (maybe more code reuse). If you do find yourself constantly overriding base styles like the above example though, it is a clear indication that your base classes are too fat, and that it's time to separate it out into more specific base classes.

Dependency Inversion Principle in CSS

The Dependency Inversion Principle (ISP) states that high-level modules should not depend on low-level modules, and both should depend on abstractions. In the general sense, the point of adhering to ISP is to reduce coupling between modules and promote module independence.

In HTML and CSS for example, it shouldn't matter to the parent div container (higher-level module) how the components within it (lower-level modules) are styled, as long as it doesn't break the layout, and vice versa. With this context, the excessive use of descendent and child selectors is a sign of the violation of DIP, since the descendent element is clearly dependent on its ancestor.

As such, instead of writing nested rules like .container .panel > .card for a nested card, writing a specific class like .first-inner-panel-card is preferable, because this class has a higher chance of further reuse in other parts of the HTML which doesn't have the same structure.

Linking it all back to Tailwind

If you are still reading (thanks!), the conclusion for this series is obvious: linking all of what we have discussed to Tailwind. Like mentioned at the start, this paradigm is not new; one can simply look towards existing frameworks and methodologies like OOCSS, BEM, and Atomic CSS for example. And looking at the reactions toward these frameworks, it is thus not surprising to find the same criticisms surrounding Tailwind.

The truth is, adhering to any framework (or to the principles discussed here) is a legitimate overhead, and one which demands developers to bear the upfront cost of. With a small project, the benefits are not immediately obvious, and may sometimes even be detrimental.

If you are looking into using (or have used) Tailwind, I certainly hope you are able to spot the clear parallels between OOP concepts, SOLID principles, and Tailwind. Tailwind describes itself as a utility-first CSS framework, which really just means that every utility class in Tailwind is designed to respect SRP, where each and every class is as independent and as loosely coupled as possible. If you look back at the last example given in the SRP section, where each class defined only has a single CSS rule and responsibility, we have essentially done up a small version of Tailwind (albeit with different class names).

In other words, Tailwind is actually just a framework which adheres strongly to the SOLID principles. Why bother coming up with your own CSS classes, trying your best to adhere to all the concepts and principles discussed here, when there is already a solid tool out there? This is not to say that once you use Tailwind, you're on your way to cleaner CSS however; there are still plenty of ways to mess it up.

So why the controversy?

As a Computer Science student, Tailwind clicked almost immediately for me, but it wasn't until recently that I found the link between Tailwind and OOP/SOLID. I've found that using Tailwind made my CSS much more maintainable, and for someone who didn't really like writing CSS, Tailwind actually made it enjoyable. So why is it that some people just really dislike it?

If I had to guess, I would say that the people most likely to write CSS are designers (or bootcamps graduates), and not necessarily programmers who are classically trained in Computer Science. Nothing against these people, but it is arguably difficult to understand OOP (we were all there once!) while adhering to the principles stated here. One reason, already discussed greatly by others, is that for someone writing CSS "their way" for all their lives, it takes a lot for them to break their habit and adopt a totally new framework. However, I would argue that if the framework is good enough (which in my opinion, Tailwind is), then there is no reason why someone would not want to change.

My theory is that those who dislike Tailwind are in one of these two groups:

  1. Those who cannot yet relate Tailwind to the OOP concepts and SOLID principles discussed in this article, and thus doesn't understand the benefits of Tailwind
  2. Those who can relate, but simply dislike the OOP way of doing things

If you are in the second group, then it explains why Tailwind is so controversial: because OOP itself is controversial. If you are in the first group, even though I didn't even touch on how to use Tailwind, hopefully this series has given you a deeper understanding of how and why Tailwind works in principle. So go ahead, try it out!

Further reading

Discussion (0)

pic
Editor guide