三月七主题插件设计
三月七主题插件设计
在只有一个 Vue 项目时,主题通常只是组件库里的一份 CSS。
但当项目逐渐扩展到原生 Web Components、Astro 模板、博客和其它前端框架后,继续让每个项目各自维护颜色、圆角和暗色模式,会出现一个很直接的问题:页面看起来相似,实际使用的规则却越来越不一样。
@mar7th/march7th-theme 就是在这个阶段拆出来的。它不是某个组件库的附属样式,而是一套框架无关的视觉基础设施。
设计目的
让视觉规则只有一个来源
主题色、辅助色、背景层级、文字层级、边框、圆角、阴影和动效都属于跨项目共享的规则。它们不应该分别散落在 Vue 组件、原生组件和站点 CSS 中。
主题包把这些值整理成 Design Tokens。页面和组件只消费 Token,不再自己决定同一种按钮应该使用什么颜色、卡片应该有多大圆角。
不绑定具体框架
主题能力需要同时服务:
- Astro 输出的静态 HTML。
- Vue 组件和 Vue Island。
- 原生 Web Components。
- React、Svelte 或传统 HTML 页面。
因此核心实现不能依赖 Vue 的响应式系统或 Astro 的组件生命周期。样式层使用 CSS Custom Properties,运行时只依赖标准 DOM API,框架只负责在自己的入口中完成初始化。
保留项目级定制空间
共享视觉不等于所有项目只能使用同一种颜色。主题包提供稳定的默认值,同时允许项目调整主色相、辅助色相、状态色和局部 Token。
这样不同网站可以拥有自己的配色,但仍然共享相同的明暗关系、状态语义和组件结构。
方案
Token 分层
主题包没有只输出一组 --primary,而是把变量分成几个层级:
主题轴├── 主色相与辅助色相├── success / info / warning / danger 状态轴└── light / dark / system 模式
基础色阶├── primary 50–950├── secondary 50–950├── neutral 50–950└── 四类状态色 50–950
语义 Token├── 背景与前景├── 操作与边框├── 状态背景、边框和文字├── 图表色└── 圆角、阴影、时长和缓动组件优先读取语义 Token,而不是直接绑定某一档颜色。以后调整底层色阶时,组件不需要跟着修改。
OKLCH 动态配色
颜色使用 OKLCH 描述。与直接修改 RGB 或 HSL 相比,OKLCH 更适合在保持感知亮度的同时调整色相和色度。
主色只需要改变 --m7-hue,整套界面的基础色阶和语义色就会跟着变化。辅助色相可以独立调整,不需要和主色完全绑定。
import { initializeTheme, setThemeHue, setThemeMode,} from "@mar7th/march7th-theme"
initializeTheme({ defaultMode: "system", defaultHue: 215 })setThemeMode("dark")setThemeHue(285)四类状态色与自动色阶
成功、信息、警告和错误不能简单地由主色换一个 Hue 得到。它们承担明确的交互语义,也需要在浅色和深色界面中保持足够对比度。
主题包将 success、info、warning、danger 设计成四个独立角色,error 作为 danger 的兼容别名。每个角色只暴露色相、色度和明度三个输入轴,由生成器统一产生 50 到 950 的 11 档色阶。
import { generateStatusPalette, initializeStatusPalettes, setStatusColor,} from "@mar7th/march7th-theme"
initializeStatusPalettes({ gamut: "rgb" })setStatusColor("success", { hue: 145, chroma: 0.17, lightness: 0.62,})
const preview = generateStatusPalette("warning", { hue: 82, chroma: 0.18, lightness: 0.72, gamut: "p3",})
console.log(preview.scale[500], preview.contrast)生成器通过 Culori 进行 RGB 或 Display P3 色域映射,并检查实色按钮文字和状态面板文字是否达到默认 4.5:1 的 WCAG AA 对比度。
这也是状态色编辑器中“通过 AA”的含义:当前配色在对应使用场景下达到了最低可读性要求,而不是单纯表示颜色生成成功。
主题控制器
主题控制器统一处理:
- 浅色、深色和跟随系统模式。
- 主色相修改。
localStorage持久化。- 系统主题变化监听。
- 跨标签页同步。
- 全局主题和局部主题作用域。
- 强制色模式与减少动态效果设置。
控制器会维护根节点上的 class、data 属性和 CSS 变量,并发送 march7th-theme-ready、march7th-theme-change 等 DOM 事件。Vue、Astro 和原生脚本都可以订阅同一份状态。
首屏防闪烁
SSG 和 SSR 页面如果等客户端脚本加载完成后再应用暗色主题,会先显示一次亮色页面。
主题包提供 createThemeInitScript(),生成一段只读取必要本地配置的首屏脚本。它在页面内容渲染前同步主题,不需要加载完整主题运行时。
import { createThemeInitScript } from "@mar7th/march7th-theme"
const themeInitScript = createThemeInitScript({ defaultMode: "system", defaultHue: 215,})Web Components 控制面板
主题包提供两个不依赖框架的控制组件:
<march7th-theme-control>:切换明暗模式和主色相。<march7th-status-colors>:分别编辑四类状态色,查看完整色阶、AA 状态并导出 CSS 或 JSON。
它们使用 Shadow DOM,可以直接放进 Astro、Vue、React、Svelte 或普通 HTML 页面。组件发送可冒泡、可穿过 Shadow DOM 的 change 事件,因此框架外部也能获得变更结果。
兼容层
新主题使用 --m7-* Token,但现有组件库和 shadcn 项目已经拥有自己的变量命名。一次性全部改写风险很高,因此主题包将兼容层作为正式出口:
compat/march7th.css映射现有 March7th UI Vue 变量。compat/shadcn.css映射--background、--primary、--border等变量。compat/generic.css提供更通用的无前缀变量。
迁移可以逐步进行,而不是要求所有项目同时重写。
当前成果
目前 @mar7th/march7th-theme 已发布 0.1.1:
- 完成框架无关的 CSS Token 和 TypeScript API。
- 支持浅色、深色、系统模式和主题持久化。
- 支持主色、辅助色以及四类状态色独立配置。
- 支持状态色 50–950 色阶、RGB/P3 色域映射和 WCAG 对比度检查。
- 提供首屏防闪烁脚本、主题控制组件和状态色编辑组件。
- 提供 March7th、shadcn 和通用变量兼容层。
- 已接入原生组件库、Vue 组件库和 Astro 项目模板。
边界
主题包只负责视觉规则和主题状态,不负责按钮、弹窗等具体组件,也不决定页面布局。
这个边界很重要。博客、作品站、图集站和论坛可以拥有完全不同的页面结构,但只要读取同一套 Token,就能够保持一致的视觉语言。主题包统一的是规则,不是把所有网站做成同一个页面。
March7th