Tailwind CSS v4 vs v3:重写级升级到底改了什么
本文基于一个真实项目(React + TypeScript + Vite 的全屏 Hero 落地页)的实际使用体验,结合官方文档,梳理 Tailwind CSS v4 相对 v3 的全部重大变化。
最后更新:2026-09-08
一句话概括
Tailwind CSS v4 用 Rust 重写了引擎,把配置从 JavaScript 搬进了 CSS,砍掉了一堆历史包袱类名。结果是构建速度提高 3-10 倍,配置文件从三个减到零,日常开发体验发生了质变。
目录
- 引擎:从 Node.js 到 Rust(Oxide)
- 配置:从 JS 到 CSS-first
- 工具链:从 PostCSS 三件套到单插件
- 内容扫描:手动声明 → 自动检测
- 类名变更:删、改、缩放位移
- 新指令一览
- 暗黑模式配置迁移
- 实际项目中的代码对比
- 迁移路径与注意事项
- 性能基准数据
- 总结:该不该升级
1. 引擎:从 Node.js 到 Rust(Oxide)
v3 的整个构建管线是 JavaScript 写的(Node.js + PostCSS)。v4 用 Rust 重写了一个叫 Oxide 的新引擎,配合 Lightning CSS 做 CSS 处理。
| 指标 | v3 | v4 | 提升 |
|---|---|---|---|
| 全量构建 | ~378ms | ~100ms | 3.8x |
| 增量构建(有新类) | ~44ms | ~5ms | 8.8x |
| 增量构建(无新类) | ~35ms | ~0.2ms | 182x |
| CSS 产物体积 | ~24KB | ~18KB | -25% |
数据来自 Tailwind Labs 官方 benchmark(Catalis 项目),2024 年 11 月 v4 beta 公告。
实际体感:开发时 HMR(热模块替换)几乎是"即时"的,项目越大感受越明显。这在大型 React/Vue 应用中改善显著。
2. 配置:从 JS 到 CSS-first
这是 v4 最大的架构变更,也是需要开发者理解的最重要的一条。
v3 的做法
项目根目录需要三个配置文件:
tailwind.config.js ← 主题、插件、内容路径全在这
postcss.config.js ← PostCSS 插件链
src/index.css ← @tailwind base / components / utilities
// tailwind.config.js(v3)
module.exports = {
content: ['./src/**/*.{js,ts,jsx,tsx}'],
theme: {
extend: {
colors: {
brand: '#6366f1',
},
fontFamily: {
display: ['Anton', 'sans-serif'],
sans: ['Inter', 'sans-serif'],
},
borderRadius: {
'4xl': '2rem',
},
},
},
darkMode: 'class',
plugins: [require('@tailwindcss/forms')],
};
v4 的做法
不需要 tailwind.config.js,不需要 postcss.config.js。全部配置写进 CSS:
/* src/index.css(v4) */
@import "tailwindcss";
@theme {
--color-brand: #6366f1;
--font-display: "Anton", sans-serif;
--font-sans: "Inter", sans-serif;
--radius-4xl: 2rem;
}
就这样。整个项目只需要这一个 CSS 文件,配合一个 Vite 插件注册,完事。
关键理解:@theme vs :root
你可能会问:为什么不用 :root { --color-brand: ... } ?
区别在于 @theme 做了两件事:
- 定义 CSS 变量(和
:root一样,运行时可用) - 自动生成对应的工具类
写 --color-brand: #6366f1 之后,Tailwind 自动产出:
- bg-brand
- text-brand
- border-brand
- ring-brand
- divide-brand
- 以及所有带透明度修饰符的变体:bg-brand/50、text-brand/75 等等
写 :root 只有变量,不会有工具类。这就是 @theme 的核心价值。
命名空间与工具类的映射规则
| CSS 变量前缀 | 生成的工具类 | 示例 |
|---|---|---|
--color-* |
bg-*、text-*、border-*、ring-* |
--color-brand → bg-brand |
--font-* |
font-* |
--font-display → font-display |
--spacing-* |
p-*、m-*、gap-* 等 |
--spacing-18 → p-18 |
--breakpoint-* |
响应式前缀 | --breakpoint-3xl → 3xl:grid-cols-4 |
--radius-* |
rounded-* |
--radius-4xl → rounded-4xl |
--ease-* |
ease-* |
--ease-spring → ease-spring |
--animate-* |
animate-* |
--animate-fade → animate-fade |
规律:变量名去掉 -- 前缀,就是工具类名。 学一次命名空间,终身受用。
3. 工具链:从 PostCSS 三件套到单插件
v3 的依赖堆栈
npm install tailwindcss postcss autoprefixer
配置 postcss.config.js:
module.exports = {
plugins: {
'postcss-import': {},
tailwindcss: {},
autoprefixer: {},
},
};
三个包,三个配置项,缺一不可。
v4 的依赖
Vite 项目(推荐):
npm install tailwindcss @tailwindcss/vite
// vite.config.ts
import tailwindcss from '@tailwindcss/vite';
export default defineConfig({
plugins: [tailwindcss()], // 就这一行
});
@tailwindcss/vite 一个插件包,内置了 CSS 处理、浏览器前缀(autoprefixer)和 Tailwind 编译,不需要 PostCSS 配置文件。
PostCSS 项目(Next.js 等):
npm install tailwindcss @tailwindcss/postcss
// postcss.config.mjs
export default {
plugins: {
'@tailwindcss/postcss': {}, // 替代三个插件
},
};
CLI(无构建工具):
npx @tailwindcss/cli -i input.css -o output.css
tailwindcss包本身不再是 PostCSS 插件了。PostCSS 插件搬到了@tailwindcss/postcss。如果你的postcss.config.js还写着tailwindcss: {},升级后会静默失效。
4. 内容扫描:手动声明 → 自动检测
v3 必须手写
// tailwind.config.js
module.exports = {
content: [
'./src/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx}',
],
};
漏写一个路径 → 该目录下的类名不会出现在产物中 → 样式丢失。这是 v3 最常见的 bug 来源之一。
v4 完全自动
不需要任何 content 配置。@tailwindcss/vite 插件在编译时自动扫描项目中所有 .js、.ts、.tsx、.html、.md 文件,发现用到的类名就生成对应 CSS。
特殊情况:排除或额外包含
/* 显式添加不在自动扫描范围的来源 */
@source "../node_modules/@my-company/ui-lib";
/* 排除某个目录 */
@source not "../src/components/legacy";
/* 彻底关闭自动检测(极少用) */
@import "tailwindcss" source(none);
5. 类名变更:删、改、缩放位移
这是升级时最容易导致视觉 bug 的部分。v4 对历史遗留类名做了一次大清理。
5.1 删除:Opacity 修饰符
v3:
<div class="bg-blue-500 bg-opacity-50">
v4:
<div class="bg-blue-500/50"> <!-- 斜杠语法,这是唯一写法 -->
所有 *-opacity-* 工具类被移除:bg-opacity-*、text-opacity-*、border-opacity-*、ring-opacity-*、divide-opacity-*、placeholder-opacity-*。
5.2 重命名:缩放阶梯下移一档
v4 在 shadow、rounded、blur 三个尺度的最前面插入了一个更小的档位(xs),原来的 sm 变成了 xs,以此类推。
| 用途 | v3 | v4 |
|---|---|---|
| 阴影最小 | shadow-sm |
shadow-xs |
| 阴影默认 | shadow |
shadow-sm |
| 圆角最小 | rounded-sm |
rounded-xs |
| 圆角默认 | rounded |
rounded-sm |
| 模糊最小 | blur-sm |
blur-xs |
| 模糊默认 | blur |
blur-sm |
最容易踩的坑: 你写的 shadow-sm 在 v4 里变成了最小阴影(而不是你记忆中的次小阴影),视觉上会"变淡"。升级后逐个对比。
5.3 重命名:语义修正
| v3 | v4 | 原因 |
|---|---|---|
bg-gradient-to-r |
bg-linear-to-r |
回归 CSS 原生命名(linear-gradient) |
bg-gradient-to-t |
bg-linear-to-t |
同上 |
flex-shrink-0 |
shrink-0 |
简化 |
flex-grow |
grow |
简化 |
overflow-ellipsis |
text-ellipsis |
对齐 CSS 属性名 |
decoration-slice |
box-decoration-slice |
对齐 CSS 属性名 |
decoration-clone |
box-decoration-clone |
同上 |
outline-none |
outline-hidden |
outline-none 现在真正设置 outline-style: none |
5.4 默认值变更
| 项 | v3 | v4 | 影响 |
|---|---|---|---|
border 默认颜色 |
gray-200 |
currentColor |
带 border 类的元素会继承文字颜色 |
ring 默认宽度 |
3px |
1px |
焦点环变细,需要 ring-3 恢复 |
outline-none 语义 |
透明 2px 轮廓(保留强制颜色模式) | outline-style: none |
原行为改名为 outline-hidden |
5.5 Hover 行为变更
v4 的 hover: 变体自动包裹在 @media (hover: hover) 内,触摸设备上的"模拟 hover"不再触发。这通常是你想要的行为,但如果你依赖 :active 类似的"按下效果",需要改用 active:。
6. 新指令一览
| 指令 | 用途 | 替代的是 |
|---|---|---|
@import "tailwindcss" |
引入 Tailwind 全部层 | @tailwind base; @tailwind components; @tailwind utilities; |
@theme { } |
定义设计令牌,自动生成工具类 | tailwind.config.js 的 theme.extend |
@theme inline { } |
映射已有变量到工具类,不生成新变量 | 无(新功能) |
@theme static { } |
定义不生成工具类的令牌 | 无(新功能) |
@utility name { } |
注册自定义工具类 | @layer utilities + @apply |
@custom-variant name (selector) |
注册自定义变体 | tailwind.config.js 的 variants |
@source |
显式添加扫描路径 | content 数组 |
@config |
加载旧版 JS 配置(过渡用) | 无(兼容桥接) |
@reference |
在 Vue/Svelte <style> 中引入主题变量 |
无(新功能) |
@plugin |
加载旧版 JS 插件 | require() |
重点:@utility 的用法
v3 里注册自定义工具类需要在 @layer utilities 里写:
/* v3 */
@layer utilities {
.tab-4 { tab-size: 4; }
}
v4 用 @utility 指令,支持变体(hover、focus 等):
/* v4 */
@utility tab-4 {
tab-size: 4;
}
/* 自带动画定义能力 */
@utility grain {
position: absolute;
inset: 0;
opacity: 0.16;
mix-blend-mode: overlay;
background-image: url("data:image/svg+xml,...");
}
theme() 函数已弃用
v3 里在自定义 CSS 里访问主题值:
/* v3(已弃用) */
.foo {
color: theme('colors.blue.500');
margin: theme('spacing.12');
}
v4 直接用 CSS 变量:
/* v4 */
.foo {
color: var(--color-blue-500);
margin: --spacing(12);
}
7. 暗黑模式配置迁移
v3
// tailwind.config.js
module.exports = {
darkMode: 'class', // 或 'media'
};
v4
默认行为 = 跟随系统偏好(prefers-color-scheme: dark),不需要任何配置。
如果要用 class 策略(在 <html> 上加 .dark 类切换):
@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));
如果要基于 data-theme 属性:
@custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));
不再需要写 darkMode: 'class',改为声明式的 CSS variant。
8. 实际项目中的代码对比
以下是我在一个真实 React + Vite 项目里写 v4 配置的完整例子,可以直接对照 v3 的写法。
项目文件结构对比
v3 项目(3 个配置文件) v4 项目(1 个 CSS 文件)
├── tailwind.config.js ├── vite.config.ts(插件注册)
├── postcss.config.js └── src/index.css(@theme 指令)
└── src/index.css
src/index.css(完整版)
@import "tailwindcss";
@theme {
/* 字体:自动生成 font-display、font-sans 工具类 */
--font-display: "Anton", "Archivo Black", "Impact", system-ui, sans-serif;
--font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
/* 颜色:自动生成 bg-ink、text-ink、bg-bone、text-bone 等 */
--color-ink: #08080a;
--color-bone: #f4f2ee;
--color-acid: #d8ff3e;
/* 缓动:自动生成 ease-out-expo 工具类 */
--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
}
/* 自定义工具类 —— v4 用 @utility 替代 @layer utilities */
@utility grain {
position: absolute;
inset: 0;
pointer-events: none;
opacity: 0.16;
mix-blend-mode: overlay;
background-image: url("data:image/svg+xml,...");
}
@utility caret {
display: inline-block;
width: 0.06em;
animation: caret-blink 1s steps(1) infinite;
}
@utility spin-slow {
animation: spin-slow 12s linear infinite;
}
@keyframes caret-blink { 0%,49% { opacity: 1 } 50%,100% { opacity: 0 } }
@keyframes spin-slow { to { rotate: 360deg } }
JSX 里使用(v3 和 v4 工具类写法完全相同)
// 大部分工具类写法不变
<div className="bg-ink text-bone font-display rounded-full">
// 透明度(v3 的 bg-opacity-* 已移除,必须用斜杠语法)
<div className="bg-black/75"> // 不是 bg-black bg-opacity-75
// 圆角(阶梯下移一档)
<div className="rounded-xs"> // v3 里的 rounded-sm
<div className="rounded-sm"> // v3 里的 rounded
// 模糊
<div className="blur-sm"> // v3 里的 blur
<div className="blur-xs"> // v3 里的 blur-sm
// 渐变(名称回归 CSS 原生)
<div className="bg-linear-to-r"> // 不是 bg-gradient-to-r
// 自定义工具类(直接用 @utility 注册的名字)
<div className="grain"> // 引用 @utility grain
<svg className="spin-slow"> // 引用 @utility spin-slow
9. 迁移路径与注意事项
自动迁移工具
npx @tailwindcss/upgrade
该工具能自动处理约 90% 的迁移工作:
- 把 tailwind.config.js 转成 @theme CSS 指令
- 重命名已废弃的类名
- 把 @tailwind 指令换成 @import "tailwindcss"
- 更新 PostCSS 配置
不能自动处理的:
- 自定义 JS 插件(插件 API 变了)
- 复杂的 theme() 函数调用
- 基于 JS 的动态配置逻辑
浏览器兼容性
v4 要求现代浏览器,依赖 @property、color-mix() 等 CSS 特性:
| 浏览器 | 最低版本 |
|---|---|
| Chrome | 111+ |
| Safari | 16.4+ |
| Firefox | 128+ |
如果需要兼容更老的浏览器,暂时留在 v3.4。
过渡策略
如果项目太大不想一步到位,v4 提供了兼容桥接:
@import "tailwindcss";
/* 继续使用旧版 JS 配置 */
@config "../../tailwind.config.js";
/* 继续使用旧版 JS 插件 */
@plugin "@tailwindcss/typography";
CSS 里定义的 @theme 会与 JS 配置合并,CSS 优先。你可以分步迁移。
10. 性能基准数据
以下数据来自 Tailwind Labs 官方 benchmark,使用 Catalyst 组件库项目:
| 场景 | v3.4 | v4 | 提升 |
|---|---|---|---|
| 全量构建 | 378ms | 100ms | 3.8x |
| 增量构建(新类) | 44ms | 5ms | 8.8x |
| 增量构建(无新类) | 35ms | 0.2ms | 182x |
"无新类"场景占比最高——随着项目开发,大部分编辑都只用到已有的工具类,这些构建在微秒级完成。这就是为什么日常开发体感"快了一个量级"。
其他第三方 benchmark(2026):
| 来源 | 全量构建 | 增量构建 | CSS 体积 |
|---|---|---|---|
| dev.to benchmark | ~800ms → ~100ms(8x) | ~200ms → ~5ms(40x) | 24KB → 18KB(-25%) |
| Digital Applied | 3.5s → <100ms(35x) | — | — |
实际体感取决于项目规模。类名越多、文件越多,v4 的优势越大。
11. 总结:该不该升级
直接升级的情况
- 新项目,没有理由用 v3
- 项目小、有测试覆盖
- 已经在用 Vite(v4 的 Vite 插件体验最好)
- 大型项目但有完善的回归测试
暂时观望的情况
- 大量自定义 PostCSS 插件,插件尚未发布 v4 兼容版本
- 必须支持 Chrome 111 / Safari 16.4 以下的浏览器
- 依赖的第三方 UI 库(如 Headless UI、Radix 等)尚未适配 v4
关键差异速查表
| 维度 | v3 | v4 |
|---|---|---|
| 配置语言 | JavaScript | CSS(原生) |
| 配置文件数 | 2-3 个 | 0 个 |
| 主题定义位置 | tailwind.config.js |
CSS @theme 指令 |
| 自定义工具类 | @layer utilities |
@utility 指令 |
| 引擎语言 | JavaScript | Rust |
| PostCSS 依赖 | 必须 | 可选(Vite 项目不需要) |
| 内容扫描 | 手动 content: [...] |
自动 |
| 透明度写法 | bg-opacity-50 |
bg-black/50 |
| 渐变写法 | bg-gradient-to-r |
bg-linear-to-r |
| 圆角/阴影/模糊 | rounded-sm = 0.125rem |
rounded-sm = 0.25rem(阶梯下移) |
| border 默认颜色 | gray-200 |
currentColor |
| ring 默认宽度 | 3px | 1px |
| hover 触发 | 无条件 | 仅 hover: hover 媒体 |
| 暗黑模式配置 | darkMode: 'class' |
@custom-variant dark (...) |
theme() 函数 |
可用 | 已弃用,用 var() |
| CSS 原生层支持 | 无(Tailwind 自实现) | 有(@layer) |
附:快速命令对照
# v3 安装
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init
# v4 安装(Vite 项目)
npm install -D tailwindcss @tailwindcss/vite
# v4 安装(PostCSS 项目)
npm install -D tailwindcss @tailwindcss/postcss
# v4 CLI
npm install -D @tailwindcss/cli
# 自动迁移
npx @tailwindcss/upgrade
本文写作过程中使用的真实项目:Mainframe — 全屏 Hero 落地页,基于 React 19 + TypeScript 7 + Vite 8 + Tailwind CSS 4.3 构建。
posted on 2026-09-08 17:46 fox_charon 阅读(3) 评论(0) 收藏 举报
浙公网安备 33010602011771号