学习TailwindCSS
如果你也曾在 styles.css 里迷失于 .card-wrapper 和 .card-container 的命名拉锯战,或者在项目后期对着几百行“不敢删、不敢改”的覆盖样式发呆,那么 TailwindCSS 很可能就是那个让你重新找回掌控感的选择。
为什么是 TailwindCSS,而不是继续写“正常”的 CSS?
TailwindCSS 是一个 Utility-First(工具优先) 的 CSS 框架。它不提供 .btn、.card 这样的成品组件类,而是给你几百个单一职责的工具类——flex、pt-4、text-center、bg-blue-500——让你直接在 HTML 里组合出任何界面。
这和传统写法的差别一目了然:
/* 传统方式:在 CSS 文件里定义语义类 */
.primary-button {
background-color: #3b82f6;
padding: 0.5rem 1rem;
border-radius: 0.375rem;
}
<!-- Tailwind 方式:样式就在元素上 -->
<button class="bg-blue-500 px-4 py-2 rounded-md text-white">点击我</button>
最大的感受是不用再离开当前文件去“另一个地方”改样式,也彻底告别了命名内耗。当然,初次看到一长串 class 确实会心里一紧,这很正常——几乎每个初学者都会问“这不就是内联样式吗?”但实际上,Tailwind 提供了内联样式做不到的东西:约束化的设计系统、响应式前缀、状态变体、暗色模式支持,以及生产构建时只打包用到的类,最终 CSS 文件可以压到 10KB 以下。
它和 Bootstrap 的思路也完全不同。Bootstrap 给你“已经装修好的房间”,定制意味着在既有样式上层层覆盖;Tailwind 给你“一面墙和一套标准色卡”,从零开始,但每一步都由你决定,后期也不会有覆盖样式的战争。
搭一个最精简的 Vue CLI + Tailwind 环境
Tailwind 需要构建工具来扫描你实际用到的类,所以不能像老式框架那样直接 <link> 一个 CDN 文件就完事。Vue CLI 项目底层是 Webpack,它和 Vite 的接入路径不同:Vite 用专门的 @tailwindcss/vite 插件,Vue CLI 则走 PostCSS 处理链,需要安装的是 @tailwindcss/postcss。如果照着 Vite 教程装了 @tailwindcss/vite,在 Vue CLI 项目里是不起作用的,先把它卸掉。
下面以已有的 Vue CLI 项目为前提(不再重新创建项目),完整走一遍 v4 的接入流程。
第一步,安装 Tailwind v4 的 PostCSS 插件和基础依赖:
npm install tailwindcss @tailwindcss/postcss postcss
在 Tailwind v4 中,PostCSS 插件已经从 tailwindcss 主包里拆分出去,单独发布了 @tailwindcss/postcss。继续在配置里写 tailwindcss: {} 是 v3 的旧写法,v4 下会直接报错。
第二步,配置 postcss.config.js:
在项目根目录找到或创建 postcss.config.js,写入:
module.exports = {
plugins: {
'@tailwindcss/postcss': {},
},
}
注意不要沿用旧教程里的 tailwindcss: {} 加 autoprefixer: {}。@tailwindcss/postcss 这个包已经内置了必要处理,单独注册它就够了。
第三步,创建 Tailwind 入口 CSS 并在 main.js 中引入:
在 src 下新建一个样式文件,比如 src/style.css,内容只有一行:
@import "tailwindcss";
然后在 src/main.js(或 main.ts)里导入它:
import { createApp } from 'vue'
import App from './App.vue'
import './style.css'
createApp(App).mount('#app')
第四步,重启并验证:
修改 PostCSS 配置后需要重启开发服务器:
npm run serve
如果之前跑过开发服务器,建议先清一下 Vue CLI 的缓存,避免旧构建结果干扰:
rm -rf node_modules/.cache # macOS / Linux
# Windows CMD: rmdir /s /q node_modules\.cache
然后在任意 .vue 文件的 <template> 里写一个带 Tailwind 类的元素:
<h1 class="bg-blue-500 text-white p-4 rounded-lg">Tailwind 已生效</h1>
看到蓝底白字、带内边距和圆角,环境就配置成功了。
版本提醒:以上流程基于 Tailwind CSS v4。v4 不再需要在 CSS 里写
@tailwind base; @tailwind components; @tailwind utilities;三行指令,也不再强制生成tailwind.config.js,只需一行@import "tailwindcss"。如果你看到的是旧教程里npx tailwindcss init -p生成tailwind.config.js、再配合tailwindcss: {}的写法,那是 v3 的流程。v3 在 Vue CLI 里依然可用,但新接入建议直接走 v4 的 PostCSS 路径。
和 Vite 方式的核心差异可以用一句话概括:Vite 注册的是 tailwindcss() 函数(来自 @tailwindcss/vite),Vue CLI 注册的是 '@tailwindcss/postcss' 这个插件字符串(写在 postcss.config.js 里) 。两者最终引用的 CSS 入口相同,都是 @import "tailwindcss";,差异只在构建工具这一层。
入门阶段真正需要吃透的几个核心概念
1. 工具类 = 积木,不是内联样式的“换皮”
一个 Tailwind 类只做一件事:p-4 管内边距,text-lg 管字号,rounded-lg 管圆角。写页面就像搭积木,用几十个小块拼出任何形状。关键在于这些“积木”来自一套受限的设计令牌系统——间距、颜色、字号都是预设的尺度,保证你在快速拼装的同时不会随手写出 margin: 13px 这样的魔法数字。
2. 响应式:前缀即断点,移动优先
Tailwind 的响应式逻辑直接内嵌在类名里。不加前缀的类对所有屏幕生效,加了 md:、lg: 的类只在对应断点及以上生效:
<!-- 移动端单列,平板及以上两列,桌面三列 -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
...
</div>
记住断点顺序:sm(640px)、md(768px)、lg(1024px),从小的往上写,不需要再跳到 CSS 文件里写 @media 块。
3. 状态变体:hover:、focus:、dark:
交互状态和暗色模式也是前缀:
<button class="bg-blue-500 hover:bg-blue-700 dark:bg-blue-600 text-white px-4 py-2 rounded">
按钮
</button>
这种“状态写在类名里”的方式,让组件的行为一眼可见,不用再去猜某个 :hover 样式定义在了哪个文件里。
为什么它改变了写样式的方式
TailwindCSS 的流行常被归因于“原子化”或“不用起类名”。但真正让它在工程上站住脚的,是五个相互咬合的设计决策:响应式内嵌、状态表达、暗色模式、CSS 原生定制、按需生成。它们单独看都不复杂,组合起来却解决了一个老问题:样式决策如何从散落的 CSS 文件和配置中,收回到组件本身。
下面从这五个特点出发,记录它们在实际使用中到底是什么、怎么用,以及背后的机制。
响应式:断点写在类名里,移动优先是默认
Tailwind 的响应式不是“额外加的功能”,而是直接长在工具类的命名系统里。无前缀的类对所有屏幕生效,加了 sm:、md:、lg: 的类只在对应断点及以上生效。这是一种移动优先(mobile-first) 的默认策略:基础样式覆盖最小屏幕,复杂样式通过前缀逐层叠加。
一个卡片列表可以在一行 class 里完成从手机到桌面的三档布局:
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
<div class="p-4 rounded-lg bg-white shadow">卡片一</div>
<div class="p-4 rounded-lg bg-white shadow">卡片二</div>
<div class="p-4 rounded-lg bg-white shadow">卡片三</div>
</div>
含义是:默认单列,sm(≥640px)两列,lg(≥1024px)三列。不用写三个 @media 块,不用在 CSS 和模板之间来回切换。断点值本身也是一套设计令牌,而不是随手写的魔法数字。
这个决策的深层效果是:响应式不再是事后补的“适配工作”,而是布局表达的一部分。你在写组件的时候就已经在考虑它在不同宽度下的形态,而不是等页面做完再去另一个文件里补媒体查询。
状态控制:伪类变成前缀,交互写在元素上
:hover、:focus、:active、:disabled 这些伪类在 Tailwind 里统一变成了前缀。按钮的完整交互状态可以全部写在它的 class 里:
<button class="bg-blue-500 hover:bg-blue-600 focus:ring-2 focus:ring-blue-300 active:scale-95 disabled:opacity-50 text-white px-4 py-2 rounded">
提交
</button>
相比在 CSS 文件里分散定义 .btn:hover { ... }、.btn:focus { ... },这种方式让组件的行为在模板里一眼可见。你不需要去猜某个 hover 样式定义在哪个文件、是否被后来的规则覆盖。
前缀之间还可以组合。md:hover:bg-blue-700 表达的是“中等屏幕以上、悬停时变色”,条件关系直接体现在类名顺序里。
更进一步的工具是 group 和 peer。它们把父元素状态和兄弟元素状态也纳入了前缀体系:父元素加 group,子元素用 group-hover:* 响应父级的 hover;一个元素加 peer,它后面的兄弟元素用 peer-checked:* 或 peer-focus:* 响应它的状态。不需要写 JavaScript,纯 CSS 就能表达“整张卡片悬停时标题变色、箭头位移”这类联动。
暗色模式:一个前缀,两种驱动策略
dark: 前缀是 Tailwind 暗色模式的核心接口。它的特殊之处在于,同一个前缀可以挂接到不同的触发机制上。
默认情况下,dark: 样式被包裹在 @media (prefers-color-scheme: dark) 中,自动跟随操作系统的颜色偏好。零配置,尊重用户系统设置,但没有手动切换的能力。
如果需要用户手动切换主题,在 v4 中通过 @custom-variant 把 dark: 的触发条件从媒体查询改成选择器:
@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));
之后,dark: 样式只在 HTML 树中存在 .dark 类时生效。用 JavaScript 动态添加或移除这个类,就实现了手动切换。也可以用 [data-theme="dark"] 属性选择器代替类名,逻辑完全相同。
一个完整的切换逻辑通常需要同时处理三种状态:用户明确选了亮色、用户明确选了暗色、用户选择跟随系统。这需要用 localStorage 记录偏好,并用 window.matchMedia 检测系统主题。这段脚本应放在 <head> 中、CSS 加载之前,以避免页面加载时的颜色闪烁(FOUC)。
暗色模式在 v4 中默认启用,不再需要 v3 时代的 darkMode: "class" 配置。设计上的意义是:主题切换从“全局开关”变成了“前缀条件” ,每个元素可以独立决定自己在暗色下的表现,而不需要维护两套样式表。
高度可定制:v4 把设计令牌交还给 CSS
Tailwind 的可定制性在 v4 中发生了一个方向性的变化:从 JavaScript 配置文件转向 CSS 原生变量。
在 v3 中,自定义主题意味着在 tailwind.config.js 里写 theme.extend.colors。v4 引入了 @theme 指令,直接在 CSS 中定义设计令牌:
@import "tailwindcss";
@theme {
--color-primary: #3b82f6;
--color-secondary: #64748b;
--spacing-18: 4.5rem;
--font-brand: "Inter", sans-serif;
}
定义之后,bg-primary、text-secondary、p-18、font-brand 这些工具类会自动可用。不需要手动注册,也不需要维护两份配置。
@theme 的底层是 CSS 自定义属性。这意味着主题值可以在运行时被覆盖:在 :root 或 .dark 选择器下重新赋值同一个变量,所有引用该变量的工具类会同步变化。你可以在 [data-theme="ocean"] 这样的属性作用域下重新映射语义色令牌,实现多品牌主题切换,而标记里只需要使用 bg-primary 这样的语义类,不需要堆叠 dark:bg-* 或主题专用前缀。
这个决策的后果是:Tailwind 的“约束”不再是框架强加的,而是由你在 CSS 中定义的。间距、颜色、断点、字号都可以替换成符合自己设计规范的值,同时仍然享受工具类的组合能力和构建优化。
性能优化:按需生成,而不是全量打包
Tailwind 的构建性能来自 JIT(Just-in-Time)编译器。它不预先生成整套工具类,而是在构建时扫描源码中实际出现的类名,只生成用到的样式。
几个关键数字:早期 JIT 在最大项目上的完整编译约 800ms,增量重建可快到 3ms。更重要的效果是,开发环境和生产环境生成的 CSS 完全一致,不需要在构建时“清除未使用样式”,也就不存在“开发时好好的、上线后样式丢了”这类问题。浏览器在开发时也不必解析数兆字节的预生成 CSS。
这个机制有一个必须遵守的约束:类名必须以完整字符串的形式出现在源码中。构建工具是静态扫描,不执行 JavaScript,所以 bg-${color}-500 这种拼接写法不会被识别,对应的类不会被生成。正确做法是用对象映射或条件表达式写出完整类名:
const colorClasses = {
blue: 'bg-blue-500',
red: 'bg-red-500',
}
<div className={colorClasses[color]} />
按需生成的代价是类名必须可被静态分析;换来的则是构建体积与项目实际使用的样式量成正比,而不是与框架提供的工具类总数成正比。

浙公网安备 33010602011771号