Tailwind CSS v4 — 当框架猜不透你的心思
你在项目里写下 text-(--brand-color),满心期待文字变成品牌色,刷新页面——字号变了。
颜色没变,字号倒是歪了。你盯着屏幕,开始怀疑人生。
别急,这不是 bug,是 Tailwind 在"猜"你的意图——而且猜错了。
这篇文章会带你走一遍真实的开发场景。从最基础的任意值用法开始,一步步遇到更复杂的情况,直到你理解 Tailwind 为什么会猜错,以及如何优雅地纠正它。
场景一:设计稿给了个非标准值
设计师甩过来一张稿子,标注写着:top: 117px、背景色 #bada55。
你翻了一遍 Tailwind 的间距和颜色系统——没有。top-28 是 112px,top-32 是 128px,不上不下。
这时候就需要任意值(Arbitrary Values)了。用方括号 [] 把具体的 CSS 值包起来:
<div class="top-[117px]">精确定位</div>
<button class="bg-[#bada55]">这个颜色名字挺快乐</button>
<div class="left-[calc(50%-4rem)]">居中偏移</div>
方括号里可以放任何合法的 CSS 值——像素、百分比、calc() 表达式,甚至 var()。Tailwind 会原封不动地把它编译成对应的 CSS。
CSS 变量怎么写?
如果你的值存在 CSS 变量里,v4 提供了一个更简洁的语法——用圆括号 () 代替方括号:
<!-- v4 新语法:圆括号 + 裸变量名 -->
<div class="bg-(--brand-color)">用 CSS 变量设背景色</div>
<!-- 当然,显式写 var() 依然有效 -->
<div class="bg-[var(--brand-color)]">效果一样</div>
这是 v4 相对 v3 的一个重要变化。v3 里 CSS 变量简写用的是方括号 bg-[--brand-color],v4 改成了圆括号 bg-(--brand-color)。这个改动不是为了好看——而是为了解决歧义问题,后面会详细说。
场景二:Tailwind 没有的 CSS 属性
项目里需要用 mask-type 控制 SVG 遮罩行为。你搜了一圈文档,Tailwind 没有提供这个工具类。
任意属性(Arbitrary Properties)登场。用方括号把完整的 属性:值 对写进去:
<div class="[mask-type:luminance]">
SVG 遮罩使用亮度模式
</div>
它和修饰符(modifier)配合也没问题:
<div class="[mask-type:luminance] hover:[mask-type:alpha]">
hover 时切换为 alpha 模式
</div>
用任意属性设置 CSS 变量
这个语法还有一个很实用的场景——在 HTML 里直接设置 CSS 变量的值:
<div class="[--scroll-offset:56px] lg:[--scroll-offset:44px]">
不同断点下设置不同的滚动偏移量
</div>
配合响应式前缀,你可以把 CSS 变量当作"响应式参数"来用,而不用写额外的媒体查询。
场景三:选择器玩不转了
产品经理说:"列表前三项要加下划线,hover 的时候。"
:nth-child(-n+3):hover —— 这选择器 Tailwind 的内置修饰符肯定不够用。
任意变体(Arbitrary Variants)可以搞定:
<ul>
<li class="[&:nth-child(-n+3)]:hover:underline">第 1 项</li>
<li class="[&:nth-child(-n+3)]:hover:underline">第 2 项</li>
<li class="[&:nth-child(-n+3)]:hover:underline">第 3 项</li>
<li>第 4 项(不受影响)</li>
</ul>
方括号里的 & 代表当前元素。Tailwind 会把 & 替换成生成的类名,编译出你需要的选择器。
再来几个例子:
<!-- 所有子 p 元素加上 margin-top -->
<div class="[&_p]:mt-4">
<p>我有 margin-top</p>
<p>我也有</p>
</div>
<!-- 当元素有 .is-dragging 类时 -->
<li class="[&.is-dragging]:cursor-grabbing">
拖拽中换光标
</li>
<!-- @supports 查询 -->
<div class="flex [@supports(display:grid)]:grid">
支持 grid 就用 grid,否则用 flex
</div>
v4 变体堆叠顺序变了:从左往右读,和 CSS 选择器一致。v3 是从右往左。
场景四:值里面有空格怎么办?
你在写 Grid 布局,需要 grid-template-columns: 1fr 500px 2fr。
直接写 grid-cols-[1fr 500px 2fr]?Tailwind 会把空格当作类名分隔符,直接报错。
解决方案:用下划线代替空格。
<div class="grid grid-cols-[1fr_500px_2fr]">
<!-- 编译后:grid-template-columns: 1fr 500px 2fr -->
</div>
Tailwind 在编译时会自动把下划线转成空格。
但是 URL 里的下划线怎么办?
放心,Tailwind 足够聪明,会保留 URL 里的下划线:
<div class="bg-[url('/what_a_rush.png')]">
<!-- 不会被转成空格,保持原样 -->
</div>
真的需要下划线呢?
用反斜杠转义:
<div class="before:content-['hello_world']">
<!-- 编译后:content: 'hello_world' -->
</div>
JSX 里反斜杠被吃了?
JSX 的字符串会把 `` 当转义字符处理。用 String.raw 模板标签:
<div className={String.raw`before:content-['hello_world']`}>
在 JSX 中安全地使用下划线
</div>
核心场景:Tailwind 猜错了
好,前面都是热身。现在进入本文的重头戏。
问题复现
回到开头的例子。你在 CSS 里定义了一个品牌色变量:
:root {
--brand-color: #e63946;
}
然后你写下:
<p class="text-(--brand-color)">品牌色文字</p>
你期望的是文字变成红色。但实际效果是——字号变了,颜色没变。
为什么?
因为 text-* 在 Tailwind 里是一个多义命名空间。它同时映射了两种不同的 CSS 属性:
-
text-lg、text-sm→font-size(字号) -
text-red-500、text-black→color(颜色)
当你写字面值的时候,Tailwind 能从值本身推断出类型:
<!-- Tailwind 看到 22px,推断为 length → font-size -->
<div class="text-[22px]">这是字号</div>
<!-- Tailwind 看到 #bada55,推断为 color → color -->
<div class="text-[#bada55]">这是颜色</div>
22px 明显是长度,#bada55 明显是颜色——推断没问题。
但 CSS 变量是个黑盒
当你写 text-(--brand-color) 的时候,Tailwind 看不到变量里存的是什么。它不知道 --brand-color 是颜色还是尺寸还是别的什么。
这时候 Tailwind 只能猜。而默认的猜测策略可能不符合你的预期——它可能把变量当成了 font-size 而不是 color。
于是你的文字不是变红了,而是字号变成了 var(--brand-color),浏览器无法解析为有效字号,表现就很诡异。
解决方案:CSS 数据类型提示
在圆括号里,变量名前面加上类型提示:
<!-- 明确告诉 Tailwind:这是颜色 -->
<p class="text-(color:--brand-color)">品牌色文字 ✓</p>
<!-- 明确告诉 Tailwind:这是字号 -->
<p class="text-(length:--font-size)">自定义字号 ✓</p>
语法格式:工具类-(类型:--变量名)
Tailwind 看到 color: 前缀,就知道应该把这个变量编译成 color 属性而不是 font-size。歧义消除。
方括号里的写法
如果你用 var() 的显式写法,类型提示放在方括号开头:
<p class="text-[color:var(--brand-color)]">同样有效</p>
不止 text-*
text-* 是最经典的歧义案例,但不是唯一一个。以下工具类都存在类似的命名空间冲突:
bg-* — 背景相关
<!-- 背景色 -->
<div class="bg-(color:--my-var)">背景颜色</div>
<!-- 背景图 -->
<div class="bg-(image:--my-var)">背景图片</div>
<!-- 背景位置 -->
<div class="bg-(position:--my-var)">背景位置</div>
bg-* 的歧义更多——它可以是颜色、图片、尺寸、位置,不加类型提示几乎必出问题。
border-* — 边框相关
<!-- 边框颜色 -->
<div class="border-(color:--my-var)">边框颜色</div>
<!-- 边框宽度 -->
<div class="border-(length:--my-var)">边框宽度</div>
shadow-* — 阴影相关
<div class="shadow-(color:--my-var)">阴影颜色</div>
decoration-* — 文本装饰
<!-- 装饰线颜色 -->
<div class="decoration-(color:--my-var)">装饰色</div>
<!-- 装饰线粗细 -->
<div class="decoration-(length:--my-var)">装饰粗细</div>
规律总结:只要一个工具类前缀同时对应多种 CSS 属性(颜色 + 尺寸最常见),用 CSS 变量时就需要类型提示。用字面值(如 #fff、2px)时不需要,因为 Tailwind 能自动推断。
可用的类型提示一览
Tailwind v4 支持的 CSS 数据类型提示:
| 类型关键词 | 匹配什么 | 示例值 |
|---|---|---|
color |
CSS 颜色 |
#fff、rgb(...)、oklch(...)
|
length |
长度 |
16px、1rem、2em
|
percentage |
百分比 | 50% |
number |
数值 |
1.5、0
|
integer |
整数 |
1、4
|
angle |
角度 |
45deg、0.25turn
|
url |
URL | url(...) |
image |
CSS 图片类型 |
url(...)、linear-gradient(...)
|
position |
位置 |
center、top left
|
ratio |
比例 | 16/9 |
line-width |
线宽 | 边框宽度值 |
bg-size |
背景尺寸 |
cover、contain
|
family-name |
字体族名 | 字体名称 |
速查表
把全文涉及的语法整理在一起,方便随时翻阅:
| 场景 | 语法 | 示例 |
|---|---|---|
| 字面任意值 | 工具类-[值] |
top-[117px]、bg-[#bada55]
|
| CSS 变量简写 | 工具类-(--变量) |
bg-(--brand-color) |
| CSS 变量 + var() | 工具类-[var(--变量)] |
bg-[var(--brand-color)] |
| 类型提示(圆括号) | 工具类-(类型:--变量) |
text-(color:--brand-color) |
| 类型提示(方括号) | 工具类-[类型:var(--变量)] |
text-[color:var(--brand-color)] |
| 任意属性 | [属性:值] |
[mask-type:luminance] |
| 设置 CSS 变量 | [--变量:值] |
[--scroll-offset:56px] |
| 任意变体 | [选择器]:工具类 |
[&:nth-child(3)]:underline |
| 空格用下划线 |
_ 代替空格 |
grid-cols-[1fr_500px_2fr] |
| 真正的下划线 |
_ 转义 |
content-['hello_world'] |
| JSX 中的转义 | String.raw`...` |
String.raw`content-['a_b']` |