Tailwind v4 最大的变化不是新特性,而是接入方式换了。旧的三行指令在新版本里没有任何意义,但它们依然能被 CSS 解析器接受——所以写错了不会报错,只会静默失效。
两代接入方式的区别
/* v3:三条指令,靠 PostCSS 扫描内容后注入 */
@tailwind base;
@tailwind components;
@tailwind utilities;/* v4:一条 import,靠 Vite 插件在构建期完成 */
@import 'tailwindcss';v4 把 PostCSS 换成了一个真正的打包器插件,所以还多了一步配置:
// vite.config.ts
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [react(), tailwindcss()],
})少了这个插件,@import 'tailwindcss' 会由 Vite 的 CSS 处理去解析——它能找到 node_modules 里的文件,于是不报错,但也不会按你的内容生成工具类。
怎么确认它真的在工作
不要看页面,看产物里的字节数。
$ npx vite build
dist/assets/index-*.css 49.32 kB # 生效
dist/assets/index-*.css 11.04 kB # 没生效(只剩预置样式)再抓几个实际用到的类名:
$ for c in min-h-screen backdrop-blur text-8xl bg-surface; do
> printf "%s: " "$c"; grep -c "$c" dist/assets/*.css
> done
min-h-screen: 1
backdrop-blur: 3
text-8xl: 1
bg-surface: 1设计令牌用 @theme inline
v4 的 @theme 会把变量变成工具类,但它默认是静态的:切换主题时变量不会跟着变。
要让主题切换生效,需要两步:语义变量定义在 :root 上,@theme 里用 inline 引用它们。
:root {
--bg: #0b0f19;
--fg: #e6e8ee;
}
[data-theme='light'] {
--bg: #fcfcfd;
--fg: #16181d;
}
@theme inline {
--color-bg: var(--bg);
--color-fg: var(--fg);
}inline 的含义是"把这个值原样写进工具类",而不是"复制一份常量"。于是 bg-bg 编译成 background-color: var(--bg),data-theme 一变,所有用到它的地方同时变——一行 JS 都不用写。
完整的切换链路——首帧内联脚本、useSyncExternalStore、两套变量的维护纪律——单独记在 主题切换不该经过 React。
顺带的两个坑
baseUrl在 TS 6 里已废弃,写了会直接报 TS5101。路径别名只写paths即可,Vite 侧用resolve.alias对齐。framer-motion和motion不要同时装。motion/react本身已经依赖前者,多装一个只是让 lockfile 更重。
现在这套配置的约束
- 字阶、间距、圆角、动效时长全部在
@theme里,组件里不许出现魔法数字; - 颜色只用语义名(
bg/surface/line/fg/muted),不许出现indigo-500这类色板名; - 深浅两套主题共用同一批变量,新增颜色必须两套都补。