我不推荐使用 Tailwind CSS。
I don't recommend Tailwind CSS

原始链接: https://en.andros.dev/blog/af3ee191/why-i-dont-recommend-tailwind-css/

尽管 Tailwind CSS 是快速开发 UI 和标准化设计系统的热门框架,但它并非万能方案。本摘要概述了开发者在针对中大型项目采用该框架前应保持谨慎的关键理由。 **主要批评:** * **复杂性与学习曲线:** 掌握数千个工具类会形成一种“天花板”,迫使开发者频繁查阅文档,往往阻碍了对 CSS 基础知识的深入理解。 * **可读性与维护性:** Tailwind 可能导致“类名混乱(class soup)”,使 HTML 变得臃肿,并违背了关注点分离原则。虽然组件化框架(如 React/Vue)可以缓解这一问题,但在服务端渲染的项目中,这仍然是一个痛点。 * **不一致性与抽象泄露:** 命名规范可能不够直观(例如对齐类),且任意值的使用削弱了该框架对严格设计系统的承诺。此外,开发者还必须面对“层叠”问题,即 HTML 类名的顺序并不决定样式的优先级,这增加了调试难度。 * **现代 CSS 的替代方案:** 随着原生嵌套、层叠层(cascade layers)和容器查询等特性的出现,现代 CSS 比以往强大得多,往往使额外的工具层显得多余。 **结论:** Tailwind 是一个强大的工具,但开发者应先精通原生 CSS,以便做出明智的架构决策。

最近 Hacker News 上一篇题为“我不推荐 Tailwind CSS”的文章引发了开发者关于现代 CSS 框架与传统开发方式孰优孰劣的讨论。 各方观点不一: * **Tailwind 支持者:** 一些资深开发者表示,他们起初持怀疑态度,但在使用后发现该工具不可或缺,并称赞它在团队协作中表现出的高度一致性和易维护性。 * **传统派:** 另一些人则认为 Bootstrap 4 等旧方案依然功能完备,他们倾向于使用简单直观的类名,并认为比起追逐“新潮流”,保持现有工作流的效率更为重要。 * **批评者:** 部分用户对 Tailwind 的泛滥感到不满,希望它不要被强行集成到 UI 框架中;也有人指出,只要界面功能完备且易于阅读,终端用户往往并不关心底层的技术栈。 归根结底,这个话题反映了开发者社区中普遍存在的矛盾:在采用流行的现代工具与坚守成熟可靠的系统之间,如何寻找平衡点。
相关文章

原文

Tailwind CSS is a very popular CSS framework among web developers. With no prior graphic design knowledge, it solves the standardization of spacing, colors, and sizes for you. And on top of that, if you are even slightly disorganized, it forces you into a working methodology based on building large structures out of simple utilities (Utility-First Fundamentals). Add to all that a documentation that is easy to understand with a flawless search.

In the past I used it on several projects, it helped me build user interfaces quickly. And despite all of that, it is a tool I would not recommend in every context. I am going to give you my point of view, my reasons, why you should think twice before using Tailwind CSS on medium or large projects. The only thing I ask of you is an open mind, self-criticism, and a willingness to learn.

You have to learn dozens of classes

Tailwind has thousands of utility classes. Even though the documentation is very good and the editor autocomplete helps a lot, there is a ceiling of basic classes you have to internalize. Most likely, at first you will develop with a browser tab open and the search box working nonstop. You spend time understanding the most appropriate way to apply each class. A slow process at the start. And if you have created your own classes that mix with Tailwind's, the difficulty grows.

It breaks the separation between structure and design

Tailwind CSS is a gigantic utility library, atomic classes with specific jobs. Unless you group classes with @apply, you end up including dozens of them in your HTML. This breaks the classic rule of separating structure from design, or HTML from CSS. It bloats the HTML, readability suffers, and you lose reusability.

That said, this one is worth pausing on, because it is the criticism that gets rebutted the most. Adam Wathan, the creator of Tailwind, wrote the foundational text of the defense, and his argument is a good one. The separation of concerns does not disappear, it changes direction. With "semantic" CSS, your CSS depends on the HTML; you can restyle the HTML, but your CSS is not reusable. With utilities, your HTML depends on the CSS; the CSS is reusable, but your HTML does not restyle itself. The coupling exists in both cases, only the direction of the arrow changes.

The argument holds in a world of components, where React or Vue already put logic, markup, and style in the same file. There, the separation by file type was already broken by you beforehand. But in a server-rendered project, with templates and classic CSS, the separation still makes complete sense. It is not a universal law, it is a decision that depends on your architecture.

Its names are not consistent

Its naming is not always coherent, at times it is opaque and tends to confuse. Look at the alignment classes. What is the difference between items-center, justify-center, text-center, and place-content-center? The name is not intuitive and does not follow a clear pattern. In case you are curious, items-center aligns items in a flex container, justify-center aligns them on the main axis, text-center centers text, and place-content-center centers content in a grid. Let me rename them looking for coherence: flex-align-items-center, flex-justify-content-center, text-align-center, and grid-place-items-center. Do you notice the difference?

There are more examples: justify-center but items-center (not align-center); border gives 1px but border-2 gives 2px. Small frictions that add up.

The design system is not enforced as much as it seems

One of the big arguments in favor of Tailwind is that its spacing and color scale forces you to be consistent. And that is true, but with a catch, the arbitrary values. You can write w-[347px], text-[#1a2b3c], or p-[5.5rem] without anything warning you. They are an escape hatch that breaks the system. Nothing stops you from using sky-400 and blue-400 in the same project. Consistency still depends on your discipline, not on the framework. The system helps you, but it does not save you from yourself.

It is not a good gateway to learning CSS

Tailwind is easy to use if you already master styling. But for those with little CSS experience, it creates a false sense of learning. It conveys that it is a minimalist way of writing CSS, taking away the effort of keeping your code tidy. Using so many pre-built classes hinders a developer's ability to truly master CSS. The time you save naming classes you lose learning to use the framework in the most efficient way.

On the other hand, if you go deep into Tailwind, you drift away from learning the fundamentals of the platform. pt-4 forces you to mentally translate to padding-top: 1rem. You gain fluency in the abstraction, not in CSS. To my students and coworkers I recommend learning CSS before Tailwind. And if you are already an experienced developer, you might not even need the framework.

It is hard to read

Let me show you a button with an indigo background, white text, semibold font, rounded corners, hover, and focus.

First in vanilla CSS. I will take the liberty of using variables for colors and spacing, and nesting for hover and focus, something compatible today with every browser.

:root {
  --color-white: #fff;
  --color-black: #000;
  --color-indigo: #4f46e5;
  --color-indigo-hover: #4338ca;
  --gap-s: 0.5rem;
  --gap-m: 1rem;
}

.button {
    padding: var(--gap-s) var(--gap-m);
    background-color: var(--color-indigo);
    color: var(--color-white);
    font-weight: 700;
    border-radius: var(--gap-s);
    &:hover, &:focus {
        background-color: var(--color-indigo-hover);
    }
}

Now in Tailwind CSS using @apply.

.button {
    @apply py-2 px-4 bg-indigo-500 text-white font-semibold rounded-lg hover:bg-indigo-700 focus:bg-indigo-700;
}

Vanilla CSS is self-explanatory, whereas with Tailwind you have to check the documentation. What color is bg-indigo-500? How round is rounded-lg? Is bg-indigo-700 darker than bg-indigo-500? The first snippet is easier to maintain, the learning curve for a new developer is low, and the CSS tends to be modular. You do put in more effort with vanilla CSS, yes, but only at the start. Afterward you have full control of variables, cascade, and naming.

A curious detail. The example above uses @apply, and Adam Wathan himself has admitted that @apply "basically only exists to trick people" and that if he started Tailwind from scratch he would not include it. In other words, the route you use to recover the readability of a normal class contradicts Tailwind's philosophy. And if you do not use @apply, then you swallow the class soup in your HTML.

The HTML lies to you about priority

You may have heard that Tailwind "ignores the cascade". That is an imprecise way to put it, because the real problem is more subtle and interesting.

Being able to control which selector wins based on its position in the stylesheet is fundamental to any architecture. A typical mistake among inexperienced web designers is to believe that the order of the classes in the HTML determines which one wins. It does not. Look at this code.

<p class="color-red color-green">I am some text</p>

What color is it? You do not know. You need to see the CSS cascade.

.color-green {
    color: green;
}

.color-red {
    color: red;
}

Now yes, it is red, because .color-red is defined later. So far, normal CSS.

The problem with Tailwind is that you cannot resolve this ambiguity even by reordering your classes. Watch.

<p class="text-red-500 text-green-500">I am some text</p>

You might think green wins, since it comes last in the attribute. Well, no, you get red. What wins is decided by the order in which Tailwind generates those classes in the final stylesheet, not the order in which you write them in the HTML. The markup lies to you. Reading the HTML you cannot know the result. And you do not fix it by changing the order of the classes, because that order is controlled by the compiler. You are left with !important or with avoiding conflicting utilities. This is what is called a leaky abstraction. It promises you simplicity and forces you to know how it works underneath. Another classic case, class="mt-4 mt-0" does not do what its order suggests.

Working with Dev Tools is not comfortable

When you build components with tons of classes, checking in the inspector which styles apply becomes slow. You have to scroll looking for what wins and what gets overridden. With vanilla CSS you quickly see which styles are there and in what order, and an error is easier to locate. It is true that editor extensions ease this at writing time, but in the browser, debugging in production, the class soup is still there.

Conclusion

What if you just use modern CSS?

Here is, for me, the truly fresh debate. It is no longer "utilities versus CSS by hand". It is "native modern CSS versus a utility layer on top".

Look at what the platform gives you today, without installing anything:

  • Cascade layers (@layer) to order priority without fighting specificity.
  • Native nesting, now without a preprocessor.
  • Custom properties (CSS variables) for your tokens.
  • :has(), the parent selector we had been asking for over twenty years.
  • Container queries for per-component responsiveness.
  • color-mix() to derive colors.

And you know what the ironic part is? That Tailwind is built right on top of those same features. It uses native cascade layers, @property, container queries, and OKLCH colors. Which gives ammunition to the most uncomfortable argument. If the platform already brings all that, how much of Tailwind is still essential?

The counterpart is clear. Native CSS gives you the features, but it does not give you the system of constraints, nor the style kept next to the markup (colocation), nor autocomplete. On small or server-rendered projects, modern CSS on its own is a great option. On a large product built with components and with many people touching it, Tailwind still has arguments. There is no universal winner, there is a context.

But let us put things in perspective. It is not a bad tool. It is a tool with a cost you pay in the future, and one that nowadays also competes with a native CSS far more capable than a few years ago. Before choosing it, learn CSS. Then decide with knowledge. The initial effort you save today, you may pay back with interest tomorrow. Or maybe not. Now, at least, you have both sides to decide for yourself.

Sources

联系我们 contact @ memedata.com