🎨 UI 组件库

shadcn/ui / Radix / Ant Design / MUI / Tailwind CSS — 组件库不是选「最好看的」,而是选「你能掌控的」。头部less、可定制、类型安全是 2024-2026 的主旋律。

01 为什么 UI 组件库是 SaaS 的基础设施

造 SaaS 产品,你可能花 60% 的前端时间在 UI 细节上:按钮状态、表单验证、Modal 层级、表格排序、Toast 动画……如果每个都从零写,你的 MVP 永远出不来。

但选错组件库的代价同样高昂:定制受限、包体积爆炸、升级断裂、设计感同质化。这篇帮你理清 什么时候用什么,以及 怎么组合

核心趋势:2024-2026 年,React 生态从「全包大库」转向 Headless + Tailwind 模式。shadcn/ui 的流行不是偶然——它把「给你源码」替代「给你 npm 包」,彻底解决了定制问题。

组件库的三个层次

🔤 原子层 (Primitives) 🧩 组件层 (Components) 🏗️ 模板块 (Blocks/Templates)

原子层:无样式、纯行为的底层原语(Radix UI、Headless UI、Ark UI)。处理键盘导航、ARIA、焦点管理,但不提供任何视觉样式。

组件层:在原子层上叠加设计系统,提供可用的 UI 组件(shadcn/ui、Ant Design、MUI)。

模板块:完整页面布局和业务模板(Dashboard 模板、SaaS Starter Kit),通常由社区或商业产品提供。

常见错误:直接选组件层(如 Ant Design),跳过原子层理解。当需要深度定制时,发现底层行为被封装死了,不得不 fork 整个库。先理解原子层,才能选对组件层。

02 Tailwind CSS — 新基建

严格来说 Tailwind 不是组件库,但它是 2024-2026 React 生态几乎所有现代组件库的底层样式引擎。不理解 Tailwind,就无法理解 shadcn/ui 等新范式。

是什么

原子化 CSS 框架。不提供组件,只提供 utility class:flexpx-4text-smrounded-lg。你通过组合这些 class 来构建 UI,而非写自定义 CSS。

核心优势

✅ 零运行时 CSS

Tailwind v4 用 Rust 引擎 (Oxide) 扫描模板,只生成用到的 CSS。生产包通常 5-15KB gzip,远小于传统 CSS-in-JS。

✅ 设计约束内置

间距、颜色、字号都由 tailwind.config 控制,天然保证设计一致性。不用 Tailwind 时,5 个开发者写 5 种 margin

代码示例:一个 SaaS 定价卡

{/* Tailwind v4 — 无需配置文件,CSS-first 配置 */}
<div class="rounded-2xl border border-border bg-card p-6 shadow-sm">
  <h3 class="text-lg font-semibold">Pro</h3>
  <p class="mt-2 text-3xl font-bold">
    $29<span class="text-sm font-normal text-muted">/月</span>
  </p>
  <ul class="mt-4 space-y-2 text-sm text-muted-foreground">
    <li>✓ 无限项目</li>
    <li>✓ 高级分析</li>
    <li>✓ 优先支持</li>
  </ul>
  <button class="mt-6 w-full rounded-lg bg-primary px-4 py-2.5
    text-sm font-medium text-primary-foreground
    hover:bg-primary/90 transition-colors">
    开始使用
  </button>
</div>

Tailwind v4 变化 (2024-2025)

⚡ v4 重大更新

何时不用 Tailwind

03 Radix UI — Headless 原语之王

Radix UI 是 React 生态最成熟的 Headless 组件原语库。它提供纯行为、零样式的组件原语——处理所有可访问性、键盘交互、焦点管理的脏活累活,样式完全交给你。

是什么

一组无样式的 React 组件原语:Dialog、Dropdown Menu、Popover、Toast、Tabs、Accordion、Tooltip 等。每个组件只负责 WAI-ARIA 规范的行为实现。

核心价值

能力Radix 做了什么为什么重要
焦点管理Modal 打开时焦点 trap,关闭后恢复手动实现至少 50 行代码且容易出错
键盘导航方向键选择、Enter 确认、Esc 关闭WAI-ARIA 规范有上百条交互规则
ARIA 属性自动添加 role、aria-label、aria-expanded可访问性审计必过项
滚动锁定Modal 打开时禁止背景滚动iOS Safari 滚动穿透是经典 bug
Portal 渲染弹出层渲染到 body 避免 overflow 裁剪z-index 层级问题的根因之一

代码示例:可访问的 Dialog

import * as Dialog from '@radix-ui/react-dialog'

function ConfirmDialog({ open, onOpenChange, onConfirm }) {
  return (
    <Dialog.Root open={open} onOpenChange={onOpenChange}>
      <Dialog.Portal>
        <Dialog.Overlay className="fixed inset-0 bg-black/50" />
        <Dialog.Content className="fixed top-1/2 left-1/2
          -translate-x-1/2 -translate-y-1/2
          bg-white rounded-lg p-6 shadow-xl">
          <Dialog.Title className="text-lg font-semibold">
            确认删除?
          </Dialog.Title>
          <Dialog.Description className="mt-2 text-gray-500">
            此操作不可撤销,数据将永久删除。
          </Dialog.Description>
          <div className="mt-4 flex justify-end gap-2">
            <Dialog.Close className="px-4 py-2 rounded
              border hover:bg-gray-50">取消</Dialog.Close>
            <button onClick={onConfirm}
              className="px-4 py-2 rounded bg-red-500
              text-white hover:bg-red-600">
              删除
            </button>
          </div>
        </Dialog.Content>
      </Dialog.Portal>
    </Dialog.Root>
  )
}
关键洞察:Radix 让你写 20 行代码就能获得 500+ 行可访问性逻辑。这不是偷懒,这是工程效率。shadcn/ui 的底层就是 Radix。

何时用 Radix 直接

04 shadcn/ui — 新范式王者 🔥 HOT

shadcn/ui 重新定义了组件库的分发方式:不是 npm 包,是源码。你用 CLI 把组件代码直接复制到项目里,然后随意修改。它的流行代表了 React 生态对「可控性」的极致追求。

是什么

基于 Radix UI + Tailwind CSS 的组件集合。每个组件都是你项目源码的一部分——可以改样式、改逻辑、改任何东西。没有 node_modules 依赖,没有版本锁定。

架构拆解

Radix Primitives Tailwind + CSS Variables shadcn/ui Component 你的项目源码

以 Dialog 组件为例:

// npx shadcn@latest add dialog 后,你的项目里多了一个文件:
// components/ui/dialog.tsx

import * as DialogPrimitive from "@radix-ui/react-dialog"
import { cn } from "@/lib/utils"  // class 合并工具

const Dialog = DialogPrimitive.Root
const DialogTrigger = DialogPrimitive.Trigger
const DialogPortal = DialogPrimitive.Portal
const DialogClose = DialogPrimitive.Close

const DialogOverlay = React.forwardRef<
  React.ElementRef<typeof DialogPrimitive.Overlay>,
  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
  <DialogPrimitive.Overlay
    ref={ref}
    className={cn(
      "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in
       data-[state=closed]:animate-out",  // ← 你可以直接改这个
      className
    )}
    {...props}
  />
))
// ... 完全可编辑的源码

核心优势

🔓 完全可定制

源码在你手里。改样式改逻辑改任何东西,不需要 fork、patch-package、或者等上游合并 PR。

📦 零依赖膨胀

只安装你用到的组件。不用的组件不存在于你的 node_modules。Button 组件不依赖 Table。

🎨 CSS 变量主题

通过 CSS 变量切换主题:--primary--background--accent。暗色/亮色模式零成本。

🧩 组合式架构

每个组件都是 Radix Primitive + Tailwind 的组合。你可以拆开重组,构建自己的变体。

CSS 变量主题系统

/* globals.css — shadcn/ui 的主题核心 */
@layer base {
  :root {
    --background: 0 0% 100%;
    --foreground: 222.2 84% 4.9%;
    --primary: 222.2 47.4% 11.2%;
    --primary-foreground: 210 40% 98%;
    --accent: 210 40% 96.1%;
    --muted: 210 40% 96.1%;
    --destructive: 0 84.2% 60.2%;
    --radius: 0.5rem;
  }

  .dark {
    --background: 222.2 84% 4.9%;
    --foreground: 210 40% 98%;
    --primary: 210 40% 98%;
    --primary-foreground: 222.2 47.4% 11.2%;
    --accent: 217.2 32.6% 17.5%;
    --destructive: 0 62.8% 30.6%;
  }
}

/* 一行切换品牌色 — 所有组件自动生效 */
:root {
  --primary: 250 89% 65%;  /* 从蓝色改为紫色 */
}

SaaS 常用组件速查

组件用途替代手动实现
DataTable数据表格(排序/筛选/分页)TanStack Table + shadcn 样式
Form表单验证 + 错误提示React Hook Form + Zod + shadcn
Command搜索面板 / 命令面板 (⌘K)cmdk 库 + shadcn 样式
Sidebar侧边栏导航shadcn sidebar 组件
Chart数据可视化Recharts + shadcn 主题
Calendar日期选择react-day-picker + shadcn

shadcn/ui 的局限

实践建议:npx shadcn@latest init 初始化,然后只 add 你需要的组件。不要一次装全部。用 shadcn@latest diff 定期检查更新。自定义 CSS 变量来避免「shadcn 脸」。

05 Ant Design — 企业级全家桶 稳定

蚂蚁集团出品,国内企业级 SaaS 最广泛使用的组件库。开箱即用、功能全面、中文生态完善。如果你做 B 端管理后台,Ant Design 是默认选项。

是什么

一套完整的企业级设计体系和 React 组件库。包含 60+ 组件、设计规范、设计资源(Figma/Sketch)、Pro 模板。从表单到表格到图表,几乎覆盖所有 B 端场景。

核心优势

🏢 开箱即用

ProTable/ProForm/ProLayout 直接提供完整业务组件。管理后台 MVP 速度最快。

🇨🇳 中文生态

文档中文、社区中文、设计规范适配中文排版。国内招人最易上手。

代码示例:ProTable(SaaS 管理后台的灵魂)

import { ProTable } from '@ant-design/pro-components'

function UserManagement() {
  return (
    <ProTable
      columns={[
        { title: '用户名', dataIndex: 'username' },
        { title: '邮箱', dataIndex: 'email', copyable: true },
        { title: '角色', dataIndex: 'role',
          valueEnum: {
            admin: { text: '管理员', status: 'Success' },
            user: { text: '普通用户', status: 'Default' },
          }
        },
        { title: '注册时间', dataIndex: 'createdAt',
          valueType: 'dateTime',
          sorter: true,
        },
        { title: '操作', valueType: 'option',
          render: (_, record) => [
            <a key="edit">编辑</a>,
            <a key="delete">删除</a>,
          ]
        },
      ]}
      request={async (params) => {
        const res = await fetch('/api/users', { params })
        return { data: res.data, total: res.total, success: true }
      }}
      rowKey="id"
      search={{ labelWidth: 'auto' }}
      pagination={{ pageSize: 20 }}
      toolBarRender={() => [
        <Button key="add" type="primary">新增用户</Button>,
      ]}
    />
  )
}

Ant Design 5 的变化

⚡ v5 重构要点

何时选 Ant Design

Ant Design 的痛点

迁移陷阱:Ant Design v5 的 CSS-in-JS 与 SSR (Next.js) 有已知兼容问题。需要 StyleProvider layer={true} + extractStyle 配合。如果你用 Next.js App Router,务必测试 SSR 场景。

06 MUI (Material UI) — 国际化首选 稳定

Google Material Design 的 React 实现。国际市场占有率最高的 React 组件库。如果你的 SaaS 面向全球用户,MUI 是最安全的选择。

是什么

Material Design 3 规范的 React 实现。包含 50+ 组件、主题系统、布局组件、数据展示组件。MUI Core 开源,MUI X (Pro/Premium) 提供高级数据组件。

与 Ant Design 的对比

🇨🇳 Ant Design

✅ 中文生态 + 文档
✅ ProComponents 全家桶
✅ B 端场景更专业
✅ 国内招人容易
❌ 国际化弱
❌ 定制更难
❌ CSS-in-JS 运行时
❌ 设计感偏「企业」
VS

🌍 MUI

✅ 全球社区最大
✅ Material Design 品牌背书
✅ 主题系统成熟
✅ MUI X 高级表格
❌ Material 味太重
❌ Pro 版付费 ($399+/年)
❌ 中文文档少
❌ 包体积也大

代码示例:MUI DataGrid(高级表格)

import { DataGrid, GridColDef } from '@mui/x-data-grid'

const columns: GridColDef[] = [
  { field: 'id', headerName: 'ID', width: 70 },
  { field: 'name', headerName: '名称', width: 200, editable: true },
  { field: 'status', headerName: '状态', width: 120,
    type: 'singleSelect',
    valueOptions: ['active', 'inactive', 'pending'],
  },
  { field: 'revenue', headerName: '收入', width: 130,
    type: 'number',
    valueFormatter: (v) => `$${v.toLocaleString()}`,
  },
]

function RevenueTable() {
  const { data, isLoading } = useRevenueData()

  return (
    <DataGrid
      rows={data}
      columns={columns}
      pageSizeOptions={[10, 25, 50]}
      checkboxSelection
      disableRowSelectionOnClick
      loading={isLoading}
      sx={{ border: 'none' }}
    />
  )
}

MUI v6 / v7 趋势

🔄 演进方向

07 其他值得关注的选项

Headless UI

Tailwind CSS 团队出品的 Headless 组件库。比 Radix 轻量,组件更少但更精致。如果你用 Tailwind 但觉得 Radix 太重,这是替代。

import { Dialog, Transition } from '@headlessui/react'

function MyDialog({ isOpen, onClose }) {
  return (
    <Transition appear show={isOpen}>
      <Dialog onClose={onClose} className="relative z-50">
        <Transition.Child
          enter="ease-out duration-300"
          enterFrom="opacity-0"
          enterTo="opacity-100"
          leave="ease-in duration-200"
          leaveFrom="opacity-100"
          leaveTo="opacity-0"
        >
          <div className="fixed inset-0 bg-black/30" />
        </Transition.Child>
        <div className="fixed inset-0 flex items-center justify-center">
          <Dialog.Panel className="w-full max-w-md
            rounded-2xl bg-white p-6">
            <Dialog.Title>确认操作</Dialog.Title>
            <Dialog.Description>确定要继续吗?</Dialog.Description>
          </Dialog.Panel>
        </div>
      </Dialog>
    </Transition>
  )
}

Ark UI

跨框架 Headless 库(React/Vue/Solid)。比 Radix 支持更多框架。如果你用 Solid 或 Vue,Ark UI 是 Headless 的最佳选择。

Ark UI 特色

Chakra UI

开发者体验优先的组件库。API 设计优雅,但运行时 CSS-in-JS 限制了性能上限。v3 已迁移到 Tailwind + CSS 变量方案,值得关注。

daisyUI

Tailwind CSS 插件形式的组件库。不提供 React 组件,只提供 CSS class:btncardmodal。适合多框架项目或非 React 项目。

<!-- daisyUI — 框架无关 -->
<button class="btn btn-primary">Click me</button>
<div class="card bg-base-100 shadow-xl">
  <div class="card-body">
    <h2 class="card-title">标题</h2>
    <p>内容</p>
  </div>
</div>

NextUI

视觉设计突出的 React 组件库。动画和默认样式比 shadcn/ui 更丰富,但定制自由度低。适合注重外观的 C 端产品。

Recharts / Nivo / Victory

数据可视化组件。SaaS Dashboard 几乎必用。shadcn/ui 的 Chart 组件基于 Recharts。

08 全面对比矩阵

维度 shadcn/ui Ant Design MUI Chakra UI Headless UI
分发方式 源码复制 npm 包 npm 包 npm 包 npm 包
样式方案 Tailwind + CSS Var CSS-in-JS (v5) CSS-in-JS (emotion) CSS-in-JS → Tailwind (v3) Tailwind
可定制性 极高 中等 中等 较高 极高
包体积 极小 中等
组件数量 40+ 60+ 50+ 50+ 12
SSR 兼容 完美 需配置 需配置 需配置 完美
TypeScript 原生 原生 原生 原生 原生
高级组件 需组合 ProComponents MUI X (付费)
设计语言 极简 / 无 Ant Design Material Chakra
升级难度 手动 diff 破坏性变更 破坏性变更 中等
适合场景 产品级 SaaS 企业后台 国际化产品 快速原型 自定义设计

09 选型决策树

🌳 你的 SaaS 该用什么?

你在做什么类型的产品?
│
├─ B 端管理后台 / 企业工具
│  ├─ 团队在中国?
│  │  └─ ✅ Ant Design + ProComponents(最快出活)
│  └─ 团队国际化?
│     └─ ✅ MUI + MUI X(全球生态最稳)
│
├─ C 端产品 / 创业公司 SaaS
│  ├─ 需要独特设计感?
│  │  └─ ✅ shadcn/ui + 自定义 CSS 变量(完全可控)
│  └─ 快速 MVP,设计不重要?
│     └─ ✅ shadcn/ui 默认主题 + Tailwind
│
├─ 自己造设计系统
│  ├─ React?
│  │  └─ ✅ Radix UI + Tailwind(行为 + 样式分离)
│  └─ Vue / Solid?
│     └─ ✅ Ark UI + Tailwind(跨框架 Headless)
│
└─ 非 React 项目
   ├─ Vue?
   │  ├─ ✅ Naive UI(Vue 3 原生,国人开发)
   │  └─ ✅ Element Plus(Vue 2/3,企业级)
   ├─ Svelte?
   │  └─ ✅ shadcn-svelte + Bits UI
   └─ 框架无关?
      └─ ✅ daisyUI(Tailwind 插件形式)
  

10 实战:SaaS 技术栈组合方案

方案 A:2026 主流栈 (推荐)

🏆 Next.js + shadcn/ui + Tailwind
# 初始化
npx create-next-app@latest my-saas --typescript --tailwind --app
cd my-saas
npx shadcn@latest init

# 按需添加组件
npx shadcn@latest add button input card dialog table form
npx shadcn@latest add sidebar command calendar chart

# 数据表格 — TanStack Table + shadcn
npm install @tanstack/react-table

# 表单 — React Hook Form + Zod
npm install react-hook-form @hookform/resolvers zod

适用:90% 的 SaaS 场景。包体积小、定制自由、SSR 完美、社区活跃。

方案 B:企业后台快速出活

🏢 Ant Design Pro + UmiJS
# Ant Design Pro 脚手架
npx create-umi@latest
# 选择 Ant Design Pro 模板

# 或手动搭建
npm install antd @ant-design/pro-components @ant-design/icons
npm install @ant-design/pro-layout @ant-design/pro-table @ant-design/pro-form

# 状态管理
npm install @umijs/max  # 内置 model、request、access

适用:国内企业后台、管理面板。出活最快,但长期定制成本高。

方案 C:极致轻量

🪶 Radix + Tailwind 手搭
# 只安装需要的 Radix 原语
npm install @radix-ui/react-dialog @radix-ui/react-dropdown-menu
npm install @radix-ui/react-popover @radix-ui/react-toast

# 自己写样式组件 — 100% 控制
# utils/cn.ts
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}

# 自己封装 Button
const buttonVariants = {
  default: "bg-primary text-white hover:bg-primary/90",
  outline: "border border-input hover:bg-accent",
  ghost: "hover:bg-accent hover:text-accent-foreground",
  destructive: "bg-destructive text-white hover:bg-destructive/90",
}

function Button({ variant = "default", className, ...props }) {
  return (
    <button
      className={cn(
        "inline-flex items-center justify-center rounded-md
         px-4 py-2 text-sm font-medium transition-colors",
        buttonVariants[variant],
        className
      )}
      {...props}
    />
  )
}

适用:打造自有设计系统、需要极致品牌差异化。

11 表单:SaaS 的高频战场

表单是 SaaS 产品的核心交互——注册、登录、设置、搜索、CRUD。选错表单方案,你的开发体验和用户体验都会很痛苦。

React Hook Form + Zod:2026 标配

import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
import { Form, FormControl, FormField, FormItem,
  FormLabel, FormMessage } from '@/components/ui/form'
import { Input } from '@/components/ui/input'
import { Button } from '@/components/ui/button'

// Zod schema — 类型安全的验证规则
const signupSchema = z.object({
  email: z.string().email('请输入有效邮箱'),
  password: z.string()
    .min(8, '密码至少 8 位')
    .regex(/[A-Z]/, '需要至少一个大写字母')
    .regex(/[0-9]/, '需要至少一个数字'),
  confirmPassword: z.string(),
}).refine(d => d.password === d.confirmPassword, {
  message: '两次密码不一致',
  path: ['confirmPassword'],
})

type SignupValues = z.infer<typeof signupSchema>

function SignupForm() {
  const form = useForm<SignupValues>({
    resolver: zodResolver(signupSchema),
    defaultValues: { email: '', password: '', confirmPassword: '' },
  })

  async function onSubmit(values: SignupValues) {
    // values 类型完全安全,不需要 any
    await fetch('/api/signup', {
      method: 'POST',
      body: JSON.stringify(values),
    })
  }

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)}
        className="space-y-4">
        <FormField control={form.control} name="email"
          render={({ field }) => (
            <FormItem>
              <FormLabel>邮箱</FormLabel>
              <FormControl><Input {...field} /></FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
        {/* password, confirmPassword 同理 */}
        <Button type="submit" className="w-full">
          注册
        </Button>
      </form>
    </Form>
  )
}
为什么是 RHF + Zod?React Hook Form 用 ref 而非 state 追踪表单值,避免每次按键触发重渲染。Zod 提供运行时验证 + TypeScript 类型推导,一份 schema 同时服务前端验证和后端校验。shadcn/ui 的 Form 组件就是为这个组合设计的。

12 数据表格:SaaS 的信息密度引擎

几乎所有 SaaS 都有列表页——用户列表、订单列表、日志列表。数据表格的选型直接影响用户体验和开发效率。

TanStack Table + shadcn/ui:灵活之选

import {
  useReactTable, getCoreRowModel, getSortedRowModel,
  getFilteredRowModel, getPaginationRowModel,
  flexRender,
} from '@tanstack/react-table'

// 列定义 — 类型安全
const columns = [
  columnHelper.accessor('name', {
    header: '名称',
    cell: info => info.getValue(),
  }),
  columnHelper.accessor('status', {
    header: '状态',
    cell: info => <Badge variant={info.getValue() === 'active'
      ? 'default' : 'secondary'}>{info.getValue()}</Badge>,
    filterFn: 'equals',
  }),
  columnHelper.accessor('createdAt', {
    header: '创建时间',
    cell: info => formatDate(info.getValue()),
    sortingFn: 'datetime',
  }),
]

function DataTable({ data }) {
  const table = useReactTable({
    data,
    columns,
    getCoreRowModel: getCoreRowModel(),
    getSortedRowModel: getSortedRowModel(),
    getFilteredRowModel: getFilteredRowModel(),
    getPaginationRowModel: getPaginationRowModel(),
  })

  return (
    <div className="rounded-md border">
      <Table>
        <TableHeader>
          {table.getHeaderGroups().map(hg => (
            <TableRow key={hg.id}>
              {hg.headers.map(header => (
                <TableHead key={header.id}
                  onClick={header.column.getToggleSortingHandler()}>
                  {flexRender(header.column.columnDef.header,
                    header.getContext())}
                  {{ asc: ' ↑', desc: ' ↓' }[header.column.getIsSorted() as string]}
                </TableHead>
              ))}
            </TableRow>
          ))}
        </TableHeader>
        <TableBody>
          {table.getRowModel().rows.map(row => (
            <TableRow key={row.id}>
              {row.getVisibleCells().map(cell => (
                <TableCell key={cell.id}>
                  {flexRender(cell.column.columnDef.cell,
                    cell.getContext())}
                </TableCell>
              ))}
            </TableRow>
          ))}
        </TableBody>
      </Table>
      {/* 分页控制 — shadcn Pagination */}
      <div className="flex items-center justify-end p-4">
        <Button variant="outline" size="sm"
          onClick={() => table.previousPage()}
          disabled={!table.getCanPreviousPage()}>上一页</Button>
        <span className="text-sm text-muted-foreground mx-2">
          第 {table.getState().pagination.pageIndex + 1} 页
        </span>
        <Button variant="outline" size="sm"
          onClick={() => table.nextPage()}
          disabled={!table.getCanNextPage()}>下一页</Button>
      </div>
    </div>
  )
}

数据表格对比

方案灵活度开箱功能适合场景
TanStack Table + shadcn极高基础排序/筛选产品级 SaaS,需定制
Ant Design ProTable中等全部内置企业后台,快速出活
MUI DataGrid (Pro)较高全部内置 + Excel 导出国际化企业 SaaS
AG Grid极高最全 (百万行)金融/数据密集型

13 主题系统:从「看起来一样」到「这就是你的品牌」

多数 SaaS 产品的 UI 库选择决定了产品的视觉基调。主题系统决定了你能走多远。

shadcn/ui 主题定制实战

/* globals.css — 品牌定制 */
@layer base {
  :root {
    /* 品牌: OpenClaw 紫 */
    --primary: 263 70% 65%;
    --primary-foreground: 0 0% 100%;

    /* 背景: 深蓝灰 */
    --background: 222 47% 5%;
    --foreground: 210 40% 98%;

    /* 强调: 亮青 */
    --accent: 187 92% 69%;
    --accent-foreground: 222 47% 5%;

    /* 圆角: 稍大,更柔和 */
    --radius: 0.75rem;
  }
}

/* 为 SaaS 仪表盘定制卡片风格 */
.card-dashboard {
  @apply rounded-xl border bg-card/50 backdrop-blur
    shadow-lg shadow-black/5 transition-all;
}

.card-dashboard:hover {
  @apply shadow-xl shadow-primary/5 border-primary/20;
}

暗色模式最佳实践

🌙 暗色模式三原则
  1. 用 CSS 变量而非 class 切换.dark class 改变 :root 变量值,组件无需感知模式
  2. 不要纯黑背景#000000 太刺眼。用 hsl(222, 47%, 5%) 这种带色调的深色
  3. 降低对比度而非提高:暗色模式下,前景色用 hsl(210, 40%, 90%) 而非纯白 #FFFFFF

14 动画与微交互

SaaS 的 UX 竞争在微交互层面。流畅的过渡让产品感觉「活」的,而非「生硬的」。

tailwindcss-animate — shadcn/ui 内置

// shadcn/ui 的 Dialog 已经内置动画
// data-[state=open]:animate-in
// data-[state=closed]:animate-out

// 自定义动画
const variants = {
  hidden: { opacity: 0, y: 10 },
  enter: { opacity: 1, y: 0 },
  exit: { opacity: 0, y: -10 },
}

// Framer Motion — 更复杂的动画
import { motion, AnimatePresence } from 'framer-motion'

function PageTransition({ children }) {
  return (
    <AnimatePresence mode="wait">
      <motion.div
        initial="hidden"
        animate="enter"
        exit="exit"
        variants={variants}
        transition={{ duration: 0.2 }}
      >
        {children}
      </motion.div>
    </AnimatePresence>
  )
}
动画性能陷阱:只用 transformopacity 做动画。这两个属性有 GPU 加速。避免动画 widthheighttopleft——它们触发布局重排,卡顿的根源。

15 Vue 生态的组件库

如果你的 SaaS 后端是 Python/Go 且前端用 Vue,这里有对应选项。

Naive UI — Vue 3 原生最优

国人 07akioni 开发,Vue 3 Composition API 原生编写。Tree-shaking 友好,TypeScript 原生支持,主题系统基于 CSS 变量。设计感比 Element Plus 现代。

import { NDataTable, NButton, NSpace } from 'naive-ui'

const columns = [
  { title: '名称', key: 'name' },
  { title: '状态', key: 'status' },
  { title: '操作', key: 'actions',
    render(row) {
      return h(NSpace, null, () => [
        h(NButton, { size: 'small' }, () => '编辑'),
        h(NButton, { size: 'small', type: 'error' }, () => '删除'),
      ])
    }
  },
]

Element Plus — 企业级 Vue 3

Element UI 的 Vue 3 版本。国内企业 Vue 项目的事实标准。组件数量多,文档中文,社区成熟。但设计语言偏旧(Element 2.0 风格),定制体验不如 Naive UI。

shadcn-vue — shadcn/ui 的 Vue 移植

社区驱动的 Vue 3 移植版,基于 Radix Vue(Radix UI 的 Vue 版本)+ Tailwind CSS。API 设计和理念与 React 版一致。

16 可访问性 (a11y) — 别等诉讼才想起它

如果你的 SaaS 面向欧美市场,WCAG 2.1 AA 合规是法律要求,不是锦上添花。2025 年欧洲无障碍法案 (EAA) 生效,不合规的产品可能被禁止销售。

Headless 组件库的可访问性优势

需求自己实现Radix / Headless UI
键盘导航手写 keydown 处理内置方向键/Tab/Esc
ARIA 角色手动添加 20+ 属性自动生成
焦点管理手写 focus-trap内置 Modal 焦点陷阱
屏幕阅读器需逐个测试预验证通过
高对比度需手动调整Tailwind 配色系统
// 一个可访问的 Tabs 组件 — Radix 自动处理所有 a11y
import * as Tabs from '@radix-ui/react-tabs'

function SettingsTabs() {
  return (
    <Tabs.Root defaultValue="account">
      <Tabs.List className="flex border-b">
        <Tabs.Trigger value="account"
          className="px-4 py-2 data-[state=active]:border-b-2
            data-[state=active]:border-primary">
          账户
        </Tabs.Trigger>
        <Tabs.Trigger value="security"
          className="px-4 py-2 data-[state=active]:border-b-2
            data-[state=active]:border-primary">
          安全
        </Tabs.Trigger>
      </Tabs.List>
      <Tabs.Content value="account" className="p-4">
        账户设置
      </Tabs.Content>
      <Tabs.Content value="security" className="p-4">
        安全设置
      </Tabs.Content>
    </Tabs.Root>
  )
}

17 性能优化:组件库的隐形成本

Bundle Size 对比

方案基础大小 (gzip)全量大小Tree-shaking
shadcn/ui + Tailwind5-15KB (CSS)按需✅ 天然按需
Ant Design~80KB (core)~300KB+✅ v5 支持
MUI~80KB (core)~350KB+✅ 支持
Chakra UI~50KB~200KB✅ 支持
Headless UI~15KB~15KB✅ 支持

运行时优化

⚡ CSS-in-JS 运行时开销

Ant Design v5 和 MUI 使用 runtime CSS-in-JS (emotion/styled)。每次组件渲染,样式在运行时计算并注入。这意味着:

shadcn/ui + Tailwind 的优势:样式在构建时生成,零运行时开销。SSR 开箱即用。

18 与 Agent 架构的关联

UI 组件库看似与 Agent 无关,但在 AI-native SaaS 中,二者深度交叉:

🤖 Agent 时代的 UI 趋势

AI SaaS 的 UI 组件需求

组件用途推荐实现
StreamingText逐字显示 LLM 输出自定义 + react-markdown
CodeBlock语法高亮代码展示Shiki / Prism + shadcn 样式
ToolCallCard展示 Agent 工具调用shadcn Card + Collapsible
ApprovalDialog人工审批 Agent 操作shadcn AlertDialog
TokenCounter显示 token 用量shadcn Badge + 自定义
ThinkingIndicator显示 Agent 思考中tailwindcss-animate 脉冲

19 常见坑与避坑指南

💥 坑 1:在同一个项目混用多个组件库

症状:Ant Design 的 Modal + shadcn 的 Button + MUI 的 Table。看起来每个都是「最好的」,但 CSS 冲突、样式不一致、包体积爆炸。

解法:选一个组件库做主力,其余用 Headless 原语补充。例如:shadcn/ui 主力 + Radix 补充特殊组件。

💥 坑 2:Ant Design + Next.js App Router

症状:SSR 样式闪烁 (FOUC) + hydration mismatch。

解法:在 layout.tsx 中用 <StyleProvider layer> + extractStyle。或干脆别在 Next.js App Router 里用 Ant Design——shadcn/ui 才是原生选择。

💥 坑 3:shadcn/ui 升级丢失自定义

症状:npx shadcn@latest add dialog 覆盖了你的自定义修改。

解法:npx shadcn@latest diff dialog 检查变更,手动合并。或将自定义放在 components/ui/ 之外的 wrapper 层。

💥 坑 4:CSS-in-JS + React Server Components

症状:Server Component 里使用 styled-components / emotion 报错。

解法:RSC 不支持 CSS-in-JS。在 RSC 中只能用 Tailwind / CSS Modules / CSS 变量。CSS-in-JS 限定在 Client Component 内。

💥 坑 5:不设置设计 Token 就开始开发

症状:6 个月后产品里 5 种灰色、3 种圆角、7 种字号。

解法:Day 1 就定义 tailwind.config 或 CSS 变量。颜色、间距、圆角、字号统一管理。宁可多花 1 天定义设计约束,省 100 天修复不一致。

20 决策总结

🎯 一句话选型

下一步阅读

📊 状态管理

组件选好后,状态怎么管?

⚛️ 前端框架

选什么框架承载你的组件库?

🔧 Agent 工具系统

工具调用的 UI 展示设计

🛡️ Agent 安全

人工审批流的 UI 交互