Tailwind 设计理念
Tailwind 本质是什么
- 它是一个“把设计语义压平成原子类,再在组件里组装回语义”的流水线。
- 入口是设计 token(色板、间距、圆角、字号),中段是 JIT 按 content 生成类,出口是组件接口用 variants/merge 把类重新打包。
- 目标是:样式可组合、可控,切换主题或品牌时只改 token,组件 API 依旧可读。
Demo(整体串联)
// tailwind.config.ts
export default {
content: ['./src/**/*.{tsx,jsx,html}'],
theme: {
extend: {
colors: { brand: { 50: '#f1f5ff', 500: '#3b82f6' } },
spacing: { 3.5: '0.875rem' },
borderRadius: { pill: '999px' },
},
},
plugins: [require('@tailwindcss/typography')],
}
// src/components/Button.tsx
import { tv } from 'tailwind-variants'
const button = tv({
base: 'inline-flex items-center justify-center font-medium transition-colors',
variants: {
tone: {
primary: 'bg-brand-500 text-white hover:bg-brand-500/90',
ghost: 'bg-transparent text-brand-500 hover:bg-brand-50',
},
size: { md: 'h-10 px-4 rounded-pill', lg: 'h-11 px-5 rounded-pill text-base' },
},
defaultVariants: { tone: 'primary', size: 'md' },
})
export function Button({ tone, size, className, ...props }) {
return <button {...props} className={button({ tone, size, class: className })} />
}
入口:设计语义的单一来源
- 先把设计值写成 token(CSS 变量或
@theme),Class 只引用 token,不直接写裸值。 - 约束梯度和命名,避免随便一个数字就进主题;换品牌/暗黑只动这份 token。
- 保证“设计稿和 Tailwind 主题是一份真相”,更新 token 时同步配置。
Demo:token 定义
// tailwind.config.ts 片段
export default {
theme: {
extend: {
colors: {
brand: { 50: 'var(--brand-50)', 500: 'var(--brand-500)' },
},
spacing: { 3.5: 'var(--space-3_5)' },
borderRadius: { pill: 'var(--radius-pill)' },
},
},
}
/* src/styles/theme.css */
:root {
--brand-50: #eef2ff;
--brand-500: #4f46e5;
--space-3_5: 0.875rem;
--radius-pill: 999px;
}
中段:Tailwind 的生成逻辑
- JIT + content:扫描模板只生成用到的类;content 越精准,产物越瘦,动态拼接要收敛。
- @layer:
base/components/utilities管覆盖关系,@apply只在同层或下层用,冲突先用 variants 解决。 - Variants/关系类:
md:、hover:、group-、peer-、aria-、data-覆盖常见状态,深度别乱堆。 - 插件:官方插件覆盖常用模式,自定义插件把你们的设计规则翻译成类。
Demo:content 精准 + 局部 @layer
// tailwind.config.ts 片段
export default {
content: ['./src/app/**/*.{tsx,jsx}', './src/pages/**/*.{tsx,jsx}'],
plugins: [],
}
/* src/styles/components.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.card {
@apply rounded-xl border border-slate-200 bg-white shadow-sm;
}
.card-title {
@apply text-lg font-semibold text-slate-900;
}
}
// src/app/Card.tsx
export function Card({ title, children }) {
return (
<div className="card">
<h3 className="card-title">{title}</h3>
<div className="text-sm text-slate-600">{children}</div>
</div>
)
}
Demo:关系类和状态类
// src/components/Nav.tsx
export function Nav() {
return (
<nav className="flex items-center gap-4">
<button className="relative group px-3 py-2 text-sm font-medium text-slate-600 hover:text-slate-900">
Home
<span className="absolute inset-x-3 -bottom-1 block h-0.5 scale-x-0 bg-brand-500 transition group-hover:scale-x-100" />
</button>
<button className="px-3 py-2 text-sm text-slate-600 data-[active=true]:text-brand-600 data-[active=true]:font-semibold">
Docs
</button>
</nav>
)
}
Demo:自定义插件扩展一个工具类
// tailwind.config.ts 片段
import plugin from 'tailwindcss/plugin'
export default {
// ...
plugins: [
plugin(({ matchUtilities, theme }) => {
matchUtilities(
{
'grid-auto-fill': (value) => ({
gridTemplateColumns: `repeat(auto-fill, minmax(${value}, 1fr))`,
}),
},
{ values: theme('spacing') },
)
}),
],
}
// 使用自定义工具类
<div className="grid gap-4 grid-auto-fill-48">
{/* 自动填充列,最小 12rem */}
</div>
出口:组件重新长出语义
- 用
cva或tailwind-variants(tv)定义组件的尺寸、色板、状态、slot,不要直接暴露一长串类。 - 用
tailwind-merge收尾,防止外部 class 无声覆盖核心样式。 - 组件 API 保持克制,只留必要的开关和插槽,既能扩展又不失读。
Demo:用 tailwind-variants 简化 variants
// src/components/Badge.tsx
import { tv } from 'tailwind-variants'
const badge = tv({
base: 'inline-flex items-center gap-1 rounded-full font-medium',
variants: {
tone: {
success: 'bg-emerald-50 text-emerald-700 ring-1 ring-emerald-100',
warning: 'bg-amber-50 text-amber-700 ring-1 ring-amber-100',
},
size: { sm: 'px-2 py-1 text-xs', md: 'px-3 py-1.5 text-sm' },
},
defaultVariants: { tone: 'success', size: 'sm' },
})
export function Badge({ tone, size, className, ...props }) {
return <span {...props} className={badge({ tone, size, class: className })} />
}
延伸阅读:
