tailwindcss4核心指南
本文第一接触 tailwindcss,要从入职 字屿画 开始这家公司开始。当时接手的 web3d 的项目就用到了 tailwindcss,那个时候应该还是 tailwind 2 还是 3,有点忘记了。
那时候我怀着满心的期待入门了 web3d,结果是我入门技术级别和公司优化需求不匹配。最后还是没有能在 web3d 的领域长久深耕。
最开始,我对 tailwind 的看法类似于一个样式库,内置了大量的 css 定义,还需要去记非常多的 class,我并没有多喜欢它。
后来,我逐渐理解的样式编写工作,在大部分情况下,很多网页更多是对盒子模型加个边距,设置一个背景,加一个圆角,再多就是加一个布局样式,就这么多 : )。
前期来看,记住这些常用的 class 并不困难,重要的是,在你希望给样式添加状态查询(伪类)时,tailwindcss 也能处理;它甚至还可以在 class 中编写媒体查询,做到响应式设计。与之对应的,需要记住更多 tailwindcss 句法,写出更加优秀的页面。
前言
本文并非一个介绍 tailwindcss 的介绍文章,面向的是具有一定的 tailwindcss 的使用经验的开发者。为此,你需要具备
- 了解
原子化 css - 熟练使用
[tailwindcss](https://tailwindcss.com/docs/installation/using-vite) - 了解 css layer
- 了解媒体查询
- 了解 css 容器查询
本文大部分经验来自于 tailwindcss 官方文档
一. 实用优先原则
tailwindcss 官方给出了详细的解释,总的来说:
- 你能更快完成任务——不用花时间想类名、做选择器决策,也不用在 HTML 和 CSS 文件之间切换,设计完成得非常快。
- 做修改感觉更安全——添加或移除一个工具类只会影响该元素,所以你不用担心不小心破坏使用相同 CSS 的其他页面。
- 维护旧项目更容易——改变某样东西只是找到项目中的那个元素,换类,而不是去回忆那些六个月没动过的自定义 CSS 是怎么工作的。
- 你的代码更可移植——因为结构和样式都在同一个地方,你可以轻松复制粘贴整个 UI 块,甚至在不同项目之间。
- 你的 CSS 停止增长——由于工具类极具重复使用性,你的 CSS 不会随着你为项目添加的每个新功能而线性增长。
1.1 状态
简单来说,状态包含鼠标悬浮、获得焦点、列表的偶数等伪类的
1.2 响应式
tailwindcss 响应式设计是移动优先的。响应式简单来说,就是根据不同屏幕尺寸的展示不同的页面信息布局。可以去体验 tailwindcss 官方案例
a. 为什么移动优先
我对移动优先设计理解,得从我个人经验出发,一般来讲,一个网页被分享出去,分享欲望最强和最方便的是移动端设备。
一个链接被分享出去时,大部分人会先点击链接进去看看是什么(钓鱼网站的目标)。如果用户觉得这个网站内容还不错,应该就分享出去了。而且移动设备是用户一定会带在身上的,因此它的接受面就是比 PC 端页面接受度更好,转发率也更好。
正如你将个人主页分享给别人后,在离开 PC 后,也会自己思考:朋友那边打开页面是什么体验?
b. tailwindcss 的移动优先
参考官方描述
简单来说,就是如果没有以 sm: md: lg: 开头的则表示所有匹配通用。
也就是最小屏幕到最大屏幕都适用,如果以 sm: 开头,则表示屏幕宽度达到 40rem(约 640px),则表示 sm: 后面的样式在屏幕大于宽度 640px 时适用。
如下结果:
<div className="md:text-blue-500 text-red-500 ">Tailwindcss</div>
则表示,当处于平板端时,Tailwindcss 在网页端显示的是蓝色的,比平板端小的时候则字体是红色的。
同时 md 或 sm 等查询不论先后顺序,都是有效的。
<div className=" text-red-500 md:text-blue-500">Tailwindcss</div>
其中 md:text-blue-500 最终的输出结果是
.md\:text-blue-500 {
@media (width >= 48rem){
color: var(--color-blue-500);
}
}
md:text-blue-500 会生成带 @media 的规则,但不会因此比 text-red-500 权重更高;二者都是单个 class,特异性相同。在达到 md 断点(width >= 48rem)时,两条规则都会生效,最终显示蓝色,是因为 Tailwind 按变体顺序生成 CSS:无变体的工具类在前,带 md: 等响应式变体的规则在后,后声明的覆盖了前面的红色。这与你在 HTML 里写 text-red-500 md:text-blue-500 还是 md:text-blue-500 text-red-500 无关——class 的书写顺序不影响结果。低于 md 断点时,@media 条件不成立,只有红色生效。
c. 可用断点
下面这张表是前面提到的 屏幕媒体查询断点。此处额外加了一个“相对比例”,用 320px(小屏基准) 做参考:比例 = 断点宽度 ÷ 320。
| 屏幕断点 | 最小宽度 | 像素 | 常见设备场景(大概) | 相对 320px 的比例 |
|---|---|---|---|---|
| 默认 | 0rem | 0 | 手机优先(不写前缀就是默认) | - |
| sm | 40rem | 640 | 大手机横屏 / 小平板 | 2.00× |
| md | 48rem | 768 | 平板 | 2.40× |
| lg | 64rem | 1024 | 笔记本 / 桌面 | 3.20× |
| xl | 80rem | 1280 | 大桌面 | 4.00× |
| 2xl | 96rem | 1536 | 超大屏 | 4.80× |
d. 容器查询
在媒体查询之后,在 2023 年 css 引入了容器查询的能力。如果说媒体查询是根据视口查询的话,那么容器查询就是根据父级元素作为查询锚点。
例如
<div className="@container contain-inline-size text-red-500">
<div className=" @xl:text-blue-500 @xl:text-2xl">
Tailwindcss
</div>
</div>
此处,当声明 class 为 @container 所在元素宽度大于一个大屏手机的宽度时,声明 @xl:text-blue-500 @xl:text-2xl 的子元素则颜色变为蓝色,且字体变大。
下面这组是 Tailwind 默认提供的 container query 断点(从小到大,规则都是“容器宽度 >= 某个值就生效”):
| Variant(前缀) | 最小容器宽度 | 像素(约) | 相当于屏幕级别 |
|---|---|---|---|
| @3xs | 16rem | 256px | |
| @2xs | 18rem | 288px | |
| @xs | 20rem | 320px | 一个小屏幕 |
| @sm | 24rem | 384px | |
| @md | 28rem | 448px | |
| @lg | 32rem | 512px | |
| @xl | 36rem | 576px | 大屏手机 |
| @2xl | 42rem | 672px | 大屏手机还大 |
| @3xl | 48rem | 768px | 平板 |
| @4xl | 56rem | 896px | |
| @5xl | 64rem | 1024px | 笔记本/桌面 |
| @6xl | 72rem | 1152px | |
| @7xl | 80rem | 1280px | 大桌面 |
二. tailwindcss 如何运行
以上通过状态查询和媒体查询的 前缀:样式类名 的写法,关键在于前缀如何运行?这点需要结合 tailwindcss 运行原理说起。理解它的运行原理有利于帮助在使用 tailwindcss 更加有信心。
在 tailwind 3.x 时代,tailwindcss 还是一个内置了大量 class 的框架,因此 [antfu](https://antfu.me/) 专门写了个 [unocss](https://unocss.dev/) 来解决这个缺陷。现在 tailwindcss 4.x 已经是编译型的库了,做到了产物的 class 一定会被使用到,产物非常干净。
甚至 tailwindcss 的配置也不需要 js 文件,而是通过类似于 css at 规则的方式声明配置,然后编辑器识别这些非原生 css 的 at 规则,实现配置 tailwindcss 的目的。
2.1 编译时框架
在 tailwindcss 3.x 时代,tailwindcss 内置了非常多的工具 class,虽然它也引入了一部分编译能力,即结合 postcss 一类的 css 预处理工具为其瘦身,也就是去掉不需要的 class。
现在,tailwindcss 更多是生成 class,是编译后做增量,也就是用到哪些,则增加哪些 class。
早期 tailwindcss 使用 js 做编译,现在 tailwindcss 使用 rust 编译。
因为早期历史因素,还遗留了一部分传统 js 编译插件的入口,现在 tailwindcss 已经不再推荐用了,更多建议使用纯 css 来配置 tailwindcss 编译时的行为。
弃用或不建议继续使用的内容
- 配置文件
tailwind.config.j|ts - css 文件指令
@plugin - css 文件指令
@config
2.2 css layer
tailwindcss 中采用 css layer 的方式组织样式层叠冲突问题,又不会加重传统上 css 的样式覆盖问题。 典型的国内的 react ui 库 antd: 他的class就是具有两层css, 如果基于antd 再封装一层组件, 那么大概率会出现css权重战争. 因此 tailwindcss 就使用 css layer 用于解决前端工程化的该问题。如果你不了解 css layer, 可以去文档, 有些博客也有相关介绍
核心样式层(css layer)包含:theme,base(preflight),components,utilities
接下来将介绍 tailwindcss 这四大 css layer。
2.3 theme: 颜色与主题
这是一个大主题,包含了响应式的断点,主题颜色,几乎所有往后的层中都会用到该层内容。
快速参考
2.4 base: 重设默认样式
根据文档中所描述的,该层使用 modern-normalize 库,重设了所有默认浏览器样式,在与其他库结合使用时,可能会出现一些意想不到的情况。
如果你想让网页上所有的 <h1> 默认就是 2rem,或者所有的 <a> 标签默认没有下划线,就写在这里。
按照文档说明,在使用了其他样式重置库时,不想使用 tailwindcss 的重置,则使用以下方式导入 tailwindcss 即可:
@import "tailwindcss";
这样编译后的结果就是:
@layer theme, base, components, utilities;
@import "tailwindcss/theme.css" layer(theme);
@import "tailwindcss/preflight.css" layer(base); /*此处就是重设浏览器默认样式*/ */
@import "tailwindcss/utilities.css" layer(utilities);
如果不需要 tailwindcss 重设浏览器默认样式,则采用指定 tailwindcss layer 的方式声明:
@layer theme, base, components, utilities;
@import "tailwindcss/theme.css" layer(theme);
@import "tailwindcss/preflight.css" layer(base); /*删除此行*/
@import "tailwindcss/utilities.css" layer(utilities);
常见用法:
@layer base {
body {
background-color: var(--color-gray-50); /* 让全局背景变灰 */
}
h1 {
font-weight: 700; /* 全局 h1 加粗 */
}
}
快速参考
2.5 components
该层专门用来存放复杂的、高复用的自定义类名。当你发现某些原子类组合在 HTML 里写得太长,或者在使用第三方 UI 库时,就可以在这里做封装。
它的优先级高于 base,但低于 utilities。这意味着如果你在 components 里写了一个 .btn 自定义组件,你在 HTML 里依然可以用 Tailwind 的原子类(utilities 层)去微调它。
例如 daisyui。
2.6 utilities
这是 Tailwind 的核心与灵魂。你在 HTML 里写的绝大多数类名(如 flex、pt-4、text-center、shadow-lg)全部都在这一层。
它是权重最高的一层(除了加了 ! 的样式)。这意味着只要你把一个原子类写在 HTML 上,它就绝对能覆盖掉来自 base 和 components 层的同名属性。如果你有自己手写的、必须能一锤定音的单属性工具类,就应该放在这里。
常见用法:
@layer utilities {
/* 自定义一个强力的内容居中工具类 */
.flex-center {
display: flex;
align-items: center;
justify-content: center;
}
}
NOTE
以上是 tailwindcss 主体内容结构,以及它如何在页面中安排 css 规则的优先级。
以上内容并没有解释类似于 前缀:样式类 如何做到媒体查询/伪类/伪元素如何实现的。
要做到动态生成媒体查询的效果,需要借助编译器帮忙实现
让编译器识别并编译的办法就是自定义 at 规则。下文中指令 @theme/@source/@custom-variant 本质上就是给编译器喂规则。
在 tailwindcss 的语境下,称为指令,因为它并不是 css 的 at 规则所支持的。
三. CSS 文件中的指令
指令的作用和 css at 规则类似,这些指令在原生 css 不存在,通过编译器识别并处理。因此像 @media 一样,@media 后面可以接一些属于它自己的后缀(媒体类型查询);指令与之对应的也存在自己的后缀。只不过这个后缀由 css 预处理(编译器)帮忙处理,而不是浏览器。
tailwindcss 这些指令用于配置最终 css 编译效果,包含
- @import
- @theme
- @source
- @utility
- @variant
- @custom-variant
- @reference
下文将详细介绍。
3.1 @import
这里有点奇怪的是,@import 是 css 官方的 at 规则,但是这里在 tailwindcss 的语境下发生了些许变化。
它将 @import 定义为了 tailwindcss 自己的语法指令。事实上,@import 不仅被 tailwindcss 拦截,还被 postcss 拦截,也被 vite 拦截。
理由很简单,因为在前端工程化的语境下,@import 不可能按照浏览器的规则去解析 css,这会显著降低打包效率。另外打包环境中有些路径是可以使用别名导入的。这个别名的能力在浏览器语境下,是实现不了的。处于以上原因考虑,现代工程化前端中,大概率 @import 规则会被拦截,并被特殊处理。正如 @import "tailwindcss"; 这句导入一样,如果按照浏览器规则导入,就是个错误的语法,根本不生效,但是这里为什么生效了?就是因为构建工具在背后做的工作。
@import 除了支持原生的后缀修饰符外,还额外支持了一些修饰符
- source 用来声明 tailwindcss 扫描编译原子类(class)的起始文件夹位置,一般来说,仅用于在
@import "tailwindcss"时使用该后缀 - theme 参考指令
@theme - prefix 为所有生成原子类前面加上前缀,此后缀用于避免特定 class 出现冲突,例如和某些 ui 库
- important
补充:最常见入口(以及如何控制 layer)
Tailwind 4 里最常见的是在你的入口 CSS(例如 src/styles.css)里写:
@import "tailwindcss";
等价于把 Tailwind 拆分成 theme / preflight / utilities 三段按 layer 注入。
项目里更“可控”的写法是显式声明层级并选择性导入:
@layer theme, base, components, utilities;
@import "tailwindcss/theme.css" layer(theme);
@import "tailwindcss/preflight.css" layer(base);
@import "tailwindcss/utilities.css" layer(utilities);
场景 A:禁用 preflight(避免与第三方 UI / reset 冲突)
- 如果项目里已经有
modern-normalize/normalize.css或某个 UI 框架自带 reset,并且你不希望 Tailwind 额外重置,建议采用“显式导入 + 去掉 preflight”的方式。 - 同时要在 README/样式规范里写清楚:我们禁用了 preflight,因此某些基础元素样式(h1、a、button)不会像 Tailwind demo 那样统一。
场景 B:给 Tailwind 的类加前缀(避免与别的 CSS 工具库冲突)
当你和一些老项目、或某些 UI 库(例如它们也有 .container、.btn)混用时,可以考虑“统一前缀”。
- 统一前缀能减少冲突
- 代价是 class 会变长、认知成本上升
(不同构建链对 @import 附加参数支持略有差异;如果你在项目里确实需要这条能力,建议以官方文档/实际编译结果为准。)
3.2 @theme
开发者在自定义主题时,就会涉及到此指令。
它是项目的“设计系统(Design System)大本营”,专门用来定义所有的设计基础标记(Tokens),比如颜色、间距、字体大小、阴影、圆角、屏幕断点等。
它不会直接生成任何可以直接写在 HTML 里的“类名”(如 .bg-blue-500),它的唯一任务是在内存中注册 CSS 变量,为后续的其他层提供数据源。
用法:
@theme {
--color-brand: #ff0000; /* 定义变量 */
}
@theme 的后缀包含如下内容:
- inline
- static
参考
补充:把设计系统 Token 变成可被 utility 消费的数据源
最小用法:
@theme {
--color-brand: #4f46e5;
--radius-card: 14px;
}
然后就可以在“自定义 utility”里消费这些变量:
@utility card {
border-radius: var(--radius-card);
background: color-mix(in oklab, var(--color-brand) 8%, white);
}
场景:做主题切换 / 多品牌
@theme {
--color-bg: white;
--color-fg: #111827;
}
.theme-dark {
--color-bg: #0b1220;
--color-fg: #e5e7eb;
}
组件里:
<div class="bg-bg text-fg">
...
</div>
3.3 @source
额外声明 tailwindcss 的文件扫描位置。
默认情况下,Tailwind 会扫描项目中的每个文件以查找类名,但以下情况除外:
.gitignore文件中的文件node_modules目录中的文件- 二进制文件,例如图像、视频或压缩文件
- CSS 文件
- 常用软件包管理器锁定文件
@source 额外后缀标识
- inline
- not
- not inline
补充:monorepo / 多入口最常用
默认扫描规则在一些工程结构下不一定够用,例如:
- monorepo:UI 组件在
packages/ui,应用在apps/web - Astro/MDX:class 写在
.md/.mdx或内容目录
你可以在入口 CSS 里补充扫描源:
@source "./src/**/*.{ts,tsx,vue,svelte,astro,md,mdx}";
@source "../packages/ui/src/**/*.{ts,tsx}";
经验:只加必要范围。
3.4 @utility
用于添加自定义的原子类。与普通 class 不太一样的是,它具有编译作用:定义一个原子类的编写模式,它将按照规则转化为目标规则。
可以参考 tailwindcss 官方文档
补充:写可复用的单元,让它像内置 utility 一样工作
@utility flex-center {
display: flex;
align-items: center;
justify-content: center;
}
使用:
<div class="flex-center min-h-40">...</div>
场景:团队统一交互手感
@utility pressable {
transition: transform 120ms ease, filter 120ms ease;
}
@custom-variant active (&:active);
@utility pressable {
will-change: transform;
}
组件:
<button class="pressable active:scale-[0.98]">...</button>
3.5 @variant
不常用,但可以参考文档
3.6 @custom-variant
贯穿整个 tailwindcss,几乎所有的状态、元素、媒体查询、容器查询、用户偏好等等,都通过此指令完成并编译。可参考 变体 部分内容。
3.7 @apply
倘若想在原生 css 中复用 tailwindcss 的原子类可以使用此指令,使用此指令一般需要结合 @reference 使用。
补充:在写原生 CSS 的地方复用 utility
.card {
@apply rounded-xl bg-white/80 shadow-sm ring-1 ring-black/5;
}
.card:hover {
@apply shadow-md;
}
在一些单文件组件(Astro/Vue/Svelte)中,为了让 Tailwind 编译器能识别并生成这些 @apply 里引用的类,可能需要 @reference(具体取决于构建链/插件实现):
@reference "tailwindcss";
.card {
@apply rounded-xl bg-white/80;
}
建议以你当前构建工具的编译结果为准。
3.8 @reference
此指令通常用于 Astro / Vue / Svelte 的 style 块,通常结合 @apply 指令使用。
3.9 @plugin
不推荐使用。传统上此指令用于 postcss 中的指令,但 postcss 本身已经抛弃了。该指令主要用于将 css 的上下文传入到 @plugin 后面的 js 文件中,js 文件的函数用于处理这个上下文,并根据上下文信息返回新的 css 规则。
3.10 @config
不推荐使用。传统上用于引入额外的 tailwind.config.js 配置。现在 tailwindcss 通过 css 指令替换传统的 js 配置,所以已经不再需要 js 配置文件了。
快速参考
四. 变体
变体由上文提到的 @custom-variant 指令。
变体 概念贯穿 tailwindcss 整个框架,只要你在使用 (变体)前缀:样式类,那么就会使用到该指令。
什么是变体
简单理解,在写入 变体(前缀):样式类 中的 变体 + : 就是变体,它使用 @custom-variant 定义。例如我要定义当前用户偏好是暗色主题时。
@custom-variant dark (&:where(.dark, .dark *));
这行代码的含义是:自定义一个名为 dark 的变体,当页面中存在 .dark 类时,所有使用 dark: 前缀的工具类都会生效。
其中 & 代表最终生成的工具类选择器,:where(.dark, .dark *) 表示匹配 .dark 元素本身以及 .dark 下面的所有子元素。
因此下面这种写法:
<div class="dark">
<button class="bg-white text-black dark:bg-black dark:text-white">
按钮
</button>
</div>
在外层存在 dark 类时,dark:bg-black 和 dark:text-white 才会生效。
上面的类最终可以理解为生成了下面这些 CSS:
.bg-white {
background-color: var(--color-white);
}
.text-black {
color: var(--color-black);
}
.dark\:bg-black:where(.dark, .dark *) {
background-color: var(--color-black);
}
.dark\:text-white:where(.dark, .dark *) {
color: var(--color-white);
}
再例如,可以定义一个 hover 变体:
@custom-variant hover (&:hover);
这表示所有使用 hover: 前缀的工具类,都会在最终选择器上追加 :hover 状态。
常用的变体
快速参考
自定义变体
使用指令 @custom-variant。
补充 1:dark: 类策略 vs 媒体策略
常见有两种 dark mode 策略:
- 类切换:加
.dark类 - 媒体查询:跟随系统
prefers-color-scheme
可以分别用不同的 @custom-variant 表达(示意):
@custom-variant dark (&:where(.dark, .dark *));
@custom-variant os-dark (@media (prefers-color-scheme: dark));
使用:
<div class="bg-white dark:bg-black os-dark:ring-1 os-dark:ring-white/10">...</div>
补充 2:data-* 变体(和 Headless UI/组件状态配合)
很多组件库会在 DOM 上打 data-state="open" / data-disabled,你可以用 attribute selector 做变体,让 class 更语义化:
@custom-variant open (&[data-state="open"]);
@custom-variant disabled (&[data-disabled="true"], &[aria-disabled="true"]);
使用:
<button data-state="open" class="bg-white open:bg-blue-50 disabled:opacity-50">...</button>
补充 3:group-* / peer-* 的本质
group-hover: 不是“子元素 hover”,而是“父元素 hover 时,改变子元素”。
<a class="group flex items-center gap-2">
<span class="i-lucide-star opacity-0 group-hover:opacity-100"></span>
<span>收藏</span>
</a>
peer-* 则适合表单:
<input class="peer" placeholder=" " />
<label class="text-gray-500 peer-focus:text-blue-600">邮箱</label>
补充 4:容器查询变体 @xl:(让组件真正可嵌入复用)
<section class="@container contain-inline-size">
<article class="grid gap-4 @lg:grid-cols-[160px_1fr]">
<img class="aspect-video w-full rounded-lg object-cover @lg:aspect-square" />
<div>
<h3 class="text-base @lg:text-lg">标题</h3>
<p class="text-sm text-gray-600 @lg:text-base">内容...</p>
</div>
</article>
</section>
要点:
- 组件不再依赖“整个页面的 viewport”,而依赖“它所在容器”的宽度
- 同一组件在侧栏/主栏/卡片里都能自然响应
补充 5:自定义组合变体(把复杂选择器固化成团队规范)
@custom-variant focus-ring (&:focus-visible);
@custom-variant hocus (&:hover, &:focus-visible);
使用:
<button class="hocus:bg-gray-100 focus-ring:outline focus-ring:outline-2 focus-ring:outline-blue-500">...</button>