UI Patterns 是一个面向 SaaS 应用页面构建与组件组合的技能。它关注结构层面的决策:使用哪些组件、如何布局页面,以及如何处理所有 UI 状态。视觉样式(颜色、字体、间距系统)参见 beautify 技能;用户流程设计参见 ux-design;无障碍要求(语义化 HTML、键盘导航、ARIA)参见 ux-design/ACCESSIBILITY.md。组件参考见 COMPONENTS.md,页面布局配方见 LAYOUTS.md。
组件库选择
默认选择:shadcn/ui + Tailwind CSS。据资料,这是 90% 创始人自建 SaaS 应用的合适选择。shadcn/ui 提供基于 Radix 原语的可复制粘贴组件——你拥有代码、可自由定制、避免依赖锁定。
决策框架(按场景选择):
- 面向公众的 SaaS 产品:shadcn/ui + Tailwind,理由是完全设计控制、默认样式好、可访问。
- 内部管理工具 / 后台:Ant Design 或 MUI,理由是密集数据展示、内置表格/表单模式、交付更快。
- 只需要一个特定原语(tooltip、dialog):直接使用 Radix,跳过样式层,只用行为。
- 已有 MUI/Chakra 代码库:沿用当前库,理由是项目中途迁移成本通常不划算。
- React Native / 移动应用:React Native Paper 或 Tamagui,Web 组件库不适用。
使用 shadcn/ui 时:逐个安装组件(npx shadcn-ui@latest add button),不要一次性全部安装。配置 components.json 路径别名以匹配项目结构。
页面组合原则
每个 SaaS 页面遵循可预测的结构,使用以下内容区域:
- 页头:标题、描述、主操作按钮(右上角)。若有嵌套,面包屑位于标题上方。高度 64-80px。
- 主内容:页面的主要目的。桌面端占宽度 60-70%。这是“脊柱”——用户视线跟随的垂直栏。
- 次要内容:支持信息、筛选器、相关项。侧边栏或主内容下方。占宽度 30-40%,移动端为全宽在下方。
- 操作区:表单的粘性底栏、悬浮操作按钮,或与内容内联。绝不要把主操作隐藏在菜单里。
页面脊柱原则:每个页面有一条主导垂直栏。仪表盘上是“指标到活动”的流;设置页上是表单区块;列表页上是表格。找出脊柱并给予视觉优先,其他内容支撑它。
标准页面容器:max-w-7xl mx-auto px-4 sm:px-6 lg:px-8。内容密集型页面(文档、设置表单):max-w-3xl。全出血布局(仪表盘):不设最大宽度,用侧边栏约束。
状态矩阵
每个数据驱动组件必须处理五种状态,缺少任一种都会造成体验破损。构建任何展示动态数据的组件时,都要实现全部五种:
加载状态:使用与真实内容形状一致的骨架加载器。绝不要对行内内容使用 spinner——spinner 仅用于整页加载或按钮操作。表格渲染 5-8 行骨架,列宽匹配的块;卡片渲染卡片外壳加脉冲占位块;列表渲染 3-5 个骨架项。使用 animate-pulse 与 bg-muted 块,高度宽度匹配实际内容。
空状态:最被忽视的状态。每个空状态需要:图标或插图、说明此处将出现什么的标题、如何填充它的描述、主 CTA 按钮。无项目→“创建你的第一个项目”按钮;无搜索结果→“试试其他关键词”建议;无通知→“你都看完了”消息(无需 CTA)。空状态是引导时刻,用来教用户下一步做什么。
错误状态:用平实语言说明哪里出错并提供重试操作。绝不显示原始错误码或堆栈跟踪。行内错误:失败组件下方红字 + 重试按钮;整页错误:居中消息加重试和“返回”选项;网络错误:“连接丢失。重试中……”加自动重试逻辑。表单错误时始终保留用户输入,绝不清空字段。
成功状态:正常内容展示。这是多数开发者最先(有时唯一)构建的。确保处理可变内容长度——长标题、缺失可选字段、各种图片宽高比。
溢出状态:数据过多时怎么办。表格:每 10-25 行分页,显示总数,提供每页条数选择器;列表:无限滚动或“加载更多”按钮,显示计数“Showing 20 of 148”;文本:省略号截断并加“显示更多”展开,使用 line-clamp-2 或 line-clamp-3;标签/徽章:显示前 3 个 + “+N more”胶囊。
响应式策略
采用移动优先配合 Tailwind 断点系统。默认样式针对移动端,再逐层叠加复杂度。断点:default 0-639px 手机;sm 640px+ 大手机、小平板;md 768px+ 平板;lg 1024px+ 小型笔记本、横屏平板;xl 1280px+ 桌面;2xl 1536px+ 大显示器。
各断点变化:
- 移动端(默认):单列。底部导航。卡片纵向堆叠。表格变为卡片列表。侧边栏隐藏在汉堡菜单后。模态框全屏。水平内边距 16px。
- sm:小幅调整。适当处两列网格。内边距略增。
- md:侧边栏可作为可折叠覆盖层出现。表格可横向滚动显示。两到三列网格。内边距 24px。
- lg:完整侧边栏可见。表格正常显示。多列布局启用。仪表盘网格扩展。内边距 32px。
- xl+:最大宽度容器居中内容。两侧留更多空白。可选:更宽侧边栏、更多列。
关键响应式模式:侧边栏导航→底部标签栏(最多 5 项)或汉堡菜单;数据表格→堆叠卡片显示关键字段;多列网格→单列堆叠;并排面板→标签页或堆叠区块;悬停 tooltip→点击显示或始终可见标签;右键上下文菜单→长按菜单或带图标操作行;模态对话框→从底部滑出的全屏面板。
对于侧边栏转底部导航模式:不要只隐藏侧边栏,而要构建专门的 BottomNav 组件,含 4-5 个图标+标签项。这些项应映射到顶层侧边栏区块,而非 1:1 复制。
深色模式
何时实现:若为验证做 MVP,跳过深色模式;若构建用户会长时间使用的产品(仪表盘、开发工具、写作应用),从第 1 天起实现。后期添加深色模式需要触及每个组件,从一开始构建显著更省。
实现方式:
- 在 :root 和 .dark 中将所有颜色定义为 CSS 自定义属性。
- 使用 Tailwind 的 dark: 前缀做覆盖。
- 若使用 shadcn/ui,此配置已就绪——只需在 globals.css 中配置主题。
- 通过在 <html> 元素上切换 class:document.documentElement.classList.toggle('dark')。
- 用 prefers-color-scheme 媒体查询尊重系统偏好,但允许用户覆盖。
- 将偏好持久化到 localStorage。
除替换背景/文本颜色外还需调整:阴影——大幅降低不透明度或完全移除,深色表面不投出可见阴影,改用细微边框或更亮表面色营造层次;边框——提高可见度,使用 border-border(深色模式下映射为更亮值)而非硬编码颜色;图片与插图——略微降低亮度(brightness-90),避免深色背景上的纯白图片;状态色——降低饱和度、提高亮度,在白底可用的红色在深灰底不可用,需要更柔和;悬停态——使用 hover:bg-muted 而非硬编码不透明度变化,浅色模式悬停变暗、深色模式悬停变亮;焦点环——确保深色背景上可见,默认蓝色环可用,自定义彩色环可能不可用。
常见错误
- 从零构建自定义组件库→使用 shadcn/ui,交付后再定制。
- 只实现成功状态→为每个数据组件构建全部五种状态。
- 到处使用 spinner→行内内容用骨架加载器,spinner 仅用于页面级或按钮加载。
- 固定布局不响应屏幕尺寸→采用移动优先响应式配合 Tailwind 断点,在 375px 宽度测试。
- 把关键操作隐藏在下拉菜单→主操作使用专用按钮,仅次要/再次要项放入菜单。
- 像素级自定义断点→使用 Tailwind 标准断点,它们覆盖真实设备。
- 把深色模式当事后想法→项目开始时就决定:现在实现或明确推迟,不要半实现。
- 移动端表格仅横向滚动→在移动端将表格转换为卡片布局,显示关键数据点。
- 模态框上叠加模态框→一次一个模态框,模态框中的次要操作用 sheet/滑出面板。
- 区块间距不一致→选定一个页面区块间距并坚持:页区块用 space-y-8,区块内用 space-y-4。