Skip to content

OpenClaw 的自定义主题导入功能允许用户从 tweakcn 链接导入一个浏览器本地存储的主题,在现有 claw、knot、dash 三套内置主题之外新增一个 custom 族。导入的 payload 必须同时包含 light 和 dark cssVars 块,否则会被拒绝。导入后通过 Settings > Appearance 中的 Custom 卡片选择,支持 light/dark/system 模式切换,仅在当前浏览器持久化,不写入网关配置。

OpenClaw 自定义主题导入配置:通过 tweakcn 链接添加主题

Status: approved in terminal on 2026-04-22

概要

在 OpenClaw Control UI 中新增一个精确的浏览器本地自定义主题插槽,可从 tweakcn 分享链接导入。现有内置主题族仍为 clawknotdash。新增的 custom 族的行为与普通 OpenClaw 主题族一致,当导入的 tweakcn payload 同时包含 light 和 dark token 集合时,支持 lightdarksystem 模式。

导入的主题仅存储在当前浏览器配置文件中,与 Control UI 其他设置一同保存。它不会写入网关配置,也不会跨设备或浏览器同步。

问题

Control UI 主题系统目前封闭在三个硬编码主题族中:

  • ui/src/ui/theme.ts
  • ui/src/ui/views/config.ts
  • ui/src/styles/base.css

用户可以在内置族和模式变体之间切换,但无法在不编辑仓库 CSS 的情况下从 tweakcn 引入主题。当前需求小于一个完整的主题系统:保留三个内置族,并增加一个用户控制的导入插槽,可通过 tweakcn 链接替换。

目标

  • 保持现有内置主题族不变。
  • 仅增加一个导入的自定义插槽,而非主题库。
  • 接受 tweakcn 分享链接或直接的 https://tweakcn.com/r/themes/{id} URL。
  • 导入的主题仅持久化在浏览器本地存储中。
  • 导入插槽与现有的 lightdarksystem 模式控制配合工作。
  • 保持安全的失败行为:错误的导入绝不会破坏当前 UI 主题。

非目标

  • 不支持多主题库或浏览器本地的导入列表。
  • 不支持网关侧持久化或跨设备同步。
  • 不支持任意 CSS 编辑器或原始主题 JSON 编辑器。
  • 不会自动从 tweakcn 加载远程字体资源。
  • 不支持仅暴露一种模式的 tweakcn payload。
  • 不会对仓库进行超出 Control UI 所需接缝的全局主题重构。

已确定的用户决策

  • 保留三个内置主题。
  • 增加一个 tweakcn 驱动的导入插槽。
  • 主题存储在浏览器中,而非网关配置。
  • 导入插槽支持 lightdarksystem
  • 后续导入覆盖自定义插槽是预期行为。

推荐方案

在 Control UI 主题模型中增加第四个主题族 ID custom。仅当存在有效的 tweakcn 导入时,custom 族才变为可选。导入的 payload 被归一化为 OpenClaw 特定的自定义主题记录,并与其他 UI 设置一起存储在浏览器本地存储中。

运行时,OpenClaw 渲染一个受控的 <style> 标签,定义解析后的自定义 CSS 变量块:

css
:root[data-theme="custom"] { ... }
:root[data-theme="custom-light"] { ... }

这使自定义主题变量范围限定在 custom 族内,避免将内联 CSS 变量泄漏到内置族中。

架构

主题模型

更新 ui/src/ui/theme.ts

  • ThemeName 扩展为包含 custom
  • ResolvedTheme 扩展为包含 customcustom-light
  • 扩展 VALID_THEME_NAMES
  • 更新 resolveTheme() 使得 custom 镜像现有族行为:
    • custom + dark -> custom
    • custom + light -> custom-light
    • custom + system -> 根据操作系统偏好返回 customcustom-light

不为 custom 添加旧别名。

持久化模型

扩展 UiSettings 持久化(ui/src/ui/storage.ts),增加一个可选的自定义主题 payload:

  • customTheme?: ImportedCustomTheme

推荐的存储形状:

ts
type ImportedCustomTheme = {
  sourceUrl: string;
  themeId: string;
  label: string;
  importedAt: string;
  light: Record<string, string>;
  dark: Record<string, string>;
};

注意:

  • sourceUrl 存储归一化后的原始用户输入。
  • themeId 是从 URL 中提取的 tweakcn 主题 ID。
  • label 为 tweakcn name 字段(若存在),否则为 Custom
  • lightdark 是已经归一化的 OpenClaw token 映射,而非原始 tweakcn payload。
  • 导入的 payload 与其他浏览器本地设置并存,在相同的本地存储文档中序列化。
  • 如果加载时存储的自定义主题数据缺失或无效,忽略该 payload,并在持久化的族为 custom 时回退到 theme: "claw"

运行时应用

在 Control UI 运行时中,新增一个狭义的自定义主题样式表管理器,位于 ui/src/ui/app-settings.tsui/src/ui/theme.ts 附近。

职责:

  • document.head 中创建或更新一个稳定的 <style id="openclaw-custom-theme"> 标签。
  • 仅当存在有效的自定义主题 payload 时发出 CSS。
  • 当 payload 被清除时移除样式标签内容。
  • 保持内置族 CSS 在 ui/src/styles/base.css 中;不要将导入的 token 拼入检入的样式表。

该管理器在加载、保存、导入或清除设置时运行。

亮色模式选择器

实现应优先使用 data-theme-mode="light" 进行跨族亮色样式,而不特殊处理 custom-light。如果现有选择器钉在 data-theme="light" 并需要应用于每个亮色族,请在此工作中扩大范围。

导入 UX

更新 ui/src/ui/views/config.ts 中的 Appearance 部分:

  • ClawKnotDash 旁边添加一个 Custom 主题卡片。
  • 当未导入自定义主题时,将卡片显示为禁用状态。
  • 在主题网格下方添加导入面板,包含:
    • 一个文本输入框,用于 tweakcn 分享链接或 /r/themes/{id} URL
    • 一个 Import 按钮
    • 当自定义 payload 已存在时,显示 Replace 路径
    • 当自定义 payload 已存在时,显示 Clear 操作
  • 当 payload 存在时,显示导入的主题标签和来源主机。
  • 如果当前活动主题是 custom,导入替换后立即生效。
  • 如果当前活动主题不是 custom,导入仅存储新 payload,直到用户选择 Custom 卡片。

快速设置主题选择器(ui/src/ui/views/config-quick.ts)也应在 payload 存在时仅显示 Custom

URL 解析与远程获取

浏览器导入路径接受:

  • https://tweakcn.com/themes/{id}
  • https://tweakcn.com/r/themes/{id}

实现应将两种形式归一化为:

  • https://tweakcn.com/r/themes/{id}

然后浏览器直接获取归一化后的 /r/themes/{id} 端点。

对于外部 payload,使用窄模式验证器。因为这是一个不受信任的外部边界,推荐使用 zod schema。

所需的远程字段:

  • 顶级 name 为可选字符串
  • cssVars.theme 为可选对象
  • cssVars.light 为对象
  • cssVars.dark 为对象

如果 cssVars.lightcssVars.dark 缺失,拒绝导入。这是刻意的:批准的产品行为是完整的模式支持,而不是尽力合成缺失的一侧。

Token 映射

不要盲目镜像 tweakcn 变量。将有限子集归一化为 OpenClaw token,并在一个辅助函数中推导其余部分。

直接导入的 Token

从每个 tweakcn 模式块中:

  • background
  • foreground
  • card
  • card-foreground
  • popover
  • popover-foreground
  • primary
  • primary-foreground
  • secondary
  • secondary-foreground
  • muted
  • muted-foreground
  • accent
  • accent-foreground
  • destructive
  • destructive-foreground
  • border
  • input
  • ring
  • radius

从共享的 cssVars.theme(如果存在):

  • font-sans
  • font-mono

如果某个模式块覆盖了 font-sansfont-monoradius,则以模式本地值为准。

为 OpenClaw 推导的 Token

导入程序从导入的基础颜色推导 OpenClaw 独有变量:

  • --bg-accent
  • --bg-elevated
  • --bg-hover
  • --panel
  • --panel-strong
  • --panel-hover
  • --chrome
  • --chrome-strong
  • --text
  • --text-strong
  • --chat-text
  • --muted
  • --muted-strong
  • --accent-hover
  • --accent-muted
  • --accent-subtle
  • --accent-glow
  • --focus
  • --focus-ring
  • --focus-glow
  • --secondary
  • --secondary-foreground
  • --danger
  • --danger-muted
  • --danger-subtle

推导规则位于一个纯辅助函数中,以便独立测试。精确的颜色混合公式属于实现细节,但辅助函数必须满足两个约束:

  • 保持接近所导入主题意图的可读对比度
  • 对同一导入 payload 产生稳定输出

v1 忽略的 Token

以下 tweakcn token 在第一个版本中故意忽略:

  • chart-*
  • sidebar-*
  • font-serif
  • shadow-*
  • tracking-*
  • letter-spacing
  • spacing

这保持范围在当前 Control UI 实际需要的 token 上。

字体

如果存在,会导入字体栈字符串,但 OpenClaw 在 v1 中不会加载远程字体资源。如果导入的栈引用了浏览器中不可用的字体,则应用正常的回退行为。

失败行为

错误的导入必须关闭失败。

  • 无效的 URL 格式:显示内联验证错误,不发起获取。
  • 不支持的主机或路径形状:显示内联验证错误,不发起获取。
  • 网络失败、非 OK 响应或格式错误的 JSON:显示内联错误,保持当前存储的 payload 不变。
  • 模式验证失败或缺少 light/dark 块:显示内联错误,保持当前存储的 payload 不变。
  • 清除操作:
    • 移除存储的自定义 payload
    • 移除受管理的自定义样式标签内容
    • 如果当前活动主题是 custom,将主题族切换回 claw
  • 首次加载时存储的自定义 payload 无效:
    • 忽略存储的 payload
    • 不发出自定义 CSS
    • 如果持久化的主题族是 custom,回退到 claw

在任何情况下,失败的导入都不能使活动文档应用部分自定义 CSS 变量。

预期变更的文件

主要文件:

  • ui/src/ui/theme.ts
  • ui/src/ui/storage.ts
  • ui/src/ui/app-settings.ts
  • ui/src/ui/views/config.ts
  • ui/src/ui/views/config-quick.ts
  • ui/src/styles/base.css

可能的新辅助文件:

  • ui/src/ui/custom-theme.ts

测试文件:

  • ui/src/ui/app-settings.test.ts
  • ui/src/ui/storage.node.test.ts
  • ui/src/ui/views/config.browser.test.ts
  • 新增针对 URL 解析和 payload 归一化的测试

测试要求

实现最低覆盖:

  • 将分享链接 URL 解析为 tweakcn 主题 ID
  • /themes/{id}/r/themes/{id} 归一化为获取 URL
  • 拒绝不支持的主机和格式错误的 ID
  • 验证 tweakcn payload 形状
  • 将有效的 tweakcn payload 映射为归一化的 OpenClaw light 和 dark token 映射
  • 在浏览器本地设置中加载和保存自定义 payload
  • lightdarksystem 解析 custom
  • 当无 payload 时禁用 Custom 选择
  • custom 已激活时立即应用导入的主题
  • 当清除自定义主题时回退到 claw

手动验证目标:

  • 从 Settings 导入一个已知的 tweakcn 主题
  • lightdarksystem 之间切换
  • custom 和内置族之间切换
  • 重新加载页面并确认导入的自定义主题在本地持久化

发布说明

此功能特意保持较小规模。如果用户后续要求多个导入主题、重命名、导出或跨设备同步,请作为后续设计处理。不要在此实现中预构建主题库抽象。

常见问题

为什么导入 tweakcn 链接后 Custom 主题不可选?

只有当导入的 payload 同时包含 cssVars.lightcssVars.dark 对象时,Custom 卡片才会变为可选。如果缺少其中任一,导入失败且不会存储。请在 tweakcn 端确认主题包含完整的亮色和暗色模式变量。

导入的主题保存在哪里?重启浏览器后会丢失吗?

导入的主题以 JSON 形式存储在浏览器本地存储中,与 Control UI 其他设置(如 UiSettings)共存。只要不手动清除浏览器数据或点击 Custom 卡片上的 Clear 按钮,主题会在重启后保留。它不会跨设备或跨浏览器同步,也不会写入 OpenClaw 网关配置。

如何替换或删除已导入的自定义主题?

在 Settings > Appearance 页面中,如果已存在自定义主题,导入面板会显示 ReplaceClear 操作。点击 Clear 会删除存储的 payload 并将当前主题族切换到 claw。点击 Replace 并输入新链接会覆盖当前自定义主题。如果当前活动主题是 custom,替换后会立即生效。