The Limits of Tailwind CSS
Tags: css, tailwindcss, discuss, webdev
Published: 2023-09-25 19:51:19

Tailwind, like everything in life, has its pros and cons. It's important for an ecosystem to have a good balance of criticism and praise in order to maintain a process of continuous improvement and providing its users with diverse opinions that enhance their hability to choose whether to use it or not.

In this article, I will treat Tailwind as a tool, not as a deity, a problem, or a definitive solution. What I hope for is that this article helps in the decision-making process of applying Tailwind to a project from the perspective of application architecture, taking into consideration its pros and cons, rather than purely individual and personal feelings like "DX" (Developer Experience) or "ease of use."

> This article expresses my humble opinion based on my personal technical judgment only. How I feel about Tailwind or its community was secondary to me when writing this article, and I hope the same from the people who will read it.

<br/>

---

<br/>

## What is Tailwind CSS

![Homepage of the Tailwind website. The slogan "Rapidly build modern websites without ever leaving your HTML" in uppercase letters.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mubzzyvb0i8lfw1vyk3l.png)

[Tailwind](https://tailwindcss.com/) is a utility-first CSS framework. It's a **framework** because it's not a standalone utility but rather a small, intricate ecosystem built around a design system. It's **utility-first** because it provides a set of single-responsibility classes that bring styles with a strong sense of design, minimizing the design decisions that a developer might otherwise have to make, which can sometimes impede the development process.

<br/>

---

<br/>

## Positive Aspects

More than just a CSS framework, Tailwind is a PostCSS plugin. This is because its biggest benefits are the generation of Just-in-Time (JIT) classes and the ease of purging, prefixing, and other optimization and compatibility tools that come right out of the box.

**JIT (Just-in-Time)**
Just-in-time was introduced in Tailwind v2.1 and is the ability to generate styles on demand. By specifying the style entry points in your configuration file, Tailwind only generates the styles that are declared in your template files (HTML, JSX, etc.), ensuring that unused classes don't exist in production.

**Purging**
Purge is the removal of unused CSS. The purge algorithm has to take into account a series of factors, which can make the removal process challenging for dense CSS code or CSS with conditional logic injected via script. Tailwind restricts various CSS idiosyncrasies and keeps the user within its token and class ecosystem, providing full predictability for PurgeCSS](https://purgecss.com/) to safely remove unused code.

**Optimization and Compatibility**
Tailwind uses [minification and compression](https://v2.tailwindcss.com/docs/optimizing-for-production) to deliver optimized CSS bundles. These are strategies that can and should be applied to any CSS approach that doesn't have them by default. Tailwind utilizes [Gzip and Brotli for compression](https://v1.tailwindcss.com/docs/controlling-file-size) and [CSSnano for minification](https://tailwindcss.com/docs/optimizing-for-production).

**DX (Developer Experience)**
Developer experience is the quality of minimizing friction in the development process, making the programming experience smooth and almost "effortless." Thanks to its ecosystem of plugins, presets, and extensions, it's possible to rapidly build interfaces with code that autocompletes with the available classes, almost like typed code. Tailwind also offers extensive and visually rich documentation, along with many community-created solutions and abstractions.

<br/>

---

<br/>

## When to Apply Tailwind CSS to My Project?

Tailwind is extremely useful for projects that have either no design system or one that is simple or minimally opinionated. It's perfect for projects compatible with uncomplicated CSS and low maintenance, and it's ideal for front-end teams with little knowledge of CSS or limited resources allocated for CSS architecture.

If you're unsure about the terms "design system" or "design tokens," storybook has a nice article about it.

{% embed https://storybook.js.org/tutorials/design-systems-for-developers/ %}

&nbsp;

When I talk about applicability, I'm not saying it's impossible to use it in a large-scale application or one with design systems, but when you do, the team will deal with many more points that Tailwind doesn't cover, leading to the creation of abstractions, patches, and extra complexity.

> 🧐 If you have good examples of an application meeting these parameters that worked well with minimal complexity, please share them; I'd love to learn about them!

<br/>

---

<br/>

## Negative Points

I'm greatly bothered by those who criticize anything without using it or based on frivolous criteria. To write this article, I tested Tailwind in two ways - I created an application to test complex UI algorithms, and I read the source code of two popular frameworks that use Tailwind, [shadcnUI](https://ui.shadcn.com/) and [DaisyUI](https://daisyui.com/).

I made this choice because one of them directly uses Tailwind classes, while the other abstracts them into unique classes using the `@apply` directive, taking advantage of its design tokens.

![A person smoking a cigarette being compared to vaping, in parallel, inline CSS code being compared to Tailwind classes.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pccyu66bxfakpdjaveon.png)
<small>I was just kidding, folks!</small>

<br/>

---

<br/>

## Existing Criticisms

Adam Wathan [has addressed one of the recurring criticisms of Tailwind, which is the separation of concerns](https://adamwathan.me/css-utility-classes-and-separation-of-concerns), maintaining a code [separation of responsibilities](https://en.wikipedia.org/wiki/Separation_of_concerns#:~:text=in%20the%20other.-,Origin,the%20role%20of%20scientific%20thought%22.) for each part of the application. While this separation can bring benefits like decoupling, maintainability, and modularization, it remains an opinion of some writers within specific contexts and should be taken into consideration, **not treated as an absolute law or axiom**.

There is little measurable evidence of benefits from separating HTML and CSS beyond the file level. Even at the file level, [it's a good performance practice to place critical CSS inline within a `<style>` tag](https://web.dev/extract-critical-css).

HTML that appears illegible to those who don't like or work with Tailwind is actually one of the fundamental premises for teams working with this framework. If you split your templates into pages and components, you need to revisit the templates less often and deal with smaller code segments. Eventually, you become accustomed to it or install a linter.

In my view, the root problem precedes the issue of ugly templates. The problem lies in the structural failure of style reuse.


<br/>

---

<br/>

## Style Reusability
<small>This passage primarily considers the ["Reusing Styles" chapter from the official documentation.](https://tailwindcss.com/docs/reusing-styles)</small>

Tailwind is incapable of handling code repetition on its own. Its methods for dealing with this issue are highly tied to the IDE or to libraries and frameworks for templating or components, such as React, Vue, or even vintage ones like [Nunchucks](https://github.com/mozilla/nunjucks).

**Editing via Multiple Cursors in the IDE**

A viable solution, but it assumes the use of an IDE and a specific feature that has low accessibility and is impractical to use across documents (through global find and replace) because it can select classes beyond the intended scope. However, this can be addressed by locating these scopes within components.

**Extracting and Componentizing**

Components can be easily reused, but they still involve a form of coupling that assumes at least a rudimentary form of templating and post-processing. Of course, it's possible to use Tailwind in vanilla projects, but not without losing the benefits of JIT (Just-in-Time), compression, and directives whose support comes entirely from PostCSS. Without PostCSS and processing, Tailwind's most significant qualities would not survive. Even in a context where components are used, CSS provides various reuse tools, such as non-utility classes and selectors. Tailwind can only be leveraged through the `@apply` directive.

**@apply Directive**

This directive exists because it's the easiest way to create classes that still utilize Tailwind's design system. It's possible, for example, to use Tailwind's design tokens because it uses [CSS variable composition](https://adamwathan.me/composing-the-uncomposable-with-css-variables/) but doesn't expose them as an API, doesn't document them, doesn't JIT them as standalone, and lacks autocomplete, which can compromise the integrity of Tailwind's design system. Not to mention that Adam himself acknowledges the excess complexity and prominence of bugs.

{% tweet 1559250406290636801 %}

&nbsp;

Again, you may be okay with this, especially if you work within an ecosystem that aligns with all these requirements. Understanding this limitation also means understanding when to make the most of it when it becomes necessary.


<br/>

---

<br/>

## Tied CSS

Much of Tailwind's Developer Experience (DX) is due to the abstraction of fundamental CSS concepts such as compound selectors, CSS units, CSS functions, inheritance, and specificity. While these concepts may indeed be complex, they also enable the creation of concise, robust, retro-compatible CSS with room for progressive enhancement.

I've created three cases with varying complexities to demonstrate this.

### Holy Grail Layout in Josh Comeau's Approach

<small>Inspired by [Josh Comeau's article "Full-Bleed Layout Using CSS Grid - An elegant solution to a tricky modern layout."](https://www.joshwcomeau.com/css/full-bleed/)</small>

![A responsive layout with a header and footer and content in a central column.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ovhq3jzwtalrl6bwdya2.png)

This layout, when applied responsively without media queries, can be achieved with just 5 lines of CSS, 8 if you don't use native nesting:

```css
.grail {
  display: grid;
  grid-template-columns: 1fr min(var(--width, 800px), 100%) 1fr;
  & > * { grid-column: 2; }
}
```

&nbsp;

Of course, there are other ways to achieve this layout, but they involve more CSS - media queries to control the side widths based on device width or more HTML by creating containers and wrappers using the same logic as the [Bootstrap Grid](https://getbootstrap.com/docs/5.0/layout/grid/)..

In other words, to produce the same layout, you need to introduce unnecessary complexity, with the sole benefit being "development experience."

You write "less CSS," and you don't need to know complicated grid algorithms, but you end up with m**ore templates to process, test, and debug**. While utility classes are easy to debug because they contain only one property, pages or templates can easily turn into a wrapper hell when the template assumes the responsibility that styles should provide.

**Arbitrary Values**
In the latest version, it's possible to create a class with these parameters on-demand using [the arbitrary values API](https://tailwindcss.com/docs/adding-custom-styles#using-arbitrary-values).

```html
<section className="grid w-full grid-cols-[1fr,min(var(--width),100%),1fr] place-items-center gap-y-4 [&>*]:col-start-2">
   <!-- ... -->
</section>
```

But the limitations are clear:

1. **It's only possible if you use a very specific syntax.** The syntax is not simple and doesn't support spacing, making it easy to introduce bugs and very difficult to identify them.

2. You can use selectors like [&>*], **but they only accept one parameter at a time, and the generated code is practically unreadable**.

```css
/* Output da classe [&>*] */
.\[\&\>\*\]\:col-start-2>* {
  grid-column-start: 2;
}
```

&nbsp;

> "But the output doesn't matter"

You may not care about the output, but the browser doesn't care which tool you're using. If there are errors or inconsistencies, you'll face a devTools with dozens of classes, each with its encapsulated context and others with unreadable declarations.

<br/>

---

<br/>

## Stay tuned, there will be a part two

There's a lot more to be said and examples to explore, so I'll create a second part analyzing another application of advanced CSS, whether Tailwind actually produces less CSS, the issue of inheritance and specificity, and Tailwind's interaction with other frameworks and foreign code.

I'd love to hear your opinions up to this point. I challenge you to set aside personal biases and preferences because, personally, I've learned a lot technically by examining this tool objectively, and I hope the same happens for you. Don't be like Adam: 

{% twitter 1390663794985250816 %}