Cherry Studio 小程序主题定制:/__cherry/theme.css 设计令牌与深色模式实战指南
2026/9/13 2:37:34 网站建设 项目流程

Cherry Studio 小程序主题定制:/__cherry/theme.css 设计令牌与深色模式实战指南

【免费下载链接】cherry-studioAI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs项目地址: https://gitcode.com/GitHub_Trending/ch/cherry-studio

Cherry Studio 的小程序(mini app)通过宿主分发的一组 CSS 设计令牌来"长得像 Cherry",而不是靠共享组件。本文以 theming.md 为核心,完整讲解样式表的引入方式、稳定的 CSS 变量契约、深色模式的零代码适配、字体策略与 Tailwind v4 的映射方案,并结合 协议处理源码 和 主题构建脚本 说明这些契约在宿主侧是如何生成与分发的。读完本文,你可以在小程序包中正确引用宿主主题变量、跟随用户的深浅色偏好实时换肤,并把契约变量接入 Tailwind 工具类。

引入样式表:/__cherry/theme.css 是什么

小程序只需在页面中引入一行样式表:

<link rel="stylesheet" href="/__cherry/theme.css" />

这个路径的几个关键性质(来自 theming.md):

属性
路径/__cherry/theme.css虚拟路径——不是你包里的一个文件;__cherry是保留前缀
内容所有--cs-*基础令牌加上下方列出的语义变量,覆盖浅色与深色
缓存no-cache;文件跟随宿主版本,因此 Cherry 升级可能改变具体取值
版本管理无版本号。语义变量名靠约定保持稳定;文件里没有别名(alias)块,所以改名即宿主级破坏性变更,并会作为破坏性变更公告
边界只定义自定义属性(custom properties)——:root上是浅色值,深色值放在@media (prefers-color-scheme: dark).dark类块中。没有元素样式、没有 reset,你的 CSS 完全不受影响

从源码看,这个虚拟路径的分发机制在 createMiniAppProtocolHandler 中实现:保留前缀__cherry(常量 MINI_APP_RESERVED_DIR 定义为'__cherry')的解析先于任何磁盘访问,即使你的包偷偷带了一个__cherry/目录,宿主也绝不会把它作为静态资源服务出去——reservedAsset()只认theme.css这一个名称,从内置的miniAppTheme.css读取并返回,其余保留路径一律 404。响应头中显式带上cache-control: no-cache,与文档中"Caching: no-cache"的描述一致。

这意味着两点实操结论:

  1. 不要在小程序包里放__cherry/目录或同名文件——它既不会生效,也属于违规的保留命名空间;
  2. 不要对/__cherry/theme.css做版本化假设——取值会随宿主版本漂移,这也是为什么契约只承诺"变量名稳定"而不承诺"取值不变"。

稳定契约:哪些变量可以长期使用

文档将两组无前缀变量定义为由宿主承诺跨版本保持名称稳定的公共契约(目前无自动化强制机制,应把任何改名视为破坏性宿主的变更)。你的小程序应只依赖这些变量。

Shadcn 语义变量(颜色 +--radius

完整清单与 theme-contract.ts 中SHADCN_VARIABLE_TOKENS一一对应:

--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 --chart-1 … --chart-5 --sidebar --sidebar-foreground --sidebar-primary --sidebar-primary-foreground --sidebar-accent --sidebar-accent-foreground --sidebar-border --sidebar-ring --radius

这组变量遵循 shadcn 的"表面色 / 前景色成对"命名模式(源码中 SHADCN_SURFACE_PAIRS 精确枚举了 11 对表面/前景组合),例如--background/--foreground--card/--card-foreground--primary/--primary-foreground。使用成对变量是正确做法:只写background: var(--card)而不配--card-foreground的文字颜色,在宿主换肤时可能出现对比度失衡。

产品语义变量(Product semantics)

分组变量
表面与边框--background-subtle--foreground-tertiary--foreground-disabled--border-subtle--border-strong--border-selected--link
反馈--success--warning--info--error,各带-subtle-subtle-foreground-border
内容--code-block--inline-code--inline-code-foreground--reference--reference-foreground--reference-subtle--highlight--highlight-foreground--highlight-accent--chat-user
列表--resource-list-row-hover--resource-list-row-active--resource-list-row-active-foreground--resource-list-row-selected--resource-list-row-selected-foreground

这些变量的权威清单在 CHERRY_PRODUCT_VARIABLE_TOKENS 中维护,源码中该列表比文档表格还多一个--markdown-important;如需最完整的契约清单,建议直接以源码数组为准。

取值格式不是契约的一部分:直接把变量用在colorbackgroundborder-color里即可,不要解析取值。多数值是oklch(...),但 Content 分组里混有 hex 和rgba()——不要写parseInt之类的值解析逻辑,也不要在 JS 里做颜色运算。

基础令牌(--cs-*):可用但不保证稳定

--cs-*系列(如色板--cs-brand-500、字体--cs-font-family-body、字号--cs-font-size-body-md、字重--cs-font-weight-medium,以及圆角、间距刻度)也在theme.css里,但不属于契约,宿主版本之间可能变化。使用原则:优先用语义变量;只在一次性点缀(one-off accents)时伸手拿--cs-*

最小可用示例

文档给出的示例覆盖了页面底色、主按钮、提示与错误状态四类最常见用法:

body { margin: 0; background: var(--background); color: var(--foreground); font-family: var(--cs-font-family-body, system-ui, sans-serif); } button { background: var(--primary); color: var(--primary-foreground); border: 1px solid var(--border); border-radius: var(--radius); } .hint { color: var(--muted-foreground); } .error { color: var(--error); background: var(--error-subtle); }

注意示例中对--cs-font-family-body使用了var(..., system-ui, sans-serif)带兜底的形式——这正是引用非契约令牌时的推荐写法:万一宿主改名,页面仍然可用。

深色模式:零代码自动跟随

小程序不需要写任何主题切换代码。宿主把用户在 Cherry 中选择的主题应用到 webview 上,因此prefers-color-scheme天然正确且实时更新;样式表本身就把深色值放在@media (prefers-color-scheme: dark)块里。上面列出的每一个变量都会随用户主题切换,你只需写一次var(--background)

这一机制的生成侧证据在 buildFlatContractCss 中:theme.css是把仓库内部的contract.css及其@import依赖扁平化内联后的产物(避免把包内部目录结构变成对外契约),随后 mirrorDarkBlocks 把原有的.dark { ... }块额外镜像成@media (prefers-color-scheme: dark) { :root { ... } }。注释里写得很直白:小程序文档里没有任何东西会添加.dark类,能切换 guest 主题的只有prefers-color-scheme

如果脚本逻辑需要知道当前是否深色或监听变化,走浏览器渠道——cherry.app.getInfo()没有theme字段,也没有主题事件:

const dark = matchMedia('(prefers-color-scheme: dark)') dark.addEventListener('change', ({ matches }) => redraw(matches))

另外,同一份深色值同时保留在.dark类块中(见上文mirrorDarkBlocks的输出结构),所以如果你的应用想覆盖用户的主题选择,可以手动给<html>加上class="dark"强制深色渲染。

字体:只给字体族名称,不打包字体文件

--cs-font-family-heading--cs-font-family-body是字体族名Inter),不是内嵌字体。Cherry 本身也不随包分发字体文件——系统没装 Inter 时 Cherry 回退到系统字体,你的小程序同样回退,因此双方渲染保持一致,而你不需要分发任何字体资产。

另一个边界:Cherry 的图标字体不会被服务,需要图标的功能请自行在小程序包里打包图标资源。这与沙箱的 CSPfont-src 'self' data:策略一致(见 buildMiniAppCsp):小程序只能加载自己包内或 data: 形式的字体,无法引用宿主的字体资源。

Tailwind CSS:用 @theme inline 映射契约

宿主不随包提供 Tailwind preset。使用 Tailwind v4 的小程序,在自己的样式表里用@theme inline把契约变量映射成工具类;Tailwind 在构建期读取变量名,工具类在运行时经由theme.css解析出实际值:

@import 'tailwindcss'; @theme inline { --color-background: var(--background); --color-foreground: var(--foreground); --color-primary: var(--primary); --color-primary-foreground: var(--primary-foreground); --color-muted: var(--muted); --color-muted-foreground: var(--muted-foreground); --color-border: var(--border); --color-error: var(--error); --radius-md: var(--radius); }

映射之后,class="bg-background text-foreground border-border"会在深浅两种模式下都跟随宿主主题;Tailwind 默认的dark:变体也直接可用,因为它同样由prefers-color-scheme驱动。

两个硬性前提:

  1. CSS 必须构建进小程序包——页面无法从 CDN 加载 Tailwind(沙箱禁止一切远程网络,connect-src 'none',远程资源只能经cherry.network.fetch中转);
  2. 只映射契约变量。@theme inline里引用--cs-*也能工作,但那部分不属于稳定契约,宿主升级可能改变工具类的实际颜色。

契约如何被维护:构建脚本与测试

了解契约的维护方式有助于判断某个变量的"地位"。仓库中 packages/ui/scripts/ 下有一条完整的主题契约工具链:

  • theme-contract.ts:机器可读的契约定义。SHADCN_COLOR_TOKENSCHERRY_PRODUCT_VARIABLE_TOKENSCHERRY_PRODUCT_COLOR_TOKENS等常量数组就是各分组的权威清单;文件头注释还区分了"稳定的产品变量可作为新代码的默认取值"与"仅用于维持历史渲染的迁移变量"两类地位;
  • build-theme-css.ts:pnpm theme:buildsrc/styles/tokens/*shadcn.css/product.css生成宿主侧theme.css,其中的buildFlatContractCss专门产出给小程序使用的、无 Tailwind 依赖的扁平版本(RUNTIME_THEME_INPUT_TOKENS只暴露primaryprimary-foreground两个宿主可运行时写入的内部输入值);
  • check-theme-contract.ts 与 validate-theme-contract.ts:对契约做一致性校验;
  • build-theme-css.test.ts:对扁平化、深色块镜像等生成行为做回归测试。

需要注意区分两个theme.css:仓库源码中的 packages/ui/src/styles/ 产物供宿主 UI 使用(含@theme inline@layer base基线);而小程序收到的是buildFlatContractCss生成的纯自定义属性版本。文档中"It defines only custom properties"描述的正是后者。

小结:把变量当 API 来用

  • 引入/__cherry/theme.css,它是虚拟资源、no-cache、只含自定义属性;
  • 只依赖无 Shadcn 语义 + 产品语义这两组契约变量,--cs-*仅作一次性点缀并写var()兜底;
  • 不解析颜色值格式,深浅色交给宿主与prefers-color-scheme,脚本侧用matchMedia监听;
  • Tailwind v4 用@theme inline映射契约变量,CSS 构建进包、不依赖 CDN;
  • 变量改名视为宿主级破坏性变更,以仓库中theme-contract.ts的常量数组为契约最终依据。

【免费下载链接】cherry-studioAI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs项目地址: https://gitcode.com/GitHub_Trending/ch/cherry-studio

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询