这一章是全书最实战、最接地气、工程价值最高的章节之一,主要内容包括:
- 企业级 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.18 深色模式(Dark Mode)最佳实践
Tailwind v3 原生支持 darkMode: 'class' 策略,推荐与系统偏好联动:
<!-- HTML 根元素动态切换 -->
<html class="dark">
<body class="bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100">
// useDarkMode.ts - React Hook
import { useEffect, useState } from 'react';
export function useDarkMode() {
const [isDark, setIsDark] = useState(() =>
document.documentElement.classList.contains('dark')
);
useEffect(() => {
const observer = new MutationObserver(() => {
setIsDark(document.documentElement.classList.contains('dark'));
});
observer.observe(document.documentElement, { attributes: true });
return () => observer.disconnect();
}, []);
const toggle = () => {
document.documentElement.classList.toggle('dark');
localStorage.setItem('theme', isDark ? 'light' : 'dark');
};
return { isDark, toggle };
}
避免硬编码颜色名:不要写 dark:bg-gray-800 在每个元素上。改用 CSS 变量 + 语义化 token:
@layer base {
:root {
--surface-primary: #ffffff;
--surface-secondary: #f9fafb;
--text-primary: #111827;
}
.dark {
--surface-primary: #111827;
--surface-secondary: #1f2937;
--text-primary: #f9fafb;
}
}
<div class="bg-[var(--surface-primary)] text-[var(--text-primary)]">
自动适配深浅色
</div>
9.19 设计 Token 与 Design System
将 Tailwind 配置抽象为设计 token:
// tokens/colors.js
module.exports = {
brand: {
50: '#eff6ff',
500: '#3b82f6',
600: '#2563eb',
900: '#1e3a8a',
},
semantic: {
success: '#22c55e',
warning: '#f59e0b',
error: '#ef4444',
info: '#3b82f6',
}
};
// tailwind.config.js
const tokens = require('./tokens');
module.exports = {
theme: {
extend: {
colors: tokens.colors,
}
}
};
Token 管理工具:
- Style Dictionary:Amazon 开源,多平台 token 导出(CSS / iOS / Android / Figma)
- Tokens Studio:Figma 插件,设计稿与代码 token 双向同步
- Tailwind CSS Variables:运行时主题切换无需重新编译
9.20 shadcn/ui 与现代组件方法论
shadcn/ui 是 2023-2024 年最受欢迎的组件方案,其核心理念值得深入理解:
| 维度 | shadcn/ui | 传统 UI 库(MUI/ANTD) |
|---|---|---|
| 代码所有权 | 复制到你项目中 | npm 依赖 |
| 定制自由度 | 100%(直接改源码) | 受限于 API 和 Theme |
| 更新方式 | 手动选择性更新 | npm update |
| 包体积 | 只打包用到的 | 全部引入 |
| 学习成本 | 中(需理解底层 Tailwind) | 低(看文档即可) |
| 设计一致性 | 依赖你的设计系统 | 内置设计规范 |
shadcn/ui 的哲学:UI 组件不是"黑盒依赖",而是"可修改的起点"。这与 Tailwind 原子类的精神完全一致。
安装方式:
npx shadcn-ui@latest init
npx shadcn-ui@latest add button card dialog
# 代码直接复制到 components/ui/ 目录下
第 9 章总结
本章完整解析了:
✔ Tailwind UI 组件体系设计原则
✔ base + variants + states 的三段式结构
✔ Button 卡片 输入框 表格 模态框 导航条 等各类组件完整写法
✔ 支持暗黑模式 / 多主题 / CSS 变量 token
✔ 使用插件自动生成 UI 工具类
✔ 组件库的目录结构、发布流程
✔ shadcn/ui 方法论对比与设计系统 token 管理
✔ 企业级 UI 的全部实践方式
这是构建大型 Tailwind UI 库的标准教程。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。