你正在错误地使用 TailwindCSS
抱歉,我没有看到需要翻译的正文内容。请提供您希望翻译成简体中文的文本,我会在保留原始链接和格式的前提下为您完成翻译。
我之前提到过,为什么我通常 不 推荐在我的项目中把 Tailwind CSS 作为主要的样式方案,我已经在这里详细解释了这种立场 here。
这次,我想从更务实的角度谈谈 如何 正确使用 Tailwind CSS,而不是把它变成一种反模式。
网络样式简史
从前,有快乐的恐龙……好吧,这个时代太久远了。让我们回到现代 HTML 和 CSS 开始广泛可用的那个节点,大约在 HTML5 与 CSS3 稳定、网络开始快速扩张的时期。
同一时期,Twitter 推出了 Bootstrap,这是首批被广泛采用的基于组件的 CSS 库之一。Bootstrap 在当时堪称革命性:它提供了一套一致的视觉语言、合理的默认样式以及现成的组件,大幅降低了构建界面的工作量。
然而,Bootstrap 也伴随了一些权衡:
- 它自带了非常主观的设计体系,这导致许多网站看起来几乎一样。
- 虽然可以覆盖样式并使用主题,但样式冲突不可避免。
- Bootstrap 需要固定的 HTML 结构;对任意元素的更改都可能导致整体样式失效。
- 你还必须记住完整的结构或从文档中复制粘贴使用。
Source: …
组件抽象的崛起
随着 React、Vue 等 JavaScript 框架的兴起,开发者体验(DX)显著提升。我们现在可以通过封装 HTML 标记和类,并通过 props/attributes 暴露干净的 API,达到更高层次的抽象。
抽象前(Bootstrap v5 模态框标记)
<div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Modal title</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
Modal body text goes here.
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary">Save changes</button>
</div>
</div>
</div>
</div>
抽象后(MUI v7.x 模态组件)
<Modal open={open} onClose={handleClose}>
<Box sx={{ p: 2 }}>
Modal body text goes here.
</Box>
</Modal>
在这里,结构复杂性被隐藏在组件内部。使用者只需与清晰的 API 交互,而不必直接处理原始标记和脆弱的类结构。
实际上 Utility CSS 试图解决的问题
想象一个常见的情形:你需要在元素的行内起始位置添加一点点内边距,例如 5 px。于是你创建一个类 .padding-inline-start-5px。后来你需要 7 px,于是又添加 .padding-inline-start-7px。随着时间推移,这种做法会膨胀成数十甚至数百个微型类,其中不乏像 .padding-inline-start-7.8px 这样可疑的类。
Tailwind CSS 引入了一个真正好的概念:utility‑first CSS。它不是让你随意发明类名和数值,而是提供了一套 受约束且一致的比例,用于间距、颜色、排版等。该系统可以减轻决策疲劳,并在整个代码库中强制实现视觉一致性。
这部分在客观上是非常强大的。
那么为什么 Tailwind CSS 会受到抨击?
如果 Tailwind CSS 如此聪明,为什么许多开发者(包括我自己)经常对它持批评态度?
在我拙见中,问题 不在于 实用类(utility‑class)概念本身,而是 Tailwind 通常的引入和采用方式。它被呈现为一个拥有另一层抽象的框架,只提供 CSS 部分,未包含任何 HTML 或 JavaScript,并且你可以完全自由地按自己的方式使用它。
这种自由,加上它的快速流行,导致了困惑——尤其是在初学者中。很多人在真正理解 CSS 之前就开始学习 Tailwind CSS。像堆叠上下文、外边距塌陷或布局流等概念,如果从未学习过底层语言,就会显得神秘。
公平地说,这并非 Tailwind CSS 所独有。使用 React/Vue 而没有扎实的 JavaScript 基础的开发者也存在同样的模式。不过,Tailwind 无意中放大了这个问题,因为它让人可以在从未编写或深入理解 CSS 的情况下构建 UI。
我个人使用 Tailwind CSS 的方式
对我而言,Tailwind CSS 不是一种样式哲学;它是一个 实用工具库。我对待它的方式就像对待 JavaScript 中的 Lodash——它是一套有用的工具,能够简化常见任务,而不是语言本身的替代品。
这也是我欣赏像 UnoCSS 这样的项目的原因,它们在此理念上投入大量精力,并以更灵活的方向进一步推进,而不试图成为完整的独立框架。
如果我选择使用 Tailwind CSS,我的做法非常严格:
- 核心组件 使用普通 CSS 编写,并采用语义化的类名。
- Tailwind 的 CSS 变量 用于设计令牌(间距、颜色、排版)。
- 布局、动画、悬停状态以及复杂交互 放在 CSS 类中,而不是内联的实用工具链。
- 实用类 仅作为一次性微调的例外,而不是默认做法。
示例
Tailwind CSS 的问题用法
<!-- Example omitted in original source -->
上述将布局、定位、视觉样式和交互状态全部混在一个长长的实用工具链中,使得标记难以阅读和维护。
更务实的做法
<div class="card">
<!-- content -->
</div>
/* card.css */
.card {
@apply flex items-center justify-center;
@apply absolute text-center bg-white rounded-xl shadow-md p-4;
@apply hover:bg-gray-100 transition-colors duration-200;
}
这里组件的意图很明确(card),而 Tailwind 的 @apply 指令在专用的 CSS 文件中使用,使 HTML 保持简洁。
仅将 Tailwind 用作设计令牌
/* design-tokens.css */
:root {
--spacing-sm: 0.5rem;
--spacing-md: 1rem;
--spacing-lg: 2rem;
--color-primary: #1d4ed8;
--color-primary-hover: #1e40af;
}
/* Example usage */
.button {
@apply bg-primary text-white py-[var(--spacing-sm)] px-[var(--spacing-md)];
transition: background-color var(--duration-medium);
}
.button:hover {
background-color: var(--color-primary-hover);
}
Tailwind 的配置提供了尺度,但实际样式写在 CSS 中,保持了关注点分离。
要点
- 实用优先(Utility‑first)非常强大,但它应当是对语义化、可维护 CSS 的补充——而不是取代。
- 将 Tailwind 视为工具箱,而非单一的整体框架。
- 保持 核心组件标记的语义化,让 Tailwind 提供低层次的令牌(间距、颜色、排版)。
- 将 内联实用链 仅用于快速、一次性的微调;否则应提取为可复用的 CSS 类。
按照这些指南操作,你即可享受 Tailwind CSS 带来的生产力提升,同时避免常被批评的陷阱。祝你样式愉快!
清理后的 Markdown
<div class="hello-tailwind">
## Hello Tailwind
</div>
为什么将布局、视觉设计和语义混合是有问题的
这种做法将布局、视觉设计和语义直接混入标记中。
现在想象一下,你需要在 5 个不同的地方 使用这个卡片,并且有轻微的差异。每次你都要复制并修改整段 class 字符串。当设计需求变化(而且总是会变化)时,你需要逐一查找所有实例并单独更新它们。
更简洁的替代方案:使用 Tailwind 的 CSS 变量
<div class="card">
## Hello World
</div>
<style>
.card {
/* Layout and behavior */
display: flex;
position: absolute;
text-align: center;
/* Tailwind design tokens */
background-color: rgb(var(--color-white));
border-radius: calc(var(--radius) * 3); /* rounded‑xl */
box-shadow: var(--shadow-lg);
padding: calc(var(--spacing) * 6); /* p‑6 */
width: calc(var(--spacing) * 80); /* w‑80 */
}
</style>
添加修饰符
.card {
/* …base styles… */
}
/* Modifier: primary */
.card.is-primary {
background-color: rgb(var(--color-blue-500));
color: rgb(var(--color-white));
}
/* Modifier: secondary */
.card.is-secondary {
background-color: rgb(var(--color-gray-100));
color: rgb(var(--color-gray-800));
}
注意: 这是真正的 CSS。嵌套现在已经是语言本身的一部分——无需预处理器。
CSS 仍在不断演进,其功能比许多人想象的更强大。详情请参阅 MDN。
合理使用 TailwindCSS 实用类
<div class="hello-world">
## Hello World
</div>
解决常见的反驳
在继续之前,让我们先回应一些常见的、支持纯粹实用主义(utility‑first)方法的论点:
-
“但是共置(colocation)让组件更易移植!”
这确实如此,但前提是你永远不需要修改它们。一旦出现多个略有差异的实例,你要么复制整段 class 字符串,要么仍然需要创建包装组件。使用 Tailwind 变量的语义化类同样可以实现可移植性,并且可维护性更好。 -
“CSS 文件会变得臃肿且难以维护!”
这在组件作用域 CSS 和 CSS 模块出现之前是对的。现代工具(CSS Modules、Vue scoped styles、CSS‑in‑JS、Svelte 等)彻底消除了这个问题。你的组件样式与组件本身一起存放。 -
“寻找未使用的 CSS 比未使用的实用类更困难!”
使用组件作用域样式时,未使用的 CSS 会在删除组件时自动移除。使用 Tailwind 实用类时,PurgeCSS 能帮忙,但仍需注意动态类名的情况。
TailwindCSS 真正有意义的场景
在一种现代情境下,TailwindCSS 不仅合情合理,甚至可以说是最佳选择:即兴编码(vibe coding)。
当你使用 AI 工具进行原型设计时,主要目标往往是速度和视觉正确性,而不是长期可维护性。你关注的是屏幕上的效果,而不是 CSS 架构的优雅程度。在这种情况下,去设计语义化的类名、考虑抽象层次,或是仔细组织样式,都会感觉是多余的阻力。
实用优先(utility‑first)的 CSS 在这里表现良好,因为:
- 反馈循环极其快速。
- 样式紧贴 AI 正在生成或修改的标记。
- 免去手动编写和调试 CSS 文件的工作。
- 视觉微调既简单又可随意丢弃。
换句话说,TailwindCSS 与探索性、一次性思维方式高度契合。如果代码不需要长期存在、细致演进或由团队维护,那么为了速度而牺牲结构就是一种理性的权衡。
这也是 Tailwind 与 AI 工具天然匹配的原因。大型语言模型擅长生成实用类字符串,却在跨文件维护连贯、可演进的 CSS 架构方面表现较差。
最后思考
TailwindCSS 不是 错的。使用它作为 CSS 的替代品 是 错的。
当它被视为建立在扎实 CSS 知识和组件设计之上的实用层时,它可以非常高效。
当它被用作避免学习 CSS 基础的捷径时,它很快就会变成技术债务。