第 23 章:Tailwind CSS
学习目标
- 掌握 Tailwind 核心概念(utility-first)
- 熟练使用常用类名
- 配置自定义主题与插件
- 避开 Tailwind 的 4 个常见坑
一、Tailwind 是什么?
Utility-first CSS 框架,直接在 JSX 写类名生成样式,不用写 CSS 文件:
tsx
<button className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600">
点我
</button>优势:
- 不用想类名(
.btn-primary) - 局部样式,无冲突
- 构建时 PurgeCSS,产物极小
- 与组件库(shadcn)无缝
二、Next.js 中已内置
create-next-app 时勾选 Tailwind 即可。配置文件 tailwind.config.ts:
ts
import type { Config } from 'tailwindcss';
export default {
content: ['./src/**/*.{ts,tsx}'],
theme: {
extend: {
colors: {
brand: '#5B9DF9',
},
},
},
} satisfies Config;三、核心类名速查
布局
tsx
<div className="flex items-center justify-between gap-4 p-6">
<div className="grid grid-cols-3 gap-2">flex/grid/block/hiddenitems-center/justify-betweengap-4/gap-x-2/gap-y-4p-4/px-6/py-2
尺寸
tsx
<div className="w-full max-w-md h-screen">w-1/2/w-full/w-[300px](任意值)min-h-screen/h-10(1 = 0.25rem = 4px)
颜色
tsx
<div className="bg-blue-500 text-white hover:bg-blue-600">色阶:50 100 200 ... 900(bg-blue-500 是主色)
文字
tsx
<p className="text-lg font-bold text-gray-700 leading-relaxed">圆角边框
tsx
<div className="rounded-lg border border-gray-200 shadow-md">响应式
tsx
<div className="text-sm md:text-base lg:text-lg">断点:sm 640 / md 768 / lg 1024 / xl 1280 / 2xl 1536
⚠️ 坑 1:移动优先,无前缀 = 移动端,
md:起才是 ≥768px。
四、状态变体
tsx
<button className="bg-blue-500 hover:bg-blue-600 active:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-400 disabled:opacity-50">hover:/focus:/active:/disabled:dark:— 暗色模式group-hover:— 父级 hover
五、暗色模式
tsx
<div className="bg-white text-black dark:bg-gray-900 dark:text-white">配置(tailwind.config.ts):
ts
export default {
darkMode: 'class', // 用 class 切换
// ...
};六、自定义值(任意属性)
tsx
<div className="w-[300px] bg-[#bada55] text-[14px] mt-[var(--header-height)]">七、条件类名:cn 工具
tsx
import { clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
// 使用
<button className={cn(
'px-4 py-2 rounded',
isActive && 'bg-blue-500 text-white',
isDisabled && 'opacity-50 cursor-not-allowed',
)}>⚠️ 坑 2:
clsx只做条件合并,有冲突类名不会去重。要去重用tailwind-merge。
八、复用样式:@apply
css
/* globals.css */
@layer components {
.btn-primary {
@apply px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600;
}
}tsx
<button className="btn-primary">点</button>⚠️ 坑 3:不要过度用
@apply,它会变成新的"全局类",违背 utility-first 初衷。
九、自定义动画
ts
// tailwind.config.ts
theme: {
extend: {
keyframes: {
'fade-in': {
'0%': { opacity: '0' },
'100%': { opacity: '1' },
},
},
animation: {
'fade-in': 'fade-in 0.3s ease-in-out',
},
},
}tsx
<div className="animate-fade-in">淡入</div>十、shadcn/ui 配合 Tailwind
shadcn 生成的组件就是 Tailwind 类名,直接改:
tsx
// components/ui/button.tsx(部分)
import { cn } from '@/lib/utils';
export const Button = ({ className, ...props }) => (
<button
className={cn(
'inline-flex items-center justify-center rounded-md text-sm font-medium',
'transition-colors focus-visible:outline-none',
'bg-primary text-primary-foreground hover:bg-primary/90',
className,
)}
{...props}
/>
);十一、性能与 purge
Tailwind 在构建时扫描 content 配置的文件,没用到的不打进 bundle。
ts
content: [
'./src/**/*.{ts,tsx}', // 必填,否则类名丢失
],⚠️ 坑 4:
content路径错了 → 类名无效。动态拼接的类名也要写在文件里(完整字符串扫描)。
十二、本章小结
| 要点 | 关键 |
|---|---|
| Utility-first | 类名即样式,不写 CSS |
| 响应式 | 移动优先,md: lg: |
| 状态 | hover focus dark |
| 任意值 | w-[300px] bg-[#ccc] |
| 合并 | clsx + tailwind-merge = cn() |
| shadcn | 默认基于 Tailwind |
| 性能 | 构建时 PurgeCSS,产物极小 |
动手练习
- 响应式卡片:不同断点变不同布局
- 暗色模式:用
dark:类 + theme 切换 - 封装组件:用
cn()封装带状态变体的 Button - 找坑:以下代码为什么
md:flex不生效?tsx<div className="md:flex hidden">内容</div>
下一章:第 24 章:测试 →