《从 0 到 1 理解 Tailwind CSS 核心:工具类、@layer 指令、响应式断点,掌握这些才算真入门》

Tailwind CSS 核心概念篇

1. 实用优先基本原理

Tailwind CSS 采用实用优先的方法,这种方法不同于传统的 CSS 编写方式。在实用优先范式中,你直接在 HTML 元素上应用预定义的类,而不是创建自定义 CSS 规则。

1.1 传统 CSS 方法 vs Tailwind 方法

传统方法:编写自定义 CSS 样式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<template>
<div class="chat-notification">
<div class="chat-notification-logo-wrapper">
<img class="chat-notification-logo" src="/img/logo.svg" alt="ChitChat Logo">
</div>
<div class="chat-notification-content">
<h4 class="chat-notification-title">ChitChat</h4>
<p class="chat-notification-message">You have a new message!</p>
</div>
</div>

</template>

<style>
.chat-notification {
display: flex;
align-items: center;
max-width: 24rem;
margin: 0 auto;
padding: 1.5rem;
border-radius: 0.5rem;
background-color: #fff;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}

.chat-notification-logo-wrapper {
flex-shrink: 0;
}

.chat-notification-logo {
height: 3rem;
width: 3rem;
}

.chat-notification-content {
margin-left: 1.5rem;
}

.chat-notification-title {
color: #1a202c;
font-size: 1.25rem;
line-height: 1.25;
}

.chat-notification-message {
color: #718096;
font-size: 1rem;
line-height: 1.5;
}
</style>

Tailwind 方法:直接在 HTML 中使用实用类

1
2
3
4
5
6
7
8
9
<div class="p-6 max-w-sm mx-auto bg-white rounded-xl shadow-lg flex items-center gap-x-4">
<div class="shrink-0">
<img class="size-12" src="/img/logo.svg" alt="ChitChat Logo">
</div>
<div>
<div class="text-xl font-medium text-black">ChitChat</div>
<p class="text-slate-500">You have a new message!</p>
</div>
</div>

1.2 实用优先方法的优势

优势描述
无需发明类名不用创建像 sidebar-inner-wrapper 这样的类名,只需使用 Tailwind 提供的实用类
CSS 不再增长传统方法中,每添加一个新功能,CSS 文件就会变大;使用实用类,一切都可重用
更安全地进行更改CSS 是全局性的,而 HTML 中的类是局部的,可以更改而不用担心破坏其他内容

1.3 与内联样式的区别

虽然实用优先方法看起来像内联样式,但有几个重要优势:

  1. 设计约束:内联样式中,每个值都是 “魔术数字”;实用类使用预定义的设计系统,更容易构建视觉一致的界面
  2. 响应式设计:内联样式不能使用媒体查询,而 Tailwind 的响应式实用类可以轻松构建响应式界面
  3. 悬停、焦点和其他状态:内联样式无法定位悬停或焦点等状态,Tailwind 的状态变体使这变得简单
1
2
<!-- 完全响应式,包含悬停和焦点状态的按钮 -->
<button class="px-4 py-1 text-sm text-purple-600 font-semibold rounded-full border border-purple-200 hover:text-white hover:bg-purple-600 hover:border-transparent focus:outline-none focus:ring-2 focus:ring-purple-600 focus:ring-offset-2">Message</button>

1.4 可维护性考虑

使用实用优先方法最大的可维护性问题是管理常见的实用类组合。这可以通过以下方式解决:

  1. 提取组件和局部模板
1
2
3
4
5
6
<!-- PrimaryButton.vue -->
<template>
<button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
<slot/>
</button>
</template>
  1. 使用编辑器多光标编辑和简单循环

多光标编辑详解

多光标编辑是在同一个文件中处理重复样式的高效方法,无需创建组件或额外的抽象层。

实例演示:

假设你有一个导航栏,其中包含多个相似样式的链接:

1
2
3
4
5
6
<nav class="flex space-x-4">
<a href="/home">Home</a>
<a href="/about">About</a>
<a href="/services">Services</a>
<a href="/contact">Contact</a>
</nav>

现在你需要给所有链接添加相同的样式。使用多光标编辑:

  1. 在 VS Code 中,按住 Alt (Windows) 或 Option (Mac) 键,然后在每个 <a> 标签后点击鼠标
  2. 当你在所有需要的位置都有光标后,开始输入 class=",然后添加所需的样式类
  3. 所有选择位置将同时更新

结果如下:

1
2
3
4
5
6
<nav class="flex space-x-4">
<a href="/home" class="px-4 py-2 text-gray-700 hover:text-gray-900 hover:bg-gray-100 rounded">Home</a>
<a href="/about" class="px-4 py-2 text-gray-700 hover:text-gray-900 hover:bg-gray-100 rounded">About</a>
<a href="/services" class="px-4 py-2 text-gray-700 hover:text-gray-900 hover:bg-gray-100 rounded">Services</a>
<a href="/contact" class="px-4 py-2 text-gray-700 hover:text-gray-900 hover:bg-gray-100 rounded">Contact</a>
</nav>

循环处理详解

当元素在页面上重复出现但在代码中只需定义一次时,循环是最有效的方法。

实例演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!-- Vue 示例 -->
<template>
<nav class="flex space-x-4">
<a
v-for="item in navItems"
:key="item.path"
:href="item.path"
class="px-4 py-2 text-gray-700 hover:text-gray-900 hover:bg-gray-100 rounded"
>
{{ item.name }}
</a>
</nav>
</template>

<script>
export default {
data() {
return {
navItems: [
{ name: 'Home', path: '/home' },
{ name: 'About', path: '/about' },
{ name: 'Services', path: '/services' },
{ name: 'Contact', path: '/contact' }
]
}
}
}
</script>

2. 悬停、焦点和其他状态

Tailwind 中的每个实用类都可以通过添加修饰符来有条件地应用,这些修饰符描述你想要定位的条件。

2.1 基本用法

1
2
3
4
<!-- 悬停时将背景色从 sky-500 变为 sky-700 -->
<button class="bg-sky-500 hover:bg-sky-700 ...">
Save changes
</button>

传统 CSS 与 Tailwind 的区别:

  • 传统方法:同一个类名在不同状态下做不同的事情
1
2
3
4
5
6
.btn-primary {
background-color: #0ea5e9;
}
.btn-primary:hover {
background-color: #0369a1;
}
  • Tailwind 方法:针对不同状态使用不同的类
1
2
3
4
5
6
.bg-sky-500 {
background-color: #0ea5e9;
}
.hover\:bg-sky-700:hover {
background-color: #0369a1;
}

2.2 修饰符类型

Tailwind 包含多种类型的修饰符:

2.2.1 伪类修饰符

修饰符描述示例
hover:鼠标悬停时hover:bg-blue-700
focus:元素获得焦点时focus:outline-none
active:元素被点击时active:bg-blue-800
first:第一个子元素first:pt-0
last:最后一个子元素last:pb-0
odd:奇数子元素odd:bg-gray-100
even:偶数子元素even:bg-white
disabled:禁用状态disabled:opacity-50
required:必填状态required:border-red-500
invalid:无效状态invalid:border-red-500
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
</head>

<body>
<button class="bg-blue-400 hover:bg-blue-500 active:bg-blue-600 text-white px-4 py-2 rounded-md">按钮</button>
</body>

</html>

2.2.2 基于父元素状态的样式 (group-*)

当你需要根据 父元素 的状态来设置 子元素 的样式时,在父元素上添加 group 类,然后在子元素上使用 group-* 修饰符。

修饰符描述示例
group-hover:父元素 group 被悬停时group-hover:text-white
group-focus:父元素 group 获得焦点时group-focus:ring-2
group-active:父元素 group 被点击时group-active:bg-blue-100
group/{name}:用于嵌套 group 时命名group-hover/item:text-blue-500

示例:

当鼠标悬停在作为父元素的 <div> 上时,图标和文本的颜色会改变。

1
2
3
4
5
6
<a href="#" class="group block p-4 border">
<div>
<h5 class="text-gray-900 group-hover:text-blue-600">首页</h5>
<p class="text-gray-500 group-hover:text-blue-400">查看应用概览</p>
</div>
</a>

2.2.3 基于兄弟元素状态的样式 (peer-*)

当你需要根据一个 兄弟元素 的状态来设置 另一个兄弟元素 的样式时,在前一个兄弟元素上添加 peer 类,然后在目标元素上使用 peer-* 修饰符。

⚠️ 注意peer 修饰符只对 后续 的兄弟元素生效,这是由 CSS 的工作方式决定的。

修饰符描述示例
peer-hover:peer 兄弟元素被悬停时peer-hover:text-green-500
peer-focus:peer 兄弟元素获得焦点时peer-focus:visible
peer-checked:peer 兄弟元素 (如复选框) 被选中时peer-checked:bg-blue-500
peer-invalid:peer 兄弟元素 (如输入框) 内容无效时peer-invalid:block
peer-disabled:peer 兄弟元素被禁用时peer-disabled:opacity-50

示例:

<input> 元素进入 focus 状态,后面的提示信息 <p> 才会变得可见。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- peer-focus 示例 -->
<div class="space-y-2">
<h2 class="text-lg font-semibold">peer-focus 焦点效果</h2>
<div class="space-y-2">
<input
type="text"
class="peer w-full px-3 py-2 border rounded focus:ring-2 focus:ring-blue-500"
placeholder="点击输入框获得焦点"
/>
<p
class="text-gray-400 peer-focus:text-blue-500 peer-focus:visible invisible"
>
输入框已获得焦点!
</p>
</div>
</div>

⚠️ 注意peer 标记只能用于 前面的 兄弟元素,因为 CSS 的相邻兄弟选择器的工作方式。

2.3 伪元素修饰符

修饰符描述示例
before:::before 伪元素before:content-['*']
after:::after 伪元素after:content-['↗']
placeholder:占位符文本placeholder:italic
file:文件输入按钮file:bg-violet-50
marker:列表标记marker:text-sky-400
selection:文本选择selection:bg-fuchsia-300
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
</head>
<body>
<div class="container mx-auto p-8">
<h1 class="text-3xl font-bold mb-6 text-blue-600">
Tailwind CSS 伪元素和修饰符演示
</h1>

<!-- before 伪元素演示 -->
<div class="mb-8">
<h2 class="text-xl font-semibold mb-3">before: 修饰符</h2>
<p
class="before:content-['*'] before:text-red-500 before:mr-1 text-gray-700"
>
这是一个必填字段,使用 before 添加星号
</p>
</div>

<!-- after 伪元素演示 -->
<div class="mb-8">
<h2 class="text-xl font-semibold mb-3">after: 修饰符</h2>
<a href="#" class="text-blue-500 after:content-['↗'] after:ml-1">
外部链接示例
</a>
</div>

<!-- placeholder 修饰符演示 -->
<div class="mb-8">
<h2 class="text-xl font-semibold mb-3">placeholder: 修饰符</h2>
<input
type="text"
placeholder="斜体占位符文本"
class="border p-2 rounded placeholder:italic placeholder:text-gray-400 w-64"
/>
</div>

<!-- file 修饰符演示 -->
<div class="mb-8">
<h2 class="text-xl font-semibold mb-3">file: 修饰符</h2>
<input
type="file"
class="file:bg-violet-50 file:border-0 file:py-2 file:px-4 file:rounded file:text-violet-700 file:font-semibold hover:file:bg-violet-100"
/>
</div>

<!-- marker 修饰符演示 -->
<div class="mb-8">
<h2 class="text-xl font-semibold mb-3">marker: 列表标记 修饰符</h2>
<ul class="list-disc pl-6 marker:text-sky-400">
<li>第一个列表项</li>
<li>第二个列表项</li>
<li>第三个列表项</li>
</ul>
</div>

<!-- selection 修饰符演示 -->
<div class="mb-8">
<h2 class="text-xl font-semibold mb-3">selection: 修饰符</h2>
<p class="selection:bg-fuchsia-300 selection:text-fuchsia-900">
选择这段文本查看自定义选择效果。尝试用鼠标选择这些文字,会显示自定义的背景色和文字颜色。
</p>
</div>
</div>
</body>
</html>

3. 响应式设计

Tailwind 提供了强大的响应式设计功能,通过使用响应式修饰符,你可以轻松创建适应不同屏幕尺寸的界面。

3.1 移动优先方法

Tailwind 采用移动优先的策略,这意味着不带响应式修饰符的实用类首先应用于所有屏幕尺寸,然后使用响应式修饰符可以在特定断点上覆盖这些样式。

1
2
3
4
<!-- 默认在移动设备上是3列,中等屏幕4列,大屏幕6列 -->
<div class="grid grid-cols-3 md:grid-cols-4 lg:grid-cols-6">
<!-- ... -->
</div>

3.2 默认断点

修饰符屏幕宽度CSS
sm:640px@media (min-width: 640px) { ... }
md:768px@media (min-width: 768px) { ... }
lg:1024px@media (min-width: 1024px) { ... }
xl:1280px@media (min-width: 1280px) { ... }
2xl:1536px@media (min-width: 1536px) { ... }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
</head>

<body>
<div class="p-8">
<div
class="bg-red-400 sm:bg-blue-400 md:bg-green-400 lg:bg-yellow-400 xl:bg-purple-400 2xl:bg-pink-400 p-4 rounded-lg text-white text-center">
<p class="text-sm sm:text-base md:text-lg lg:text-xl xl:text-2xl 2xl:text-3xl">
响应式断点演示
</p>
<p class="mt-2 text-xs sm:text-sm md:text-base lg:text-lg xl:text-xl 2xl:text-2xl">
调整浏览器宽度查看不同断点效果
</p>
</div>
</div>
</body>
</html>

4. 深色模式

Tailwind 包含一个内置的 dark 变体,使你可以在深色模式下轻松设置不同样式。

4.1 基本用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<!-- 一般来说在配置文件里配置这一行,我们是CDN所以就在这里配置 -->
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
darkMode: "selector",
};
</script>
</head>

<body>
<button
id="theme-toggle"
class="mb-4 px-4 py-2 bg-slate-200 dark:bg-slate-700 text-slate-800 dark:text-slate-200 rounded-md hover:bg-slate-300 dark:hover:bg-slate-600"
>
切换暗色模式
</button>

<div
class="bg-white dark:bg-slate-800 rounded-lg px-6 py-8 ring-1 ring-slate-900/5 shadow-xl"
>
<h3
class="text-slate-900 dark:text-white mt-5 text-base font-medium tracking-tight"
>
Writes Upside-Down
</h3>
<p class="text-slate-500 dark:text-slate-400 mt-2 text-sm">
The Zero Gravity Pen can be used to write in any orientation, including
upside-down.
</p>
</div>
</body>

<script>
document
.getElementById("theme-toggle")
.addEventListener("click", function () {
document.documentElement.classList.toggle("dark");
});
</script>
</html>

4.2 深色模式策略

默认情况下,Tailwind 使用 prefers-color-scheme CSS 媒体特性来检测用户的系统偏好。你可以在 tailwind.config.js 中配置深色模式策略:

4.2.1 媒体查询策略(默认)

1
2
3
4
5
// tailwind.config.js
module.exports = {
darkMode: 'media', // 使用媒体查询
// ...
}

4.2.2 类选择器策略(手动切换)

1
2
3
4
5
// tailwind.config.js
module.exports = {
darkMode: 'selector', // Tailwind v3.4.1+ 使用 selector 代替原来的 class
// ...
}

使用 selector 策略时,dark: 类将在 HTML 树中的早期存在 dark 类时应用:

1
2
3
4
5
6
7
8
9
<!-- 启用深色模式 -->
<html class="dark">
<body>
<!-- 将会是黑色 -->
<div class="bg-white dark:bg-black">
<!-- ... -->
</div>
</body>
</html>

4.3 手动切换深色模式

通过 JavaScript 来控制深色模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 根据本地存储切换深色模式
// 在你的应用初始化脚本中
if (localStorage.getItem('darkMode') === 'dark' ||
(!localStorage.getItem('darkMode') &&
window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}

// 切换函数
function toggleDarkMode() {
if (document.documentElement.classList.contains('dark')) {
document.documentElement.classList.remove('dark');
localStorage.setItem('darkMode', 'light');
} else {
document.documentElement.classList.add('dark');
localStorage.setItem('darkMode', 'dark');
}
}

5. 重用样式

随着项目的增长,你会发现自己重复使用相同的实用类组合。以下是几种重用样式的方法:

5.1 提取组件和模板部分

对于需要在多个文件中重用的样式,最好的策略是创建组件(如果使用前端框架)或模板部分(如果使用模板语言)。

1
2
3
4
5
6
7
8
// React 组件示例
function Button({ children }) {
return (
<button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
{children}
</button>
);
}

5.2 使用 @apply 提取样式

当创建模板部分感觉过重时,可以使用 Tailwind 的 @apply 指令将重复的实用类模式提取到自定义 CSS 类中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="https://cdn.tailwindcss.com"></script>
<style type="text/tailwindcss">
@layer components {
.btn-base {
@apply px-4 py-2 rounded font-medium transition-colors;
}
}
</style>
</head>
<body class="p-8 space-y-4">
<button class="btn-base bg-blue-500 hover:bg-blue-600 text-white">
蓝色按钮
</button>
<button class="btn-base bg-green-500 hover:bg-green-600 text-white">
绿色按钮
</button>
<button class="btn-base bg-red-500 hover:bg-red-600 text-white">
红色按钮
</button>
<button class="btn-base bg-gray-200 hover:bg-gray-300 text-gray-800">
灰色按钮
</button>
</body>
</html>

⚠️ 注意:避免过早的抽象化。不要仅仅为了让 HTML 看起来更整洁而使用 @apply。这样会丢失 Tailwind 提供的工作流和维护优势。

6. 添加自定义样式

6.1 自定义主题

tailwind.config.js 文件中自定义颜色、间距、排版和断点等:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// tailwind.config.js
module.exports = {
theme: {
extend: {
colors: {
primary: '#3490dc',
secondary: '#ffed4a',
danger: '#e3342f',
},
spacing: {
'72': '18rem',
'84': '21rem',
'96': '24rem',
}
}
}
}

6.2 使用任意值

当你需要突破设计约束时,使用方括号标记法生成具有任意值的类:

1
2
3
4
5
6
7
<div class="top-[117px] lg:top-[344px]">
<!-- ... -->
</div>

<div class="bg-[#bada55] text-[22px] before:content-['Festivus']">
<!-- ... -->
</div>