Tailwind CSS 改变了 CSS 的编写方式。本文深入探讨 Tailwind 的最佳实践和高级技巧。
Tailwind 核心概念
原子化 CSS
Tailwind 设计哲学:
┌─────────────────────────────────────────────────────┐
│ │
│ 传统 CSS │
│ └── 编写语义化类名 → 定义样式规则 │
│ │
│ Tailwind CSS │
│ └── 组合原子类 → 直接在 HTML 中构建样式 │
│ │
│ 优势 │
│ ├── 无需命名纠结 │
│ ├── 样式即组件 │
│ ├── 一致的设计系统 │
│ └── 更小的生产包体积 │
│ │
└─────────────────────────────────────────────────────┘
基础用法
<!-- 传统方式 -->
<div class="card">
<h2 class="card-title">标题</h2>
<p class="card-content">内容</p>
</div>
<!-- Tailwind 方式 -->
<div class="bg-white rounded-lg shadow-md p-6">
<h2 class="text-xl font-bold text-gray-900 mb-2">标题</h2>
<p class="text-gray-600">内容</p>
</div>
配置文件
tailwind.config.js
// tailwind.config.js
module.exports = {
content: [
'./src/**/*.{js,ts,jsx,tsx,astro}',
'./components/**/*.{js,ts,jsx,tsx}',
],
theme: {
extend: {
// 自定义颜色
colors: {
primary: {
50: '#eff6ff',
100: '#dbeafe',
500: '#3b82f6',
600: '#2563eb',
700: '#1d4ed8',
},
brand: '#ff6b35',
},
// 自定义字体
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
mono: ['JetBrains Mono', 'monospace'],
},
// 自定义间距
spacing: {
'18': '4.5rem',
'88': '22rem',
'128': '32rem',
},
// 自定义断点
screens: {
'xs': '475px',
'3xl': '1920px',
},
// 自定义动画
animation: {
'fade-in': 'fadeIn 0.5s ease-out',
'slide-up': 'slideUp 0.3s ease-out',
},
keyframes: {
fadeIn: {
'0%': { opacity: '0' },
'100%': { opacity: '1' },
},
slideUp: {
'0%': { transform: 'translateY(10px)', opacity: '0' },
'100%': { transform: 'translateY(0)', opacity: '1' },
},
},
},
},
plugins: [
require('@tailwindcss/forms'),
require('@tailwindcss/typography'),
require('@tailwindcss/aspect-ratio'),
],
};
响应式设计
断点使用
<!-- 移动优先设计 -->
<div class="
w-full /* 默认:全宽 */
md:w-1/2 /* 中等屏幕:半宽 */
lg:w-1/3 /* 大屏幕:三分之一 */
xl:w-1/4 /* 超大屏幕:四分之一 */
">
响应式卡片
</div>
<!-- 响应式网格 -->
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
<div>项目 1</div>
<div>项目 2</div>
<div>项目 3</div>
<div>项目 4</div>
</div>
<!-- 响应式导航 -->
<nav class="flex flex-col md:flex-row md:items-center md:justify-between">
<div class="text-xl font-bold">Logo</div>
<div class="hidden md:flex space-x-4">
<a href="#">首页</a>
<a href="#">关于</a>
<a href="#">联系</a>
</div>
</nav>
Container 查询
<!-- 使用 @container -->
<div class="@container">
<div class="@md:flex @md:items-center">
<img class="w-full @md:w-48" src="..." alt="" />
<div class="@md:ml-4">
<h3 class="text-lg @lg:text-xl">标题</h3>
</div>
</div>
</div>
状态变体
交互状态
<!-- Hover, Focus, Active -->
<button class="
bg-blue-500
hover:bg-blue-600
focus:ring-2
focus:ring-blue-300
active:bg-blue-700
transition-colors
">
按钮
</button>
<!-- Group Hover -->
<div class="group">
<div class="group-hover:bg-gray-100 p-4">
<h3 class="group-hover:text-blue-600">标题</h3>
<p class="group-hover:text-gray-700">描述</p>
</div>
</div>
<!-- Peer 状态 -->
<input type="checkbox" class="peer" />
<label class="peer-checked:text-blue-600">
选中时变蓝
</label>
暗色模式
<!-- 系统偏好 -->
<div class="bg-white dark:bg-gray-900">
<h1 class="text-gray-900 dark:text-white">标题</h1>
<p class="text-gray-600 dark:text-gray-400">内容</p>
</div>
<!-- 手动切换 -->
<script>
// tailwind.config.js: darkMode: 'class'
function toggleDark() {
document.documentElement.classList.toggle('dark');
}
</script>
组件抽象
@apply 指令
/* styles/components.css */
@layer components {
.btn {
@apply px-4 py-2 rounded-lg font-medium transition-colors;
}
.btn-primary {
@apply btn bg-blue-500 text-white hover:bg-blue-600;
}
.btn-secondary {
@apply btn bg-gray-200 text-gray-800 hover:bg-gray-300;
}
.input {
@apply w-full px-3 py-2 border border-gray-300 rounded-lg
focus:ring-2 focus:ring-blue-500 focus:border-transparent;
}
.card {
@apply bg-white rounded-xl shadow-lg p-6;
}
}
React 组件
// components/Button.tsx
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const buttonVariants = cva(
'inline-flex items-center justify-center rounded-lg font-medium transition-colors',
{
variants: {
variant: {
primary: 'bg-blue-500 text-white hover:bg-blue-600',
secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300',
outline: 'border border-gray-300 hover:bg-gray-50',
ghost: 'hover:bg-gray-100',
},
size: {
sm: 'px-3 py-1.5 text-sm',
md: 'px-4 py-2',
lg: 'px-6 py-3 text-lg',
},
},
defaultVariants: {
variant: 'primary',
size: 'md',
},
}
);
interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {}
export function Button({ className, variant, size, ...props }: ButtonProps) {
return (
<button
className={cn(buttonVariants({ variant, size }), className)}
{...props}
/>
);
}
// 使用
<Button variant="primary" size="lg">主要按钮</Button>
<Button variant="outline">边框按钮</Button>
cn 工具函数
// lib/utils.ts
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
// 使用示例
cn('px-4 py-2', isActive && 'bg-blue-500', className);
// 自动合并冲突的类名
布局模式
Flexbox
<!-- 水平居中 -->
<div class="flex justify-center">
<div>居中内容</div>
</div>
<!-- 垂直居中 -->
<div class="flex items-center h-screen">
<div>居中内容</div>
</div>
<!-- 完全居中 -->
<div class="flex items-center justify-center h-screen">
<div>居中内容</div>
</div>
<!-- Space Between -->
<div class="flex justify-between items-center">
<div>左侧</div>
<div>右侧</div>
</div>
Grid
<!-- 基础网格 -->
<div class="grid grid-cols-3 gap-4">
<div>1</div>
<div>2</div>
<div>3</div>
</div>
<!-- 自适应网格 -->
<div class="grid grid-cols-[repeat(auto-fit,minmax(250px,1fr))] gap-4">
<div>卡片 1</div>
<div>卡片 2</div>
<div>卡片 3</div>
</div>
<!-- 复杂布局 -->
<div class="grid grid-cols-12 gap-4">
<div class="col-span-12 lg:col-span-8">主内容</div>
<div class="col-span-12 lg:col-span-4">侧边栏</div>
</div>
动画效果
<!-- 过渡 -->
<button class="
bg-blue-500
hover:bg-blue-600
transition-all
duration-300
ease-in-out
hover:scale-105
">
悬停放大
</button>
<!-- 自定义动画 -->
<div class="animate-fade-in">
淡入元素
</div>
<div class="animate-pulse">
脉冲效果
</div>
<div class="animate-spin">
旋转加载
</div>
<!-- 条件动画 -->
<div class="
opacity-0
translate-y-4
transition-all
duration-500
data-[visible=true]:opacity-100
data-[visible=true]:translate-y-0
">
滚动显示
</div>
性能优化
生产构建
// tailwind.config.js
module.exports = {
content: [
// 确保只扫描需要的文件
'./src/**/*.{js,ts,jsx,tsx}',
],
// 生产环境自动 Tree Shaking
};
最小化类名
<!-- 避免重复 -->
<div class="p-4 m-4">
<!-- 使用 space 替代多个 margin -->
<div class="space-y-4">
<div>项目 1</div>
<div>项目 2</div>
</div>
</div>
<!-- 使用 divide 替代边框 -->
<ul class="divide-y divide-gray-200">
<li class="py-4">项目 1</li>
<li class="py-4">项目 2</li>
</ul>
最佳实践总结
Tailwind CSS 最佳实践:
┌─────────────────────────────────────────────────────┐
│ 设计系统 │
│ ├── 在 config 中定义设计 Token │
│ ├── 使用一致的间距和颜色 │
│ ├── 创建可复用的组件 │
│ └── 使用 CVA 管理变体 │
│ │
│ 代码组织 │
│ ├── 按逻辑分组类名 │
│ ├── 使用 @apply 抽象常用模式 │
│ ├── 配合 cn() 合并类名 │
│ └── 组件化替代过长类名 │
│ │
│ 性能优化 │
│ ├── 正确配置 content 路径 │
│ ├── 生产环境自动 purge │
│ ├── 避免动态类名拼接 │
│ └── 使用 JIT 模式 │
│ │
│ 响应式设计 │
│ ├── 移动优先 │
│ ├── 合理使用断点 │
│ ├── Container 查询 │
│ └── 暗色模式支持 │
└─────────────────────────────────────────────────────┘
| 场景 | 推荐做法 |
|---|---|
| 按钮组件 | CVA + cn() |
| 表单样式 | @tailwindcss/forms |
| 文章排版 | @tailwindcss/typography |
| 暗色模式 | dark: 前缀 |
Tailwind 让样式成为组件的一部分,写 CSS 从此不再纠结。