为什么本博客选择 Tailwind CSS:更适合 AI 开发的前端方案

为什么本博客选择 Tailwind CSS:更适合 AI 开发的前端方案

Tailwind CSS 大概是近年来争议最大的前端工具之一。有人觉得它让 HTML 「脏得没法看」,有人觉得它是 CSS 的终极形态。我用了一年多,从怀疑到依赖,写一点自己的真实感受。

我为什么离开手写 CSS

之前的博客用的是手写 CSS + BEM 命名。很快遇到了几个问题:

命名消耗心力。 每写一个新组件,就得想一个全局唯一的类名。.card-wrapper.card-container 谁包谁?.section-title--large 还是 .section-title-large?这些决策和 UI 逻辑毫无关系,纯粹是命名系统的熵增。

CSS 只增不减。 删组件的时候从来不敢删 CSS——谁知道那个 .sidebar__link-active 有没有被别处引用?几个月下来,CSS 文件里三分之一是死代码。

上下文切换。 写一个按钮样式要在 HTML 和 CSS 两个文件间反复横跳。HTML 里写 class="btn-primary",然后切到 CSS 里看这个类到底干了什么。心智负担不大,但累积起来很烦人。

Tailwind 解决的不是「怎么写 CSS」,而是「怎么写更少的心智开销」。

Utility-First 不是语法,是约束

Tailwind 的核心不是那一堆 bg-blue-500p-4。核心是一套预定义的、有上限的设计 token

传统 CSS 的自由度是无限的。你可以写 padding: 13pxfont-size: 17.5pxcolor: #1a2b3c。这种自由在个人项目里是灾难——你会无意识地引入不一致。

Tailwind 的 spacing scale(以 0.25rem 为单位步进)、color palette(每色 10 级)、font size scale——本质上是一个强制的设计系统。你不需要「遵守规范」,因为压根没有不规范的选择。

/* 手写 CSS:自由但容易失控 */
.card { padding: 13px; }
.section { padding: 16px; }
.banner { padding: 14px; }

/* Tailwind:只有有限选项,强迫一致 */
p-3  /* 0.75rem */
p-4  /* 1rem   */
p-5  /* 1.25rem */

这恰恰是个人项目的痛点——没有设计师,没有 design system,全靠自己凭感觉。Tailwind 替你把”感觉”变成了”规则”。

实际项目中的体会

Misaka 这个博客主题完全用 Tailwind v4 构建。v4 最大的变化是去掉了 tailwind.config.js,改用 CSS-first 配置——在 CSS 里用 @theme 定义 token。

项目的 global.css 实际长这样:

@import "tailwindcss";
@plugin "@tailwindcss/typography";

@theme {
  --color-misaka-dark: #1e293b;
  --color-misaka-circuit: #4ade80;
  --color-misaka-light: #f0f8ff;
  --color-misaka-blue: #00bfff;
  --color-misaka-accent: #38bdf8;
  --color-misaka-bg: #0f172a;
  --color-misaka-gray: #64748b;
}

之后所有组件直接用 text-misaka-bluebg-misaka-darkborder-misaka-circuit,和用 Tailwind 内置类一样的体验。不需要在组件里 import 任何东西,不需要 CSS 变量到处传。设计 token 和 utility class 是同一个东西——这是 Tailwind v4 最大的设计进步。

Dark mode 也极大简化。项目用两层机制::root 定义深色模式(默认)的 CSS 变量,:root:not(.dark) 覆写为浅色值,组件里直接用 dark: 变体处理边界情况。主题切换只是一个 document.documentElement.classList.toggle('dark'),没有复杂的 CSS 覆盖层级问题。

对常见质疑的看法

“HTML 太长了。” 确实长。但 Gzip 对重复字符串的压缩效率极高——你写了 50 次 flex items-center gap-4,压缩后可能只占几个字节。而且没有额外的 CSS 文件,HTTP 请求数反而少了。实际对比下来,总体积通常更小。

“这不就是 inline style 吗?” 完全不是。inline style 不能写 media query、不能写 hover/focus 状态、不能利用 CSS 变量。Tailwind 的 md: 前缀和 hover: 前缀,本质上是把 CSS 的能力完整映射到了类名空间。

“学 Tailwind 等于学另一门语言。” 有一定道理。但 Tailwind 的类名和 CSS 属性是一一映射的:p-4 = padding: 1remflex = display: flex。这不是新语言,这是 CSS 的别名系统。真正需要学习的是设计 token 的尺度感——什么场景用 p-4 而不是 p-6——而这本身就是设计能力,和你用什么工具无关。

什么时候不该用 Tailwind

Tailwind 不是银弹。以下场景我不推荐:

  • 高度定制的动画/特效。 Tailwind 的 transition-*animate-* 足够日常使用,但复杂的关键帧动画还是手写 CSS 更直观。
  • 需要和设计师紧密协作的项目。 如果设计师给你具体的 px 值,Tailwind 的固定 scale 会让你很难受。不过 Tailwind v4 的 arbitrary value(w-[137px])缓解了这个问题。
  • 不需要频繁迭代的小型项目。 三五个页面、没有复杂布局的网站,手写 CSS 更轻量。

为什么 Tailwind 更适合 AI 辅助开发

上面的讨论是从一个人类开发者的视角出发的。但过去一年,我的开发流程发生了根本变化——超过一半的新代码是 AI 生成的,前端几乎全部由 Claude Code 完成。在这个新范式下,Tailwind 的优势被进一步放大。

AI 的 Token 偏好

LLM 本质上是 token 预测引擎。它们不”理解”代码,但极其擅长模式匹配。Tailwind 的 utility class 恰好是 LLM 最擅长的输入形式:

原子 token。 flexjustify-centerp-4bg-blue-500。这些是离散的、高度可预测的字符串 token。因为 Tailwind 在 GitHub 上有海量代码示例,LLM 在训练数据中见过这些 token 组合数百万次。它们精确知道 flex items-center gap-4 应该产生什么效果。

而传统 CSS 需要 LLM 同时处理两套心智模型:HTML 结构和 CSS 规则。class 名是自定义的(.card-wrapper__header--dark),定义散落在另一个文件中,LLM 必须跨越文件边界追踪依赖。每多一个间接层,出错率就上升一截。

不用起名。 命名是计算机科学里最难的事,对 LLM 也是。写传统 CSS 时,LLM 需要编造一个语义化的 class 名——我们见过太多 .card-wrapper-inner-container-div。Tailwind 直接绕过了这个问题:class 名就是样式值,不需要”命名”这个环节。

上下文窗口效率。 LLM 的上下文窗口是有限且昂贵的。Tailwind 把样式和结构放在同一个元素上,LLM 不需要在一个文件里读结构、去另一个文件找样式。处理一次编辑请求涉及的文件越少,准确度越高。

这是社区已经形成的共识。Marcus Reynolds 在其文章 “Why LLMs are Addicted to Tailwind” 中总结了三个技术原因:上下文窗口效率、token 可预测性、消除命名负担。Phillip Lovelace 在 “When the Autocomplete Changes Its Mind” 中指出了这个自强化循环:LLM 因为训练数据中 Tailwind 最多而擅长它 → 开发者因为 LLM 擅长它而选 Tailwind → 更多 Tailwind 代码进入训练数据 → LLM 更擅长它。选择 Tailwind 不再只是一个审美问题——“因为 AI 写 Tailwind 比写其他方案更准”正在成为一个工程决策。

自定义 token 的问题与对策

但这不意味着 Tailwind + AI 没有问题。AI 最大的问题是幻觉自定义 token

比如我项目中用了 text-misaka-blue(CSS @theme 中定义的自定义颜色),但 AI 可能自信地写出 text-blue-400bg-brand-primary——这些类在我的项目中不存在,但在 Tailwind 默认配置中存在,或者在别的项目的训练数据中见过。

解决这个问题需要让 AI 能看到项目的实际 token。Tailwind v4 的 CSS-first 配置(@theme 块)在这里显出优势——MatchKit 的文章指出,Claude Code 和 Cursor 可以直接读取 CSS 文件,理解 CSS 自定义属性,但解析 JavaScript 配置文件时容易因为模块系统和导入路径的多样性而出错。用 @theme 把 token 写成纯 CSS,等于让 AI 直接看到一张”设计 token 表”。

更进一步,有开源工具开始解决这个精确问题——比如 tailwind-context-resolver-mcp,它加载项目的 Tailwind 配置,暴露给 AI agent 一个可查询的接口:项目有哪些品牌色?p-18 是有效的 spacing 值吗?这个 class 字符串中有没有冲突的 utility?AI 写组件之前先查一遍 token,写完之后再 validate 一遍 class 字符串,从流程上消灭幻觉。

我的实际体验

用 AI 写这个博客的前端时,Tailwind 的体验明显优于之前手写 CSS 项目时的 AI 体验:

  • AI 生成的组件,class 名不需要我在 JSX 和 CSS 文件间跳来跳去对照
  • 修改请求(“把这个按钮从蓝色改成绿色”)只需改一个 class 名,AI 几乎不会改错
  • 新写的组件和之前的组件风格一致——因为 AI 从 global.css@theme 中读到的是同一套 token,不是靠”感觉”调色
  • 当 AI 编造了一个不存在的 class(如 text-misaka-red),出问题的方式是样式不生效,而不是引入了一个命名冲突或全局样式泄漏——更容易发现和修复

总结

Tailwind 改变的不是我写 CSS 的方式,而是我思考样式的方式

过去是「这个元素需要什么样式」→ 写一个新 class → 在 CSS 文件里定义。现在是「这个元素在 design system 里对应什么 token」→ 直接在 HTML 里组合。这个思维转变比语法重要得多。

如果你在做一个人维护的前端项目,我建议至少试一次。不一定会爱上它,但你会重新思考 CSS 到底在解决什么问题。