跳到主要内容

设计系统

Elftia 的视觉设计追求温暖、亲和、高级感,避免冰冷的纯技术风格。


设计哲学

色彩原则

  • 暖调中性色:所有表面色带有微暖色调(hue 24-38),而非纯灰度
  • 深色模式:暖炭色(warm charcoal),而非纯黑
  • 浅色模式:暖奶油色(warm cream),而非纯白
  • 禁止冷灰:不使用 hsl(0, 0%, ...) 的纯灰色作为背景或边框

圆角原则

元素圆角Tailwind 类
卡片/容器12pxrounded-xl
输入框12pxrounded-xl
按钮/徽章6pxrounded-md
默认8pxrounded-lg

禁止使用小于 4px 的圆角(除线条装饰)。

排版原则

用途字体Tailwind 类
展示/大标题Noto Serif / Georgiafont-display
正文/界面Interfont-sans
代码JetBrains Monofont-mono

大标题使用 font-semibold(而非 font-bold),配合 tracking-tight


颜色系统

语义化 Token

所有颜色通过 CSS 变量定义,Tailwind 配置映射为实用类。禁止使用硬编码颜色值。

{/* 禁止 */}
<div className="bg-white text-black border-gray-200">
<div style={{ backgroundColor: '#ffffff' }}>

{/* 正确 */}
<div className="bg-surface-0 text-foreground border-border">
<div style={{ backgroundColor: 'var(--surface-0)' }}>

常用 Token 对照表

用途Tailwind 类CSS 变量
页面背景bg-backgroundvar(--background)
L0 背景bg-surface-0var(--surface-0)
L1 背景(卡片/侧边栏)bg-surface-1var(--surface-1)
L2 背景(输入框/次级容器)bg-surface-2var(--surface-2)
L3 背景(弹出层)bg-surface-3var(--surface-3)
主文本text-foregroundvar(--foreground)
次要文本text-muted-foregroundvar(--muted-foreground)
辅助文本text-text-subtlevar(--text-subtle)
边框border-bordervar(--border)
主题色bg-primary / text-primaryvar(--primary)
成功text-successvar(--success)
错误text-destructivevar(--destructive)
警告text-warningvar(--warning)

深色/浅色模式

切换机制

使用 Tailwind 的 class 策略(darkMode: 'class'),通过 ThemeContext 统一管理。

{/* 正确:通过 useTheme 获取主题信息 */}
import { useTheme } from '@/shared/state/themeStore';

function MyComponent() {
const { mode, resolvedMode, userTheme } = useTheme();
}

{/* 禁止:手动检测 */}
const isDark = localStorage.getItem('theme') === 'dark';
const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;

层级体系

深色模式依靠亮度区分层级:越靠近用户的 UI 元素,背景越亮。

层级Token亮度用途参考色值
L0--surface-07%页面主背景#121212
L1--surface-112%侧边栏、卡片#1E1E1E
L2--surface-217%次级容器、输入框#2B2928
L3--surface-322%弹出层、Dropdown#383635

层级间亮度差必须 >= 4-5%,确保肉眼可区分。

边框规范

深色模式下人眼对暗部的感知灵敏度降低,边框需要更高可见度:

场景浅色模式深色模式
卡片边框border-border/30 ~ /40border-border/50 ~ /70
分隔线border-border/20 ~ /30border-border/40 ~ /50
输入框border-border/40border-border/60 ~ border-border

WCAG 无障碍

基于 WCAG 2.1 标准。

对比度要求

元素类型最低对比度说明
普通文本 (< 18pt)4.5:1正文、描述、标签
大号文本 (>= 18pt 或 14pt 加粗)3:1标题
UI 控件(图标、边框)3:1输入框边框、图标、徽章
禁用状态豁免,建议 2.5:1避免完全不可见

文本 Token 使用规则

Token亮度允许的背景典型用途
text-foreground (93%)最高所有 surface主标题、正文
text-muted-foreground (65%)surface-0, surface-1次要文本、描述
text-text-subtle (50%)仅 surface-0时间戳、元数据
{/* 好:描述文字使用 text-muted */}
<p className="text-muted-foreground">共 5 个模型</p>

{/* 不好:在 surface-1 卡片内使用 text-subtle(对比度不足) */}
<div className="bg-surface-1">
<span className="text-text-subtle">看不清</span>
</div>

颜色传达信息

不要仅依赖颜色传达状态,必须同时配有文字提示或图标:

{/* 不好:仅靠颜色 */}
<div className={status === 'error' ? 'border-red-500' : 'border-border'} />

{/* 好:颜色 + 图标 + 文字 */}
<div className={status === 'error' ? 'border-destructive' : 'border-border'}>
{status === 'error' && <AlertCircle className="text-destructive" />}
<span>{errorMessage}</span>
</div>

焦点状态

使用 focus-visible 为键盘导航用户提供焦点边框:

{/* 好 */}
<button className="focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none">
按钮
</button>

{/* 禁止:移除焦点样式 */}
<button className="outline-none focus:outline-none">按钮</button>

动画偏好

尊重系统的 prefers-reduced-motion 设置:

<div className="motion-safe:animate-fadeIn motion-reduce:animate-none">
内容
</div>

UI 组件规范

禁止使用原生控件

原生控件项目组件路径
<select>Select@/components/ui/select
<input type="text">Input@/components/ui/input
<input type="checkbox">Switch / Checkbox@/components/ui/switch
<button>Button@/components/ui/button
window.confirm()ConfirmDialog@/components/ui/confirm-dialog
window.alert()Toast 组件-

下拉组件

所有下拉组件应支持:

  • 视口感知定位(使用 useDropdownPosition Hook)
  • 点击外部区域关闭
  • Escape 键关闭
  • 项目主题色和样式

壁纸透明度系统

当用户设置壁纸时,body 添加 data-wallpaper-active="true" 属性,触发 CSS 透明度规则。

CSS 规则层级

优先级选择器效果用途
1.bg-background, .bg-surface-0全透明页面主背景
2.wallpaper-blur35% 不透明 + blur主容器(WorkspaceShell)
3.wallpaper-blur .wallpaper-blur透明 + blur*0.67嵌套容器(避免叠加)
4bg-surface-0/XX.wallpaper-blur透明布局面板
5bg-surface-1/XX.wallpaper-blur12% + blur内容卡片(alpha 变体)
6bg-surface-1, bg-popover15% + blur按钮/卡片(排除 input)
7bg-surface-220% + blur次级容器(排除 input)
8.wallpaper-panel85% + blur*1.33浮动下拉/菜单
9.wallpaper-solid不透明 surface-0Dialog/Modal

层级叠加模型

WorkspaceShell (wallpaper-blur, 35%)
+-- Sidebar (bg-surface-0/75 -> transparent) = 35%
+-- Main content (bg-background -> transparent) = 35%
| +-- 内容卡片 (bg-surface-1/80 -> 12%) ~ 43%
| +-- 按钮 (bg-surface-1 -> 15%) ~ 45%
| +-- 输入框 (input -> 实色) = 不透明
| +-- 下拉面板 (wallpaper-panel -> 85%) = 85%
+-- Bottombar (wallpaper-blur -> transparent) = 35%

壁纸 CSS 类使用指南

场景推荐做法
页面主容器bg-backgroundbg-surface-0(自动全透明)
按钮/卡片bg-surface-1bg-surface-1/80(自动半透明)
次级容器bg-surface-2(自动 20% 半透明)
主布局容器添加 wallpaper-blur
浮动下拉/菜单添加 wallpaper-panel
Dialog/Modal添加 wallpaper-solid
输入框<input> / <textarea> + bg-surface-1(自动排除,保持实色)

已内置壁纸支持的组件

半透明毛玻璃 (wallpaper-panel):

  • Select 下拉面板
  • DropdownMenuContent / DropdownMenuSubContent
  • ContextMenuContent / ContextMenuSubContent

完全不透明 (wallpaper-solid):

  • DialogContent

禁止事项

  • 禁止使用裸 bg-surface-0 作为卡片背景(壁纸模式下会全透明消失)
  • 禁止使用 bg-whitebg-blackbg-gray-*bg-neutral-* 等硬编码颜色
  • 如需不透明效果,请同时添加 wallpaper-solid

用户可自定义着色层(0.1.11+)

在上述"基础壁纸透明度系统"之上,壁纸面板暴露了三类用户可调的色彩层,每一类都通过 body 数据属性 + CSS 变量 来驱动,CSS 选择器层叠地覆盖默认 surface 表现。所有写入由 themeUtils.applyWallpaperToDocument 集中管理,组件不要直接 body.style.setProperty

1. 遮罩层(body::before 伪元素)

数据属性触发条件CSS 变量
data-wallpaper-active="true"任何壁纸源就绪--wp-dimming (0–1),--wp-dim-{h,s,l}
data-wp-dim-gradient="true"wallpaperDimmingGradient 已设--wp-dim-gradient(覆盖 HSL)

亮度 fallback:未设 --wp-dim-l 时,浅色模式默认 100%、深色模式默认 0%(对应原来的 white/black 行为)。

2. 元素表面层(侧边栏 / 卡片 / 标签 / 上下文菜单)

数据属性触发条件CSS 变量
data-wp-element-tint="true"wallpaperElementTint 是有效 hex--wp-elem-{h,s,l}
data-wp-element-gradient="true"wallpaperElementGradient 已设--wp-elem-gradient-{15,20,35}(按 surface tier alpha 分版本)

设计要点:每个 surface tier 保留独立 alpha(surface-1 = 15%、surface-2 = 20%、.wallpaper-card = 35%),所以即使整体改成同一色调,视觉层级依然可分辨。Input/Textarea 和 wallpaper-solid / wallpaper-panel 选择器都被 :not(...) 排除——可读性优先于色彩一致性。

3. 消息气泡层(用户 / 助手独立)

数据属性触发条件CSS 变量
data-wp-bubble-override="true"wallpaperBubbleOverride === true--wp-bubble-alpha (0–1)
data-wp-bubble-tint-user="true"用户气泡 hex 有效--wp-bubble-user-{h,s,l}
data-wp-bubble-tint-assistant="true"助手气泡 hex 有效--wp-bubble-asst-{h,s,l}
data-wp-bubble-gradient-{user,assistant}="true"对应渐变已设--wp-bubble-{user,asst}-gradient

CSS 选择器是分两层级联的:

/* 第 1 层:override 关闭时,气泡跟随元素 tint(继承) */
body[data-wp-element-tint="true"]:not([data-wp-bubble-override="true"]) .chat-bubble-user,
body[data-wp-element-tint="true"]:not([data-wp-bubble-override="true"]) .chat-bubble-assistant {
background: hsl(var(--wp-elem-h) var(--wp-elem-s) var(--wp-elem-l) / var(--wp-alpha-35, 0.35)) !important;
}

/* 第 2 层:override 开启时,per-side tint + 自定义 alpha 生效 */
body[data-wp-bubble-override="true"][data-wp-bubble-tint-user="true"] .chat-bubble-user {
background: hsl(var(--wp-bubble-user-h) var(--wp-bubble-user-s) var(--wp-bubble-user-l) / var(--wp-bubble-alpha, var(--wp-alpha-35, 0.35))) !important;
}

第 2 层选择器具有更高特异性 + 写在后面,因此 !important 平局时会胜出。

添加新的 user-tint 字段时

  1. ThemePreferencessettings-types.ts)和 ThemePreferencesSchemaconfigSchema.ts)同时加字段
  2. ThemeService.setWallpaperPreferences 增加 setter 分支 + readPreferences/importProfile/resetTheme/mergePreferences 默认值
  3. ThemeRouter 的两处 Zod schema(themeProfileSchematheme:setWallpaperPreferences 的 inline schema)都增加字段
  4. ThemeContext 增加 context value + setWallpaperPreferences 入参 + commitState fallback
  5. themeUtils.applyWallpaperToDocument 增加参数 + _prev* 缓存 + body 属性/CSS 变量写入
  6. WallpaperPanel 增加 UI(开关/取色盘/滑杆) + i18n 三语
  7. index.css 增加选择器(注意层级顺序与 !important 优先级)
  8. 透传链:AppearanceTabSettings.tsx / ThemeStudioPage.tsx(含 DraftState + effective + Apply 提交)
  9. Agent stubs:desktop-api.ts(preload 契约)、shared/agent/types/settings.ts(共享接口)、shared/agent/web/theme.ts(HTTP 实现)
  10. 更新本表 + architecture-index SKILL.md 的字段速查表 + ipc-channels.mdtheme:setWallpaperPreferences 字段表 + appearance.md 用户文档

主题兼容开发规则

只使用语义化 Token

禁止直接写 #fff/rgb() 或 Tailwind 默认色值。

统一使用 ThemeContext

组件需要主题信息时通过 useTheme() 读取。

尊重可配置字体

文本/代码区域使用 CSS 变量:

<div style={{ fontFamily: 'var(--font-ui)' }}>普通文本</div>
<code style={{ fontFamily: 'var(--font-code)' }}>代码</code>

{/* 或使用 Tailwind 类 */}
<div className="font-ui">普通文本</div>
<code className="font-code">代码</code>

允许 customCss 覆盖

避免 !important 和大段内联样式,优先使用 className + CSS 变量。


自测检查清单

创建新 UI 组件时:

  • 只使用语义化 Token(不使用硬编码颜色)
  • 通过 useTheme() 获取主题信息
  • 测试深色/浅色模式切换
  • 测试壁纸透明度效果
  • 普通文本对比度 >= 4.5:1
  • 深色模式边框使用 dark:border-border/50 以上
  • text-subtle 仅用于 surface-0 背景
  • 交互元素使用语义标签或添加 role + tabIndex
  • 焦点样式使用 focus-visible:ring-2
  • 屏幕亮度 30% 下文字和边框仍可辨认
  • 窗口缩放至 200% 时布局不破裂