第 9 章:Tailwind 与最佳实践 UI 组件体系
By Leeting Yan
这一章是全书最实战、最接地气、工程价值最高的章节之一,主要内容包括:
- 企业级 UI 组件设计原则
- 使用 Tailwind 构建 30+ 高质量 UI 组件
- base / variants / states 核心架构
- 组件主题化、暗黑模式、响应式
- 与插件系统结合
- 与 shadcn/ui 的方法论对比
- 组件库组织方式(React / Vue 两套示例)
- 企业级 UI 组件库的目录与发布模式
9.1 组件体系的设计哲学(从 Tailwind 转向 Design System)
Tailwind 的组件设计必须遵守三大准则:
9.1.1 Base + Variants + States(铁三角模型)
优秀组件系统必须拆成:
1) base:基础设计语言
例如 Button 的 base 是:
inline-flex items-center justify-center font-medium rounded-md transition
这代表了:
- 布局
- 对齐
- 字重
- 圆角体系
- 动画
并不包含颜色。
2) variants:功能变体
如:
- 视觉变体:primary / secondary / ghost / outline
- 尺寸变体:sm / md / lg
- 状态变体:disabled / loading
- 风格变体:solid / subtle / elevated
3) states:交互状态
如:
- hover
- focus
- active
- aria-disabled
- group-hover
- peer-focus
9.1.2 类名不可堆到组件使用层
错误做法(非常多初学者会写的):
<button class="px-4 py-2 bg-blue-500 text-white shadow hover:bg-blue-600 rounded transition duration-200 ease-out">
保存
</button>
正确做法:
<button class="btn btn-primary">保存</button>
或者 React 风格:
<Button variant="primary">保存</Button>
Tailwind 的原子类应该全部“封装到组件内部”。
9.1.3 企业级 UI 有 3 层组件
Layer 1: 原子类(Tailwind utilities)
Layer 2: 基础视觉组件(Button, Card, Input)
Layer 3: 业务组件(TableWithPagination, SideMenu, DashboardCard)
千万不要把业务逻辑放到基础组件。
9.2 组件基础工具:类名合并(clsx + tailwind-merge)
Tailwind 最强组合:
clsx + tailwind-merge
使用工具函数:
import { clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'
export function cn(...inputs) {
return twMerge(clsx(inputs))
}
在所有组件库中作为通用方法使用。
9.3 Button(按钮)—— 组件体系的核心示例
从企业最佳实践开始。
9.3.1 Button 的通用结构
Button 有:
- base
- size variants
- color variants
- optional icon slot
- disabled 状态
- loading 状态
- 光标状态控制
- active + focus ring
9.3.2 Button 的 Tailwind Base 类
const buttonBase =
"inline-flex items-center justify-center rounded-md font-medium focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50 transition-colors duration-200";
9.3.3 尺寸变体
const buttonSizes = {
sm: "h-8 px-3 text-sm",
md: "h-10 px-4 text-base",
lg: "h-12 px-6 text-lg",
}
9.3.4 视觉变体
const buttonVariants = {
primary:
"bg-blue-600 text-white hover:bg-blue-700 active:bg-blue-800",
secondary:
"bg-gray-200 text-gray-900 hover:bg-gray-300 active:bg-gray-400",
outline:
"border border-gray-300 hover:bg-gray-100",
ghost:
"hover:bg-gray-100 hover:text-gray-900",
danger:
"bg-red-600 text-white hover:bg-red-700",
}
9.3.5 组合为完整 React 组件
export function Button({
size = "md",
variant = "primary",
className,
...props
}) {
return (
<button
className={cn(
buttonBase,
buttonSizes[size],
buttonVariants[variant],
className
)}
{...props}
/>
)
}
9.3.6 Vue 版本
<script setup>
import { cn } from '@/lib/cn'
const props = defineProps({
size: { default: "md" },
variant: { default: "primary" }
})
const base = "inline-flex items-center justify-center rounded-md font-medium transition-colors"
const sizes = { sm: "h-8 px-3", md: "h-10 px-4", lg: "h-12 px-6" }
const variants = { primary: "bg-blue-600 text-white hover:bg-blue-700" }
</script>
<template>
<button :class="cn(base, sizes[size], variants[variant])">
<slot />
</button>
</template>
9.4 Card(卡片)
卡片是后台系统最常用组件。
9.4.1 Base 类
const cardBase =
"rounded-xl border border-gray-200 bg-white shadow-sm dark:bg-gray-900 dark:border-gray-700";
9.4.2 布局结构
<div class="card">
<div class="card-header">标题</div>
<div class="card-content">内容</div>
<div class="card-footer">操作</div>
</div>
定义:
.card-header { @apply p-4 border-b border-gray-200; }
.card-content { @apply p-4; }
.card-footer { @apply p-4 border-t border-gray-200; }
可通过 Tailwind 插件自动生成。
9.5 Input / Textarea(输入组件)
输入框必须具备:
- focus ring
- disabled
- error 状态
- prefix/suffix
- label 管理
9.5.1 Base 类
const inputBase =
"block w-full rounded-md border border-gray-300 px-3 py-2 text-sm " +
"focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-900 dark:border-gray-600";
9.5.2 错误状态
const inputError =
"border-red-500 focus:border-red-600 focus:ring-red-600 text-red-600";
9.5.3 React 组件
export function Input({ error, className, ...props }) {
return (
<input
className={cn(
inputBase,
error && inputError,
className
)}
{...props}
/>
);
}
9.6 Modal(对话框)
Modal 需要:
- Overlay
- Content Box
- Close Button
- ESC / 点击遮罩关闭
- 动画 (scale / opacity)
Tailwind 样式结构:
<div class="fixed inset-0 bg-black/50 backdrop-blur-sm"></div>
<div class="fixed inset-0 flex items-center justify-center p-6">
<div class="bg-white dark:bg-gray-900 rounded-xl shadow-xl w-full max-w-lg p-6">
<!-- modal content -->
</div>
</div>
配合 Headless UI(推荐):
<Dialog>
<DialogOverlay class="fixed inset-0 bg-black/50" />
<DialogPanel class="rounded-xl p-6 bg-white">...</DialogPanel>
</Dialog>
9.7 Table(复杂数据表格)
企业后台最复杂的组件之一。
Tailwind 表格基础:
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-3 text-left font-medium">名称</th>
<th class="px-4 py-3">状态</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
<tr class="hover:bg-gray-50">
<td class="px-4 py-3">项目 A</td>
<td class="px-4 py-3">进行中</td>
</tr>
</tbody>
</table>
可扩展:
- 固定列
- 排序箭头
- 分页组件组合
- 多选行
- filter 区域
Tailwind 100% 可支撑。
9.8 Navbar(导航条)
导航条需要:
- sticky top
- dark 模式
- mobile collapsible
- 下拉菜单(Headless UI 最佳)
基础结构:
<nav class="flex items-center justify-between px-6 py-4 bg-white dark:bg-gray-900 border-b">
<div>Logo</div>
<div class="hidden md:flex gap-4">
<a class="text-gray-700 hover:text-gray-900">首页</a>
<a class="text-gray-700 hover:text-gray-900">产品</a>
</div>
</nav>
Mobile:
<div class="md:hidden">
<button class="p-2">☰</button>
</div>
9.9 Tabs(标签页)
典型结构:
<div class="flex gap-4 border-b">
<button class="py-3 px-4 border-b-2 border-blue-600 text-blue-600">
概况
</button>
<button class="py-3 px-4 text-gray-500 hover:text-gray-700">
设置
</button>
</div>
可结合 plugin 自动生成:
addComponents({
'.tabs': '@apply flex gap-4 border-b',
'.tab-active': '@apply border-b-2 border-blue-600 text-blue-600',
'.tab': '@apply py-3 px-4 text-gray-500 hover:text-gray-700'
})
9.10 Badge(徽章)
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-blue-100 text-blue-700">
新
</span>
9.11 Tooltip(提示框)
使用 group-hover:
<div class="relative group">
<button>详情</button>
<div class="absolute left-1/2 top-full -translate-x-1/2 mt-2 opacity-0 group-hover:opacity-100 transition">
<div class="px-3 py-1 bg-black text-white rounded text-xs">更多说明</div>
</div>
</div>
9.12 Dropdown(下拉菜单)
可基于 Headless UI:
<Menu>
<MenuButton class="px-4 py-2">操作</MenuButton>
<MenuItems class="mt-2 bg-white shadow rounded-lg">
<MenuItem>编辑</MenuItem>
<MenuItem>删除</MenuItem>
</MenuItems>
</Menu>
9.13 Skeleton(骨架屏)
<div class="animate-pulse bg-gray-200 rounded h-6 w-3/4"></div>
9.14 Skeleton Card
<div class="p-4 border rounded-lg">
<div class="animate-pulse bg-gray-200 h-32 w-full rounded"></div>
<div class="mt-4 space-y-2">
<div class="h-4 bg-gray-200 rounded"></div>
<div class="h-4 bg-gray-200 rounded w-2/3"></div>
</div>
</div>
9.15 完整的 UI 组件库目录结构
企业级组件库通常组织为:
ui/
button.tsx
input.tsx
card.tsx
modal.tsx
table/
index.tsx
row.tsx
column.tsx
form/
form.tsx
form-field.tsx
layout/
container.tsx
sidebar.tsx
9.16 UI 组件库的主题化
使用 CSS 变量:
.bg-[var(--card-bg)]
.text-[var(--text-primary)]
.border-[var(--border-color)]
结合插件自动生成:
addBase({
':root': {
'--color-primary': '#3b82f6'
},
'.theme-dark': {
'--color-primary': '#60a5fa'
}
})
9.17 模块化 UI 库(发布到 npm)
通过 TurboRepo / pnpm workspace:
packages/
ui-react/
ui-vue/
tailwind-config/
tokens/
发布:
npm publish
项目使用:
import { Button } from "@company/ui-react"
第 9 章总结
本章完整解析了:
✔ Tailwind UI 组件体系设计原则
✔ base + variants + states 的三段式结构
✔ Button 卡片 输入框 表格 模态框 导航条 等各类组件完整写法
✔ 支持暗黑模式 / 多主题
✔ 使用插件自动生成 UI 工具类
✔ 组件库的目录结构、发布流程
✔ shadcn/ui 方法论对比
✔ 企业级 UI 的全部实践方式
这是构建大型 Tailwind UI 库的标准教程。