Just after publishing my previous post about TailwindCSS, I took part in yet another discussion about the framework over what I believe is another misconception: that TailwindCSS is somehow against the Don't Repeat Yourself (DRY) principle. This is far from the truth. The DRY principle, in my experience, is stretched beyond its meaning constantly. As per The Pragmatic Programmer, it means: “Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.” It never meant “every button must have only a single CSS class!”
The problem
If I gave you this single piece of code, could you tell me what's the minimum and maximum width of it?
<!-- button_component.html --> <button class="button">Press to DRY</button>
No, you can't. At least, not until you open your CSS file. And I won't write the CSS file here because that's exactly the point! You shouldn't need it! Stop depending on it!
Regarding web interfaces, the way to be DRY is by creating components. Components are reusable custom HTML elements. In the spirit of DRY, for any HTML element to be truly unique, you need more than just giving it a separate CSS class: you should be able to:
- have a default behavior and state;
- be able to extend the behavior as needed; and
- change its state.
The behavior here consists of all of its attributes and `class` is one of those. However, state can make your component behave differently, for example, disabling the input while the request is ongoing. But then, you might ask: why can't I extend this Button component with just two CSS classes that compress the dozen or more that would be necessary to have this styling done?
The cost of compression
Too much compression of information has a cost in code maintenance. That cost might be invisible if you are using VS Code or another powerful IDEs, but once you don't have those tools to assist you, you pay the price for compressing your code beyond what's healthy.
Let's say your team makes a Pull Request on GitHub asking you to review that Button component. It has a single class: `.button`. They said they changed the styling, it should look a lot different. The class is the same. TailwindCSS is the same. But now they decided that, in name of Separation of Concerns (SoC), they moved that class from `main.css` to `component.css`. Due to how Git works, you have no diff, unless you manually create one.
Let's say your team makes a Pull Request on GitHub asking you to review that Button component. It has a single class: `.button`. They said they changed the styling, it should look a lot different. The class is the same. TailwindCSS is the same. But now they decided that, in name of Separation of Concerns (SoC), they moved that class from `main.css` to `component.css`. Due to how Git works, you have no diff, unless you manually create one.
Locality of Behavior
If your Button component was styled straight into the component file itself from the beginning, most of that time would be saved. There'd be a real diff to compare, there would be one less file for you to check and you would know everything you need about the component without any IDE functionality.
That way, you'd adhere to the principle of Locality of Behavior (LoB). According to Richard Gabriel, in 1996, on his book Patterns of Software:
That way, you'd adhere to the principle of Locality of Behavior (LoB). According to Richard Gabriel, in 1996, on his book Patterns of Software:
Locality is that characteristic of source code that enables a programmer to understand that source by looking at only a small portion of it. Compressed code doesn’t have this property, unless you are using a very fancy programming environment.
In my opinion, LoB lies in the other side of the balance to SoC. They have nothing to do with code performance, and all of them (together with DRY) may lead to bad runtime performance. Don't be tricked into believing any of those decisions will make your code faster, but the humans involved in developing your software will be slower if you choose the wrong principle for the task at hand.
I would chose LoB in cases like this:
I would chose LoB in cases like this:
<button class="min-w-20 max-w-40 rounded-md bg-green-300 px-2.5 py-1.5 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50"> Locality of Behavior </button>
Truth be told, you will probably never look at this file again. The readability might be negatively influenced by the density of classes, but for the next however many years you use this component, you'll just include it in another file and never read it again.
That “other file”, however, may be a very important page that needs a bigger investment of time and resources that can result in the decision to structure it with SoC in mind. That's where the strength of SoC in frontend lies: breaking apart a very complex UI and making sure its building blocks are components.
Post Scriptum
A feedback I received after publishing was that I had not been clear enough on the scale of business that I was addressing here. I'm talking about applications that, as the company scales, can reach dozens or hundreds of user interfaces. From that scale onwards, I view components as an inevitable necessity. Below that scale, all you're doing with components is slowing your growth by fiddling with text files. You will know when to switch from a custom CSS class straight into a fully-fledged component that completely substitutes an HTML element.