DESIGN.md · 插件视觉一致性规范(beautifului 审美)
读者是写 UI 的 Agent/开发者。目标:任何来源的插件,界面观感与宿主完全一致,
达到 beautifului.dev(MIT)的质感。
规则分两类:强制(可机器校验/一眼可辨)与默认(无充分理由不得偏离)。
契约问题看 ../plugins.md;本文件只管"长什么样"。
Token 单一事实源:apps/desktop/src/renderer/src/styles/beautifului.css(= foundation 原版),
组件参考源码归档在 docs/ui-ref/beautifului/*.json(26 个 registry)。
0. 两条铁律
- 色值只允许来自 token。任何
#hex/rgb()/oklch() 硬编码(规范中列出的除外)都算违规;
语义永远用 token 名表达(text-ink-3 而不是 text-gray-400)。
- 不发明新组件形态。先查本文 §4 标准模式与公共组件库;确需新形态时,
用 §1 的 token 组合拼装,不引入新色/新圆角/新阴影。
1. Token 表(单一事实源:peer://assets/tokens.css = 宿主 beautifului.css)
表面(背景)——冷调近白,亮暗两套完整 token
| token |
意图 |
--page |
页面最底层背景 |
--canvas |
画布(primary 按钮的文字色取这里) |
--surface |
卡片/面板表面(纯白) |
--inset |
内嵌区域(输入框底、预览底、代码块底) |
--hover / --hover-2 |
悬停态一级/二级 |
文字——中性 ink ramp(深灰,不用纯黑)
| token |
意图 |
--ink |
主文字(标题、关键值) |
--ink-2 |
次级文字(正文说明) |
--ink-3 |
弱化文字(占位、提示、元信息、路径) |
边线——实色 hairline,不用 alpha
| token |
意图 |
--line |
常规边框(卡片、列表分隔) |
--line-strong |
强边框(输入框、可点击描边) |
--line-soft |
极弱分隔 |
强调与语义——蓝色 accent,语义色只作"调味料"
| token |
意图 |
--accent / --accent-ink / --accent-tint |
焦点环/选区/选中态/链接(accent-ink 负责可交互文本,保 AA 对比) |
--green / --green-tint |
成功/正常 |
--orange / --orange-tint |
警示 |
--red / --red-tint |
错误/危险 |
阴影——「ring + 分层柔影」,不是纯 hairline
| token |
意图 |
--shadow-btn |
ring(line-strong) + xs——secondary 按钮 |
--shadow-card |
ring(line) + sm——卡片 |
--shadow-raised |
ring(line) + md——悬浮件 |
--shadow-overlay |
ring(line) + lg——弹层/模态 |
--shadow-inset-field |
输入框内嵌阴影 |
--shadow-hairline |
纯 ring(无投影) |
暗色模式 ring 自动切换为低透明白渐进(0.10→0.15 按浮起高度),阴影加深——
黑白主题不是简单反色:暗色有一套专属 elevation 表达。
圆角体系(关键差异)
| token |
值 |
用途 |
--radius-chip |
6px |
chips、小徽标 |
--radius-control |
8px |
输入框、菜单行、icon 按钮 |
--radius-card |
10px |
卡片 |
--radius-window |
14px |
窗口级容器(composer 外壳、弹窗) |
rounded-full |
999px |
按钮(pill 是核心语言)、状态点、头像 |
动效——强缓动曲线,内置 ease 太弱
| token |
值 |
用途 |
--ease-out-strong |
cubic-bezier(0.23, 1, 0.32, 1) |
大位移(滑层、展开、fade-up) |
--ease-in-out-strong |
cubic-bezier(0.77, 0, 0.175, 1) |
往返位移 |
--ease-link |
cubic-bezier(0.16, 1, 0.3, 1) |
下划线/微交互 |
| 控件默认 |
150ms ease-out |
pill 按钮、悬停反馈 |
| 按压 |
active:scale-[0.96] |
所有可点 pill |
2. 类名词汇(宿主 Tailwind 映射,renderer 组件直接用)
背景 bg-page / bg-canvas / bg-surface / bg-inset / bg-hover / bg-hover-2 / bg-accent / bg-accent/10 ·
文字 text-ink / text-ink-2 / text-ink-3 / text-accent / text-accent-ink / text-red ·
边框 border-line / border-line-strong / border-dashed border-line ·
圆角 rounded-chip(6)rounded-control(8)rounded-card(10)rounded-window(14)rounded-full(pill)·
阴影 shadow-btn / shadow-card / shadow-raised / shadow-overlay。
3. 字号与间距
- 基准 14px/1.5,
letter-spacing -0.01em;字号阶:11px(元信息/徽标)→ 12px(chips、正文说明)→ 12.5/13px(行标题、按钮)→ 14px(正文)→ 16px(页面标题)。不使用其他字号。
- 间距基数 4px:卡片内边距
px-3 py-2.5(紧凑)或 primitive-card-pad(12px),区块间 space-y-1.5,小节间 pt-3。
- 图标 12–16px 线条风格,
currentColor;一律 lucide(宿主白名单 plugin-icons.tsx),禁止自绘 svg。
- 颜色只作"调味料":结构永远是中性 ink + line,彩色仅出现在状态/数值/选中。
4. 标准模式
renderer 组件优先用组件库:@aso/plugin-sdk/ui 的 Button/SettingRow/LoadingState/
ErrorState/EmptyState/Badge… 已按本规范实现(见 L6)。
下面的 HTML 形态留给 iframe 插件页作者。
按钮(pill 是核心语言)
设置行(label 左 / 控件右)
卡片(window 级容器用 14px)
徽标 / 空态 / 开关
徽标 rounded-chip bg-hover px-1.5 py-0.5 text-[10px] text-ink-3;
空态 = 虚线卡(同旧版);开关 h-5 w-9(行内)/ h-6 w-10(表单级),
滑块 size-4/5 rounded-full bg-white shadow,开 bg-accent、关 bg-line-strong。
5. 动效范式
- 悬停反馈:150ms ease-out,只变 background-color/text(配
transition-colors)
- 内容进场:
fade-up 400ms var(--ease-out-strong)(聊天消息、卡片)
- 弹层:
pop-in(scale 0.95→1)+ shadow-overlay
- 滑动高亮层(菜单/标签条):220ms
cubic-bezier(0.23,1,0.32,1)(GlideMenu)
- 流式输出:光标 2px
--ink 常亮→结束后闪烁;prefers-reduced-motion 全局降级
6. iframe 插件页附加规则
<head> 必须引入 <link rel="stylesheet" href="peer://assets/tokens.css" />,并用同名 token 编译自己的 Tailwind
- 主题跟随宿主推送(
useHarnessTheme),禁止 prefers-color-scheme 自测;亮暗两套 token 都要核对
- 与宿主的视觉差异应趋近于零:同 token + 同类名词汇 + 同字号阶 + 同圆角/阴影体系
7. renderer 组件附加规则
- 类名直接用宿主词汇(§2),无需引样式;优先 import 公共组件库(L6),不要手写标准模式
- React 必须来自
ctx.react(见 L4),否则 hooks 失效
- 禁止操作宿主 DOM(document 全局查询/改样式),一切交互限制在自己的组件子树
- 机器关卡:
pnpm plugin:check <插件目录>(hex 色值 / React 副本 / 三态缺失等,见 L7)
8. 自检清单(提发布前)