I Love Tailwind. Sorry, Not Sorry

昨天我写了一篇"我不喜欢 Tailwind"的文章,得到了不少反馈。今天我想写一篇"我喜欢 Tailwind"的文章,作为平衡。

不是因为昨天那篇文章错了。是因为那个观点太片面。

我用过 Tailwind,用过 CSS Modules,用过 Styled Components,用过 CSS-in-JS,用过普通 CSS。每个方案都有它的价值。今天这篇文章,是从"我喜欢 Tailwind"的角度,给出另一个角度的判断。

如果你昨天读了我的批评,今天请带着"这个人为什么喜欢 Tailwind"的心态读这篇。

我为什么喜欢 Tailwind

先说核心原因:Tailwind 让团队的前端开发效率提升了 3 倍

这不是我拍脑袋的数字。这是我们团队的真实数据。

我们团队有 6 个后端开发者和 2 个前端开发者。以前做新功能,前端开发者是瓶颈——后端 API 半天就能写完,但前端页面要排 2-3 天。

用 Tailwind 之后,前端页面的开发时间从 3 天降到了 1 天。后端开发者也能参与简单的前端开发了,因为 Tailwind 不需要懂 CSS——只需要知道 p-4 是 padding,text-xl 是 font-size。

这不是 Tailwind 的功劳,这是降低前端开发门槛的功劳。Tailwind 把 CSS 的门槛从"需要学习选择器、优先级、盒模型、flexbox、grid"降到了"知道 p-4 是什么意思就行"。

这个门槛降低,让团队协作更高效。

Tailwind 的真实价值:设计约束

批评 Tailwind 的人会说:"它把样式和内容混在一起"。

我的反驳:设计约束比"语义清晰"更重要

一个没有设计系统的团队,写出来的 CSS 是什么样的?

/* 各人写各人的,最后这个文件有 3000 行 */
.button-primary { background: #2563eb; }
.btn-submit { background: blue; }
.submit-btn { background: #0066FF; }
.primaryButton { background: #1E90FF; }

没有约束的 CSS,比 utility-first 更混乱

Tailwind 提供的约束是:颜色只能是这些,间距只能是这些,字体大小只能是这些。这不是限制,这是设计系统的基础

用 Tailwind 的团队,至少不会写出 10 种不同的蓝色来。

我用 Tailwind 解决的实际问题

问题一:快速原型迭代

我们有个内部工具,原来用 Bootstrap。Bootstrap 的问题是:所有的页面看起来都一样

用户反馈:"你们的工具看起来像个 2015 年的网站。"

我们决定改版。用传统 CSS 改版,需要:设计稿 → 写 CSS → 测试。至少 2 周。

用 Tailwind 改版,我们直接在 HTML 上调样式,一边调一边给用户看。1 周完成改版,用户满意。

这不是"不专业",这是快速验证价值

问题二:多人协作的样式冲突

我们团队有 6 个开发者,以前用普通 CSS,最大的问题是样式冲突

开发者 A 在 buttons.css 里写了 .btn { border-radius: 4px; }
开发者 B 在 forms.css 里写了 .btn { border-radius: 0; }
开发者 C 在 components.css 里写了 .btn { border-radius: 8px; }

最后按钮长什么样?取决于 CSS 文件的加载顺序。加载顺序变了,按钮的样子就变了。

用 Tailwind:

<button class="bg-blue-600 text-white px-4 py-2 rounded">
  Submit
</button>

每个按钮的样式都在这个 class 里,没有跨文件的冲突。样式内聚,冲突消失

问题三:响应式开发的效率

响应式开发用普通 CSS 是这样的:

.card {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

@media (min-width: 768px) {
  .card {
    flex-direction: row;
  }
}

HTML:

<div class="card">
  <!-- content -->
</div>

这个写法没问题。但问题是:如果我想改移动端的布局,我要去 CSS 文件里找到 .card,然后改 media query。如果这个 .card 在 20 个页面里用,我得改 20 个地方?不,不对,改 CSS 文件一处就够了。

但现实是:很多团队的 CSS 不是一个 .card 到处用,而是一个 .card 在不同的文件里重复定义

用 Tailwind:

<div class="flex flex-col gap-4 md:flex-row">
  <!-- content -->
</div>

响应式断点直接写在 HTML 里,一目了然。不需要在 CSS 和 HTML 之间来回跳转。

Tailwind 让我惊讶的几个场景

场景一:ChatGPT 生成的 UI

去年我让 ChatGPT 帮我生成一个 Dashboard UI。它生成了这样的代码:

<button onclick="submitForm()" style="background-color: #007bff; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer;">
  Submit
</button>

行内样式,14 行。我要改成设计系统的样子,要花 20 分钟重构。

如果我用 Tailwind,可以让 ChatGPT 帮我生成 Tailwind 版本:

<button class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700">
  Submit
</button>

这段代码我可以直接用,不需要重构。因为 Tailwind 的 class 是设计系统的一部分,颜色、间距、圆角都是预设的。

这就是AI + Tailwind 的组合价值:AI 生成的代码不用重构,直接可用。

场景二:维护别人的代码

我接手过一个老项目,Bootstrap 写的,5 万行 CSS。没有注释,没有文档,没有设计系统。

我需要改一个按钮的颜色。我怎么找这个按钮的样式?

  1. 用 Chrome DevTools 检查元素
  2. 找到 Applied Styles
  3. 一层层往上找选择器
  4. 有些是 Bootstrap 的,有些是自定义的,混在一起
  5. 找到 btn-primary 定义的位置
  6. 改了它,可能影响其他用 btn-primary 的按钮

用 Tailwind:

<button class="bg-blue-600 text-white px-4 py-2 rounded">
  Submit
</button>

我直接改这个 class:bg-blue-600bg-indigo-600。影响范围是这个按钮,清晰可控

场景三:Design Token 的应用

有人批评 Tailwind:"颜色选择太多,会导致不一致"。

这让我想到 Design Token 的概念

Tailwind 的 bg-blue-600 看起来是硬编码值,但实际上它是 语义化的blue-600 是品牌的蓝色,red-600 是错误色,green-600 是成功色。

如果你需要改品牌色,只需要改 Tailwind 配置:

// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      colors: {
        brand: {
          50: '#eff6ff',
          100: '#dbeafe',
          500: '#3b82f6',  // 这是主品牌色
          600: '#2563eb',  // 改这里,所有 bg-brand-600 都变
          700: '#1d4ed8',
        }
      }
    }
  }
}

改一处配置,整个应用的品牌色全变了。这比改 CSS 变量更方便,因为 Tailwind 提供了完整的语义化层级。

回答对 Tailwind 的批评

批评一:"HTML 变得臃肿"

我承认这一点。但我要说:这是开发时的权衡,不是用户的代价

用户看到的是加载后的网页,不是一行行 HTML 代码。开发者读的是 HTML + Tailwind class,这是开发效率的交换。

如果 HTML 臃肿让你不舒服,用 Extract Component 模式:

// 提取成组件
function Button({ children, variant = 'primary' }) {
  return (
    <button className={clsx(
      'px-4 py-2 rounded font-medium transition-colors',
      variant === 'primary' && 'bg-blue-600 text-white hover:bg-blue-700',
      variant === 'secondary' && 'bg-gray-200 text-gray-900 hover:bg-gray-300',
    )}>
      {children}
    </button>
  )
}

// 使用
<Button variant="primary">Submit</Button>

HTML 保持整洁,样式在组件里封装。这不是 Tailwind 的问题,这是组件设计的问题。

批评二:"HTML 和 CSS 混在一起"

这是设计哲学的问题。

传统的 Web 开发:HTML 是结构,CSS 是表现

这个分离在 1998 年是对的,因为那时候 HTML 和 CSS 是不同的人写的:HTML 作者写内容,CSS 作者写样式。

今天的前端开发:一个人同时写 HTML 和 CSS,组件既是结构也是表现。

组件时代的开发,HTML + CSS 放在一起是自然的。Tailwind 只是把这一点做到了极致。

如果你坚持 HTML 和 CSS 分离,Vue SFC 或 React 的 CSS-in-JS 适合你。
如果你接受 HTML + CSS 放在一起,Tailwind 适合你。

这不是对错,是开发模式的选择

批评三:"学 Tailwind 要学另一套东西"

这是真的。但我要说:Tailwind 学起来比 CSS 快

CSS 的学习曲线:
1. 选择器(class、id、tag、组合)
2. 优先级(specificity、!important)
3. 盒模型(margin、padding、border、content)
4. Flexbox
5. Grid
6. 响应式(media query)
7. 动画(@keyframes)
8. 变量(CSS Custom Properties)
9. 预处理器(Sass/Less)
10. 后处理器(PostCSS)

加起来要学的东西很多,而且很多概念是相互关联的。

Tailwind 的学习曲线:
1. 基础语法:p-4 = padding: 1rem
2. 响应式前缀:md:p-4 = @media (min-width: 768px) { padding: 1rem }
3. 变体:hover:bg-blue-600
4. 组件模式:提取成组件

4 个概念,能解决 80% 的问题

剩下的 20%(自定义样式、CSS 动画)可以学标准 CSS,不需要全学。

Tailwind 在大型项目里的实践

我们用 Tailwind 做了一个 SaaS 产品,2 年,代码量 10 万行。

怎么保持一致性

我们有 tailwind.config.js,定义了:
- 品牌色(brand-*
- 间距系统(space-*
- 阴影(shadow-cardshadow-modal
- 圆角(radius-smradius-mdradius-lg

开发者不允许直接用 Tailwind 的原始值(如 bg-blue-600),必须用我们的设计 token(如 bg-brand-600)。

这样既利用了 Tailwind 的便利性,又保证了设计一致性。

怎么组织代码

// components/ui/Button.tsx
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
  ({ variant = 'primary', size = 'md', className, children, ...props }, ref) => {
    const baseClasses = 'inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2'

    const variantClasses = {
      primary: 'bg-brand-600 text-white hover:bg-brand-700 focus:ring-brand-500',
      secondary: 'bg-gray-100 text-gray-900 hover:bg-gray-200 focus:ring-gray-500',
      ghost: 'text-gray-700 hover:bg-gray-100 focus:ring-gray-500',
    }

    const sizeClasses = {
      sm: 'px-3 py-1.5 text-sm',
      md: 'px-4 py-2 text-base',
      lg: 'px-6 py-3 text-lg',
    }

    return (
      <button
        ref={ref}
        className={clsx(baseClasses, variantClasses[variant], sizeClasses[size], className)}
        {...props}
      >
        {children}
      </button>
    )
  }
)

所有组件封装好了,用的人不需要知道 Tailwind 写了什么。用 <Button variant="primary"> 就够了。

怎么做 Code Review

Tailwind 的 code review 跟普通 CSS 不一样。

普通 CSS review 问:"这个样式放在哪个文件里?"
Tailwind review 问:"这个按钮的 variant 是 'primary' 还是 'secondary'?"

Review 的是组件使用,不是样式细节。样式细节在组件定义里,review 一次就够了。

我的结论

Tailwind 是一个工具,工具的价值取决于用它的人。

用 Tailwind 写出混乱代码的人,不用 Tailwind 也会写出混乱代码,只是混乱的方式不一样。

用 Tailwind 写出高质量 UI 的团队,是因为他们知道什么时候用 Tailwind,什么时候不用,什么时候需要封装组件

Tailwind 适合我,因为它:
1. 降低了团队的前端开发门槛
2. 提供了设计约束,让我不用担心"10 种蓝色"的问题
3. 让 AI 生成的代码直接可用
4. 响应式开发效率高

Tailwind 不适合你,如果你:
1. 追求 HTML 的绝对语义化
2. 有复杂的 CSS 动画需求(Tailwind 的动画支持有限)
3. 团队里有人坚决不喜欢 Tailwind(强行推广会有反效果)

这不是对错,是适合不适合的问题。

我喜欢 Tailwind,就像我喜欢 Go 一样。它们都是"做减法"的设计哲学:去掉你不需要的,留下你真正需要的。


如果你用过 Tailwind 并且喜欢它,这很正常。如果你用过并且不喜欢,也很正常。观点不同,互相尊重。