最近有个叫《I Don't Like Tailwind. Sorry Not Sorry》的帖子在 dev.to 上很火,引发了一场 CSS 框架的口水战。我看完之后忍不住想说几句——不是因为我想蹭热度,而是因为这个话题真的戳中了什么。

Tailwind CSS 是近年来最火的 CSS 框架,GitHub 星标数破天际,教程满天飞,Twitter 上说 "学 Tailwind 吧" 几乎是政治正确的宣言。好像不用 Tailwind 就是古董,好像还在写传统 CSS 的人是不爱学习的落后分子。

但今天我要说点不一样的:我用过 Tailwind,用了大概八个月,在两个实际项目里。然后我把它删掉了。

这不是一时冲动。是我认真思考过后的结论。

先说清楚,我不讨厌的是什么

很多 Tailwind 支持者会说:"你讨厌 Tailwind,是因为你不了解它。"

这个逻辑很讨厌。说一个工具不好,不代表我没有研究过它。

我认真学过 Tailwind 的文档,看过他们的高级课程,读过源码里关于 JIT 模式的实现,在真实项目里用过它的响应式设计系统、dark mode 切换、自定义配置。我甚至用过 @apply 来在某些场景下复用样式。

所以我接下来的批评,是基于真实使用经验,而不是门外汉的臆想。

问题一:HTML 变得无法阅读

让我先给你看一段 Tailwind 代码:

<button class="inline-flex items-center justify-center px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white font-medium text-sm rounded-md transition-colors duration-150 ease-in-out shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed">
  提交
</button>

这段代码干的事很简单:画一个蓝色按钮。

你能快速告诉我这段代码做了什么吗?

反正我不能。一眼望过去全是utility class,没有任何语义。字体大小、padding、圆角、阴影、hover 效果、focus 状态——全部挤在一个字符串里。

现在对比一下传统 CSS 写法:

.btn-submit {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 8px 16px;
  background-color: #2563eb;
  color: white;
  font-weight: 500;
  font-size: 14px;
  border-radius: 6px;
  transition: background-color 0.15s ease-in-out;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}

.btn-submit:hover {
  background-color: #1d4ed8;
}

.btn-submit:focus {
  outline: none;
  box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.3);
}

.btn-submit:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
<button class="btn-submit">
  提交
</button>

HTML 现在是干净的。我可以一眼看出"这是一个提交按钮"。样式和结构分离了,各司其职。

Tailwind 的核心哲学是"结构即样式"——把样式直接写在 HTML 里。听起来很美好,但代价是 HTML 变成了一个巨大的样式清单。 当你需要修改按钮颜色时,你要去 HTML 文件里翻那一长串字符串;当设计师问你"所有次要按钮的字体大小是否统一",你没法grep,只能一个一个文件去找。

问题二:组件封装是个谎言

Tailwind 的支持者会说:"你可以用 @apply 或者组件化来复用代码。"

对,但这是后补的解决方案,而且补得很勉强。

// 创建一个 Button 组件
const Button = ({ children, variant = 'primary' }) => {
  const baseClass = 'px-4 py-2 rounded-md font-medium text-sm transition-colors duration-150'
  const variants = {
    primary: 'bg-blue-600 hover:bg-blue-700 text-white',
    secondary: 'bg-gray-100 hover:bg-gray-200 text-gray-900',
    danger: 'bg-red-600 hover:bg-red-700 text-white'
  }
  return <button class={`${baseClass} ${variants[variant]}`}>{children}</button>
}

嗯,这是一个 React 组件。但这里有个问题:你在 JavaScript 里管理 CSS。

这算什么?Tailwind 声称让你不需要写 CSS,但它只是把 CSS 的复杂度转移到了 JavaScript 里,然后给了你一个更难维护的版本。

你现在的状态是:CSS 的知识依然需要(因为你要理解 hover:bg-blue-700 这种语法),JavaScript 的知识也需要(因为你要写组件逻辑),但两者的边界已经模糊了——这不是进步,这是混乱。

问题三:自定义配置是另一个坑

Tailwind 的卖点之一是"完全可定制",通过 tailwind.config.js

但现实是:

// tailwind.config.js
module.exports = {
  content: ['./src/**/*.{js,jsx,ts,tsx}'],
  theme: {
    extend: {
      colors: {
        brand: {
          50: '#f0f9ff',
          100: '#e0f2fe',
          200: '#bae6fd',
          300: '#7dd3fc',
          400: '#38bdf8',
          500: '#0ea5e9', // 你的品牌主色
          600: '#0284c7',
          700: '#0369a1',
          800: '#075985',
          900: '#0c4a6e',
        },
      },
      borderRadius: {
        'btn': '6px', // 我们叫它 btn 为了复用
      },
      spacing: {
        'btn-padding-x': '16px',
        'btn-padding-y': '8px',
      }
    },
  },
  plugins: [],
}

然后你发现在 HTML 里:

<button class="bg-brand-500 hover:bg-brand-600 rounded-btn px-btn-padding-x py-btn-padding-y">
  品牌按钮
</button>

这还是 Tailwind 吗?这已经变成了另一种写 CSS 的方式——更糟糕的方式,因为它让你觉得你不需要学 CSS,但实际上你在用一种更难理解的 DSL(领域特定语言)。

当你的 Tailwind 配置比传统 CSS 还复杂的时候,你已经输了。

真实案例:我的一个后台管理系统

大约半年前,我接了一个小项目:一个内部后台管理系统,需求不复杂,主要是 CRUD 页面、表格、表单、一些简单的图表。

开始用 Tailwind 开发。前两周速度确实快——不需要写 CSS 文件,直接在 JSX 里堆 utility class,页面很快就出来了。

然后问题来了:

第三周,设计师改稿。 需要把所有按钮的圆角从 6px 改成 8px,把某个区块的背景色从 gray-50 改成 slate-50,把表格的斑马纹从 white/gray-50 改成 white/slate-50。

我打开了三个文件,每个文件里有 4-6 个地方需要改。每改一个,我都要确认我没有遗漏任何地方。

如果用传统 CSS,这件事就是:

/* 改一行 */
.table-row:nth-child(odd) {
  background-color: #f8fafc; /* slate-50 */
}

在 Tailwind 里,我需要:
1. 找到所有写 bg-gray-50 的地方
2. 判断哪些是表格斑马纹,哪些是其他用途
3. 逐一替换
4. 检查是否有没有覆盖到的地方

这不是 Tailwind 的错,但它的设计哲学让它在这种场景下变得非常痛苦。

最后我花了两天时间重构,把 Tailwind 全删掉,换成了传统的 CSS Modules。项目活了。

问题四:协作成本被低估了

团队里来了个新人,他 CSS 基础很好,但没用过 Tailwind。

第一天,我给他一个组件,让他"把主色调改成品牌色"。

他在组件里找 bg-blue-600,找不到——因为这个组件用的是自定义的 bg-primary,来自 tailwind.config.js 里的 theme.extend。

他要了解这个系统的配色体系,需要:
1. 读 tailwind.config.js
2. 理解 theme.extend 的结构
3. 找到对应的 class name
4. 确认是否有遗漏的地方

如果用传统 CSS,他只需要:
1. 打开 variables.css
2. 改一行 --color-primary

Tailwind 把 CSS 的复杂度隐藏起来,但当你要做全局性修改时,你会发现这个复杂度从来没有消失——只是换了个地方等着你。

我承认 Tailwind 有它的用武之地

我不能假装 Tailwind 一无是处。它确实解决了一些真实的问题:

  • 快速原型:做 Demo、PoC 时,Tailwind 确实快
  • 小型团队/个人项目:没有设计体系约束的场景下,Tailwind 可以省去很多决策成本
  • 设计系统不存在的项目:从零开始时,Tailwind 比先写一套 CSS 体系再写组件更省事

但问题是,这些场景在真实的中大型项目里很少出现。当你有一个认真的设计师、一套设计系统、一年以上维护周期的项目,Tailwind 的缺点就开始显现,而且越来越明显。

我的立场

我不反对 Tailwind。我反对的是把它当作银弹的言论,反对的是"所有项目都适合 Tailwind"这种非黑即白的论调。

Tailwind 是一个工具,一个解决特定问题的工具。它不是 CSS 的替代品,也不是前端工程的终点。

用 Tailwind 没问题。但不要因为它火就用它,不要因为别人说好就盲目跟。用它之前,先问自己:这个项目的规模是什么?团队构成是什么?长期维护的成本有多高?

如果你在一个三人小团队里做原型,用 Tailwind 很合理。
如果你在做一个要维护三年的企业级产品,慎重考虑。
如果你刚学前端,连 CSS 盒模型都没搞清楚,Tailwind 不会让你变成更好的开发者——它只会让你跳过那些本该经历的挫折,然后在你遇到第一个真正复杂的问题时,让你措手不及。


结尾说结论:我用过 Tailwind,不喜欢它。原因不是它不够好,而是它的好是有条件的,而那些条件在中大型项目里很难满足。

这不是道歉,这只是一个认真用过的开发者的真实看法。

封面图建议:一只手握着一支笔,正在试图把一段乱糟糟的英文字符串(代表 Tailwind 的 utility classes)划掉,旁边画了一个干净整洁的 CSS 代码块作为对比。整体风格:黑白线条简笔画,带一点讽刺感。