跳到主要内容

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 越精准,产物越瘦,动态拼接要收敛。
  • @layerbase/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>

出口:组件重新长出语义

  • cvatailwind-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 })} />
}

延伸阅读: