主题系统总览
lh-ui 的主题系统是双源的,这一点必须先讲清楚:
| 源 | 内容 | 消费方式 |
|---|---|---|
styles/theme/common/var.scss + var-dark.scss |
CSS 自定义属性(--lh-*) |
组件 SCSS 里 var(--lh-*),运行时求值 |
styles/theme/common/variables.scss |
SCSS 变量($lh-*) |
编译期烤死成具体值,用于计算(颜色函数、阴影合成等) |
src/theme/themes/index.ts |
5 个预设的 vars 映射 |
JS 运行时注入 <style>(ConfigProvider / ThemeStudio) |
组件只认 CSS 变量。JS 侧的主题系统存在的意义是:把一套设计令牌在运行时注入到 DOM 的某个子树上,让组件无需重新编译就换肤。
1. 预设主题(开箱即用)
Section titled “1. 预设主题(开箱即用)”五个内置预设:青花瓷 / 松花绿 / 朱砂 / 水墨 / 藤黄。UI 层用 buildPresetStyleText(vars) 生成 <style> 文本写入 DOM。
import { LH_THEME_PRESETS, buildPresetStyleText, LH_PRESET_THEME_STYLE_ID } from '@lh-ui/share/theme'
const preset = LH_THEME_PRESETS[0] // 青花瓷const styleText = buildPresetStyleText(preset.vars) // 拼成 CSS 文本// → 注入到 <style id="lh-preset-theme">详见 主题预设。
2. 运行时定制(themeVars)
Section titled “2. 运行时定制(themeVars)”ConfigProvider 接收 themeVars,键为简写(自动补 --lh- 前缀):
<lh-config-provider :theme-vars="{ 'primary-color': '#1677ff' }"> <App /></lh-config-provider>详见 运行时定制。
五个运行时原语
Section titled “五个运行时原语”src/theme/index.ts 导出(实现在 src/theme/themes/index.ts):
| 原语 | 签名 | 用途 |
|---|---|---|
LH_THEME_PRESETS |
LhThemePreset[] |
全部预设数组,docs 与 ConfigProvider 遍历它 |
registerTheme(preset) |
(preset: LhThemePreset) => void |
注册或覆盖预设(同名覆盖,内置预设也可覆盖) |
getThemePreset(name) |
(name: LhThemePresetName) => LhThemePreset | undefined |
按名取预设 |
getThemeVars(name) |
(name: LhThemePresetName) => LhThemeVars | undefined |
按名取其 vars(未注册返回 undefined) |
buildPresetStyleText(vars) |
(vars: Record<string, string>) => string |
把 vars 拼成可注入的 <style> 文本 |
另有对比度计算原语(verify:theme 与 ThemeStudio 用):
| 原语 | 签名 | 用途 |
|---|---|---|
getRelativeLuminance(color) |
(color: string | undefined) => number | undefined |
WCAG 相对亮度(非 hex 返回 undefined) |
getContrastRatio(a, b) |
(a, b: string | undefined) => number | undefined |
WCAG 对比度(1~21) |
buildPresetDarkVars(vars) |
(vars: Record<string, string>) => Record<string, string> |
由预设 vars 推导暗色块变量 |
LH_PRESET_THEME_STYLE_ID |
'lh-preset-theme' |
注入元素的固定 id |
export type LhThemePresetName = | 'cerblue' | 'songhua' | 'zhusha' | 'shuimo' | 'golden' | (string & {}) // ← 开放自定义名称,同时保留字面量自动补全
export interface LhThemePreset { name: LhThemePresetName label: string // 中文名称 labelEn: string // 英文名称 description: string // 主题描述 vars: Record<string, string> // CSS 变量映射}
export type LhThemeVars = Record<string, string>LhThemePresetName 末尾的 (string & {}) 是 TS 技巧:保持前五个字面量的自动补全,同时允许 registerTheme({ name: 'my-brand', ... }) 传任意字符串。未注册的名字在 getThemePreset / getThemeVars 中返回 undefined。
注入约束:只能写 <style>,不能写内联 style
Section titled “注入约束:只能写 <style>,不能写内联 style”这是主题系统最重要的约束,踩过一次坑:
// ✅ 正确:写 <style> 元素,特异性 0,1,0const el = document.createElement('style')el.id = LH_PRESET_THEME_STYLE_IDel.textContent = buildPresetStyleText(vars)document.head.appendChild(el)
// ❌ 错误:写 documentElement 的内联 style,特异性 1,0,0,0Object.assign(document.documentElement.style, vars)为什么:内联 style 的特异性高于任何样式表,会压过 var-dark.scss 里整段的暗色变量声明([data-theme='dark'], .dark { --lh-*: ... }),导致暗色模式下配色失真——暗色变量被内联的明色值盖死。
buildPresetStyleText 生成的文本结构:
:root { --lh-primary-color: #2B4C7C; /* ...预设的全部 vars */}
[data-theme='dark'],.dark { --lh-primary-color-bg: color-mix(in srgb, var(--lh-primary-color) 25%, transparent); --lh-selected-color: color-mix(in srgb, var(--lh-primary-color) 40%, #fff); /* 深主色预设额外提亮主色 */}暗色块在 :root 块之后、同特异性下后者胜——所以浅主色预设在 :root 里声明的 --lh-selected-color 明色值不会泄漏进暗色。暗色块的内容由 buildPresetDarkVars 推导。
buildPresetDarkVars 的暗色推导
Section titled “buildPresetDarkVars 的暗色推导”三个规则:
-
无条件重定义两个变量(用
var()引用,在应用元素上求值,跟随当前主色):--lh-primary-color-bg: color-mix(in srgb, var(--lh-primary-color) 25%, transparent)--lh-selected-color: color-mix(in srgb, var(--lh-primary-color) 40%, #fff)
-
深主色提亮:若
--lh-primary-color相对暗色页面底#1A1A2E(DARK_PAGE_BG)对比度小于 3(DARK_PRIMARY_CONTRAST_THRESHOLD),判定为深主色,额外提亮:--lh-primary-color: color-mix(in srgb, <字面值> 55%, #fff)—— 直接代入字面值,因为color-mix(var(--lh-primary-color) …)会形成 CSS 自引用循环--lh-primary-color-hover: color-mix(in srgb, var(--lh-primary-color) 70%, #fff)--lh-primary-color-active: color-mix(in srgb, var(--lh-primary-color) 85%, #000)--lh-color-on-primary: #101410(INK_ON_LIGHTENED_PRIMARY)—— 提亮后白字对比度反而不足 3:1,改墨字
-
对比度计算用
getContrastRatio(getRelativeLuminance(primary), getRelativeLuminance(DARK_PAGE_BG))。非 hex 值(含var()的动态值)返回undefined,按「无法判定」处理——所以预设 vars 里的主色必须是字面 hex。
改内置主题的主色时,两边要同步改:
src/theme/themes/index.ts里预设的vars['--lh-primary-color'](JS 运行时注入用)styles/theme/common/variables.scss里的$lh-primary-color(组件 SCSS 计算用)
tests/unit/theme.spec.ts 有一致性回归,跑 pnpm test 会拦不同步。
主题不能做什么
Section titled “主题不能做什么”- 不能改控件高度:高度令牌
--lh-control-height-*尚未建立(各控件高度不统一,记录在「高度令牌专项」待后续 changeset)。 - 不能改组件结构:主题只换令牌值,布局与 DOM 结构不变。
- 花纹是独立系统:见 国风花纹。
cerblue · Ceramic Blue青花瓷釉下彩,清雅素净
--lh-primary-color#2B4C7C--lh-primary-color-hover#3D649E--lh-primary-color-active#1F3A5F--lh-primary-color-bg#E8EDF3--lh-primary-color-border#D4DCE8
songhua · Pine Green竹林修竹,生机勃勃
--lh-primary-color#5B8C5A--lh-primary-color-hover#7BB079--lh-primary-color-active#4F8F4C--lh-primary-color-bg#E8F5E8--lh-primary-color-border#D0E8CF--lh-color-on-primary#101410--lh-selected-color#456F44
zhusha · Vermilion传统印章朱砂红,喜庆庄重
--lh-primary-color#B23A3A--lh-primary-color-hover#C45050--lh-primary-color-active#9A3030--lh-primary-color-bg#FDF0F0--lh-primary-color-border#F0D8D8
shuimo · Ink Wash水墨丹青,素雅极简
--lh-primary-color#2C2C2C--lh-primary-color-hover#5A5A5A--lh-primary-color-active#1A1A1A--lh-primary-color-bg#F5F5F5--lh-primary-color-border#E0E0E0
golden · Golden Autumn深秋枫黄,温暖金黄
--lh-primary-color#B8860B--lh-primary-color-hover#D4A843--lh-primary-color-active#A87F0A--lh-primary-color-bg#FBF6EA--lh-primary-color-border#F0E8D0--lh-color-on-primary#1E1A16--lh-selected-color#82600A
Copyright © 2024-2026 风默文学 | 宁ICP备2024006574号 |
苏公网安备32040002010537