UI 与主题
01MVP 的视觉语言、token 体系、主题预设和组件约束——改样式前先读这个。
设计背景
这套模板的定位很明确:给需要快速上线 MVP 的开发者、技术创始人和小团队用的。核心目标就一个——在不牺牲专业感的前提下,尽快交付。
品牌个性:现代、极简、克制。接近 Vercel Design 风格,但在边框、留白和少量阴影上保留了自己的层次感。
设计方向是硬核极简:单色系优先、边框分层、紧凑信息密度,移动端默认接近 App 级紧凑程度。
硬性约束(每次提交 UI 代码前请自查):
- 永远优先语义化 token,而不是硬编码颜色(如
bg-background而非bg-white) - 使用边框作为主要分层机制,阴影只做辅助
- 交互反馈尽量依赖颜色、透明度和边框变化
- 所有页面默认支持键盘导航与清晰焦点态
先理解三层组件边界
项目使用 shadcn/ui,组件按三层划分:
packages/ui:纯 shadcn/ui 原版组件,不能手动修改,只能通过 shadcn CLI 更新products/01mvp/apps/web/src/shared/ui:当前 Web 应用复用的基础 UI,如 Logo、语言切换、主题切换products/01mvp/apps/web/src/features/products/01mvp/apps/web/src/pages:业务功能组件和页面组合
Typography
font-sans(Geist Sans + Source Han Sans CN):正文、界面、标题font-mono(Geist Mono):代码、日志
| Token | 尺寸 | 用途 |
|---|---|---|
text-sm | 14px | 次级文本、标签 |
text-base | 16px | 正文默认值 |
text-lg ~ text-xl | 18-20px | 强调正文、小标题 |
text-2xl | 24px | 模块标题 |
text-4xl ~ text-6xl | 36-60px | 页面标题、Hero |
大标题用 tracking-tight,长文正文用 leading-relaxed。
Color System
以黑白灰单色系为基础,通过少量语义色(success、destructive)承载状态,不作为装饰。
实际页面分层规则:
页面分层速查:最外层用黑色强边框 -> 内部元素用灰色细边框 -> 文档正文用浅灰底 -> 状态色只给反馈用。每个语义 token 在亮色和暗色模式下有对应取值,组件引用 token 就能自动适配,不需要写 dark: 前缀。
Spacing 和 Layout
基于 4px 基准:4, 8, 12, 16, 24, 32, 48, 64, 96
常用建议:按钮 px-4 py-2、输入框 px-3 py-2、卡片 p-6、Modal p-8、Section py-16 md:py-24。
交互层级
边框主要用来提示“这里能操作”。
- 主操作、购买、提交、继续阅读这类高意图动作,可以使用实色按钮或强边框按钮。
- 指向重要页面的卡片链接可以使用
theme-frame,但整块内容必须可点击。 - 主题、视图模式、筛选、复制、排序这类工具按钮默认要轻,优先用图标/文字按钮;只在 hover、focus 或启用状态出现轻背景或细边框。
- 普通说明、作者介绍、问题列表、版本声明和元信息不要用黑色大框。用正文、分割线、浅色底或留白来组织。
一个简单判断:如果用户看到边框后会想点一下,那它就应该真的能点;如果不能点,就不要让它长得像按钮。
Radius 和 Shadow
01MVP 默认方角(--radius: 0px)。头像、开关和 pill badge 可用 rounded-full。
推荐语义类:
<div className="theme-frame p-6">外层强面板</div>
<div className="theme-surface p-4">普通信息块</div>
<input className="theme-control h-12 px-4" />阴影策略:默认不依赖阴影,优先用边框和背景对比。只有浮层、悬浮卡片和下拉菜单才使用轻量阴影。
Motion
- 动画必须有明确目的,时长控制在
150ms到300ms - 尊重
prefers-reduced-motion - 推荐 easing:
ease-out、ease-in-out
主题实现
当前 Web 应用固定使用 theme-preset-01mvp,主题变量在 products/01mvp/apps/web/src/shared/styles/theme.css。公共导航只提供亮色 / 暗色切换,不再维护多套 preset。
新增页面、组件、弹窗、表单时,不要只按当前截图写死一套样式。默认要求是:亮色和暗色模式下都能读、能点、层级清楚。
优先使用这些语义类:
<section className="theme-frame bg-background p-4 text-foreground">
<div className="theme-surface p-4">
<input className="theme-control h-11 px-3" />
<button className="theme-action-primary h-11 px-4">保存</button>
</div>
</section>不要在业务组件里直接写:
bg-white/bg-black/bg-slate-*这类固定背景色text-gray-*/border-zinc-*这类固定文字和边框色shadow-xl/ 大圆角 / 任意 hex 或 OKLCH 颜色- 只适配当前 preset 的局部 CSS
如果确实需要新的视觉角色,比如"强调面板""代码预览框""付费提示块",先在 products/01mvp/apps/web/src/shared/styles/theme.css 里补变量和类,并给亮色和暗色模式都定义好,再到组件里使用这个语义类。
新增顶层页面也要确认用户能切换亮暗色:公共页面用现有 header 控制,应用内页面复用同一套 token。不要做一个完全游离在主题 token 系统之外的新 shell。
响应式设计
断点:sm: 640px、md: 768px、lg: 1024px、xl: 1280px、2xl: 1536px。移动优先,用 md:、lg: 前缀逐层增强。交互元素尽量达到 44x44px 触摸区域。
手机端要更紧凑
移动端是主要使用场景之一,默认按更接近微信小程序和 WhatsApp 的信息密度来做:
- 手机端先保证可扫读,不要把桌面端的大留白直接搬过来
- section 默认用更小的
py,到md:/lg:再放大 - 卡片、列表、按钮组在手机端少用大圆角、大阴影和过厚 padding
- 保留 44px 左右的点击区域,但减少图标框、文字上下间距和整屏空白
- 底部导航、固定按钮和弹窗要避开安全区,不要让正文被迫留出过大底部空白
设置页和表单页要更像 App 设置,而不是桌面后台卡片:
- 优先用分组列表、分割线、边框和背景层级,不要在手机端堆大阴影卡片
- 分组间距通常控制在
8px到12px,内容 padding 通常控制在12px到16px - 输入框高度保持
44px到48px,既能点击,又不撑高页面 - 未修改时不要常驻一个灰色不可用的保存按钮;用户修改后再出现满宽主按钮
- 头像、账号信息这类内容优先用横向行:小头像 + 操作文字 / 按钮,不要用大头像占掉半屏
常见改造点
换 Logo
替换 products/01mvp/apps/web/src/shared/ui/logo.tsx 中的 SVG/图片文件,或用 products/01mvp/apps/web/src/features 下的业务组件覆盖。如果你有多个 Logo 变体(如亮色/暗色),可在 Logo 组件中根据当前主题自动切换。
改配色
在 products/01mvp/apps/web/src/shared/styles/theme.css 中修改 CSS 变量。核心变量包括:
| 变量名 | 用途 | 示例值 |
|---|---|---|
--primary | 主色,按钮、链接等强调色 | hsl(0 0% 3.9%) |
--background | 页面背景色 | hsl(0 0% 100%) |
--foreground | 正文文字颜色 | hsl(0 0% 3.9%) |
--muted | 次级背景色,用于卡片、标签等 | hsl(0 0% 96.1%) |
--muted-foreground | 次级文字颜色 | hsl(0 0% 45.1%) |
--border | 边框颜色 | hsl(0 0% 89.8%) |
--ring | 聚焦环颜色 | hsl(0 0% 3.9%) |
--destructive | 危险操作颜色(删除等) | #dc2626 |
不要只改 :root。当前主题系统按 .theme-preset-01mvp 和对应 dark mode 覆盖变量。新增变量时,亮色和暗色都要有定义,否则切换主题时会退回到错误的视觉状态。
改字体
字体在 products/01mvp/apps/web/src/shared/styles/fonts.css 中通过 @font-face 引入(默认使用 Geist Sans、Geist Mono 和 Source Han Sans CN)。改字体时先替换字体文件或 CDN,再更新对应 CSS 变量。
加新页面
在 products/01mvp/apps/web/src/routes/ 下创建 TanStack Router 文件路由。例如创建 /about 页面:新建 products/01mvp/apps/web/src/routes/{-$locale}/(root-layout)/about/index.tsx,使用 createFileRoute 导出路由即可。
给页面补加载态和空状态
- 加载态:使用 TanStack Router 的
pendingComponent或组件内的 query loading state - 错误态:使用 route 的
errorComponent或已有错误页组件 - 空状态:在组件内判断数据是否为空,渲染友好的占位提示
相关资源
这篇文档有问题?