lh-ui 的所有组件默认支持明色与暗色。这不是事后追加的主题,而是令牌系统的设计前提。
暗色的开关是 CSS 选择器,不是 JS:
--lh-text-primary: #E8E0D8;
两种触发方式:
<!-- 方式一:data 属性(推荐,语义清晰) -->
<!-- 方式二:class(兼容老代码 / 第三方切换库) -->
UI 层(各库的 ConfigProvider 或业务代码)只要切这个属性 / 类名,所有组件立即变色——CSS 变量是运行时求值的,没有重渲染开销。
function toggleDark(isDark: boolean) {
document.documentElement.dataset.theme = isDark ? 'dark' : 'light'
暗色文件只写 118 条,不是完整一套 185 条。只覆盖需要翻转的令牌,其余沿用明色值。
设计原则是语义令牌重指向,而非逐组件重写:
--lh-menu-item-color: var(--lh-text-primary);
--lh-table-cell-color: var(--lh-text-primary);
--lh-menu-item-color: rgba(255, 255, 255, 0.85); // 与 text-primary 脱钩,后续调色要改两处
这是暗色模式最容易踩的坑,也是 verify:styles 第 7 项存在的理由。
var.scss 里值形如 #{$lh-foo} 的令牌:
--lh-menu-item-color: #{$lh-menu-item-color}; // SCSS 变量在编译期被替换成具体明色值
SCSS 变量在编译期被烤死成字面值,等价于:
--lh-menu-item-color: rgba(0, 0, 0, 0.88); // 暗色下不会自动翻转!
结果:暗色模式下菜单 / 表格 / 时间线的文字沿用明色值,黑底黑字,不可见。这类缺口曾真实出现过,补缺记录见 0.4.0 的 changeset。
在 var-dark.scss 显式重新指向暗色语义令牌:
--lh-menu-item-color: var(--lh-text-primary);
组件 SCSS 里的链式回落:
color: var(--lh-foo, var(--lh-bar, #333));
var() 的回落是运行时求值的:只要链上任一令牌有暗色覆盖,整个链就跟随翻转。无需重复声明。
对照表:
| 写法 |
求值时机 |
暗色行为 |
var(--lh-foo, fallback) |
运行时 |
✅ 链上令牌有暗色覆盖即翻转 |
#{$lh-foo}(SCSS 插值) |
编译期 |
❌ 烤死成明色,需在 var-dark 显式重指向 |
mix($lh-a, $lh-b) 等 SCSS 函数 |
编译期 |
❌ 同上 |
五个内置预设中,深主色的(青花瓷 / 朱砂 / 水墨)在暗色下主色会「沉进」页面底色。buildPresetDarkVars 自动处理:
判定规则:--lh-primary-color 相对暗色页面底 #1A1A2E(DARK_PAGE_BG)的对比度小于 3(DARK_PRIMARY_CONTRAST_THRESHOLD)即判定深主色,然后:
--lh-primary-color: color-mix(in srgb, <字面值> 55%, #fff); /* 提亮 55% */
--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; /* 提亮后白字对比度反而不足,改墨字 */
--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);
为什么主色提亮用字面值不用 var():color-mix(in srgb, var(--lh-primary-color) 55%, #fff) 会形成 CSS 自引用循环(--lh-primary-color 的值里引用了自己)。所以这一条代入字面值,其余用 var() 跟随当前主色。
浅主色预设(松花绿 / 藤黄)不需要提亮,但仍会拿到 --lh-primary-color-bg 与 --lh-selected-color 的暗色重定义。
buildPresetStyleText 生成的文本是 :root { ... } 块在前、[data-theme='dark'], .dark { ... } 块在后。同特异性下后者胜,所以浅主色预设在 :root 里声明的 --lh-selected-color 明色值不会泄漏进暗色。
这也是「只能写 <style> 不能写内联 style」的根因——内联 style 特异性 1,0,0,0 压过 var-dark.scss 的整段暗色变量,暗色直接失效(详见 主题总览)。
主色与衍生语义
| 令牌 | 明色 | 暗色 | SCSS 源 | 备注 |
|---|
--lh-primary-color | | #3D649E | $lh-primary | |
--lh-primary-color-hover | | #5078AB | $lh-primary-hover | |
--lh-primary-color-active | | #2B4C7C | $lh-primary-active | |
--lh-primary-color-bg | | color-mix(in srgb, var(--lh-primary-color) 25%, transparent) | $lh-primary-light | |
--lh-primary-color-border | | #2A3A4A | $lh-primary-lighter | |
--lh-color-on-primary | | #fff | $lh-on-primary | |
状态
| 令牌 | 明色 | 暗色 | SCSS 源 | 备注 |
|---|
--lh-hover-bg-color | | rgba(255, 255, 255, 0.08) | $lh-hover-bg | |
--lh-selected-color | | color-mix(in srgb, var(--lh-primary-color) 40%, #fff) | — | |
功能色
| 令牌 | 明色 | 暗色 | SCSS 源 | 备注 |
|---|
--lh-success-color | | #6FA36E | $lh-success | |
--lh-success-color-hover | | #82B581 | $lh-success-hover | |
--lh-success-color-active | | #5B8C5A | $lh-success-active | |
--lh-success-color-bg | | #1A2A1A | $lh-success-light | |
--lh-warning-color | | #D4B76F | $lh-warning | |
--lh-warning-color-hover | | #E0C680 | $lh-warning-hover | |
--lh-warning-color-active | | #C4A35A | $lh-warning-active | |
--lh-warning-color-bg | | #2A251A | $lh-warning-light | |
--lh-danger-color | | #C45050 | $lh-danger | |
--lh-danger-color-hover | | #D46060 | $lh-danger-hover | |
--lh-danger-color-active | | #B23A3A | $lh-danger-active | |
--lh-danger-color-bg | | #2A1A1A | $lh-danger-light | |
--lh-info-color | | #8F8F8F | $lh-info | |
--lh-info-color-hover | | #A0A0A0 | $lh-info-hover | |
--lh-info-color-active | | #7A7A7A | $lh-info-active | |
--lh-info-color-bg | | #1A1A1A | $lh-info-light | |
国风装饰色
| 令牌 | 明色 | 暗色 | SCSS 源 | 备注 |
|---|
--lh-gold-color | | #D4A843 | $lh-gold | |
--lh-gold-color-hover | | #E8C35C | $lh-gold-hover | |
--lh-gold-color-active | | #B8860B | $lh-gold-active | |
--lh-gold-color-bg | | #2A241A | $lh-gold-light | |
--lh-gold-color-border | | #3A2E1A | $lh-gold-lighter | |
--lh-vermilion-color | | #E85D4A | $lh-vermilion | |
--lh-vermilion-color-hover | | #FF7B6B | $lh-vermilion-hover | |
--lh-vermilion-color-active | | #C43A31 | $lh-vermilion-active | |
--lh-vermilion-color-bg | | #2A1A18 | $lh-vermilion-light | |
--lh-jade-color | | #7AAE7A | $lh-jade | |
--lh-jade-color-hover | | #9CC89C | $lh-jade-hover | |
--lh-jade-color-active | | #5B8C5A | $lh-jade-active | |
--lh-jade-color-bg | | #1A2A1A | $lh-jade-light | |
--lh-decoration-color | | rgba(212, 168, 67, 0.08) | $lh-decoration-color | |
文本
| 令牌 | 明色 | 暗色 | SCSS 源 | 备注 |
|---|
--lh-text-primary | | #E8E0D8 | $lh-text-primary | |
--lh-text-regular | | #C8C0B8 | $lh-text-regular | |
--lh-text-secondary | | #A09890 | $lh-text-secondary | |
--lh-text-placeholder | | #7A7270 | $lh-text-placeholder | |
--lh-text-disabled | | #5A5250 | $lh-text-disabled | |
边框
| 令牌 | 明色 | 暗色 | SCSS 源 | 备注 |
|---|
--lh-border-color-base | | #3A3530 | $lh-border-color-base | |
--lh-border-color-light | | #302B28 | $lh-border-color-light | |
--lh-border-color-lighter | | #2A2520 | $lh-border-color-lighter | |
--lh-border-color-extra-light | | #242018 | $lh-border-color-extra-light | |
圆角
| 令牌 | 明色 | 暗色 | SCSS 源 | 备注 |
|---|
--lh-border-radius-base | | 沿用明色 | $lh-border-radius-base | |
--lh-border-radius-small | | 沿用明色 | $lh-border-radius-small | |
--lh-border-radius-round | | 沿用明色 | $lh-border-radius-round | |
--lh-border-radius-circle | | 沿用明色 | $lh-border-radius-circle | |
背景
| 令牌 | 明色 | 暗色 | SCSS 源 | 备注 |
|---|
--lh-bg-color | | #2A2520 | $lh-bg-color | |
--lh-bg-color-page | | #1E1A16 | $lh-bg-color-page | |
--lh-bg-color-disabled | | #242018 | $lh-bg-color-disabled | |
--lh-bg-color-overlay | | rgba(10, 8, 6, 0.7) | $lh-bg-color-overlay | |
阴影
| 令牌 | 明色 | 暗色 | SCSS 源 | 备注 |
|---|
--lh-box-shadow-base | | 0 2px 12px 0 rgba(0, 0, 0, 0.5) | $lh-box-shadow-base | |
--lh-box-shadow-light | | 0 2px 4px 0 rgba(0, 0, 0, 0.4) | $lh-box-shadow-light | |
--lh-box-shadow-lighter | | 0 0 4px 0 rgba(0, 0, 0, 0.3) | $lh-box-shadow-lighter | |
字体
| 令牌 | 明色 | 暗色 | SCSS 源 | 备注 |
|---|
--lh-font-family | | 沿用明色 | $lh-font-family | |
--lh-font-size-extra-large | | 沿用明色 | $lh-font-size-extra-large | |
--lh-font-size-large | | 沿用明色 | $lh-font-size-large | |
--lh-font-size-medium | | 沿用明色 | $lh-font-size-medium | |
--lh-font-size-base | | 沿用明色 | $lh-font-size-base | |
--lh-font-size-small | | 沿用明色 | $lh-font-size-small | |
--lh-font-size-extra-small | | 沿用明色 | $lh-font-size-extra-small | |
--lh-font-weight-primary | | 沿用明色 | $lh-font-weight-primary | |
--lh-font-weight-bold | | 沿用明色 | $lh-font-weight-bold | |
--lh-font-line-height-primary | | 沿用明色 | $lh-font-line-height-primary | |
组件级 · control
| 令牌 | 明色 | 暗色 | SCSS 源 | 备注 |
|---|
--lh-control-height-mini | | 沿用明色 | $lh-control-height-mini | |
--lh-control-height-small | | 沿用明色 | $lh-control-height-small | |
--lh-control-height-medium | | 沿用明色 | $lh-control-height-medium | |
--lh-control-height-large | | 沿用明色 | $lh-control-height-large | |
--lh-control-height-x-large | | 沿用明色 | $lh-control-height-x-large | |
间距
| 令牌 | 明色 | 暗色 | SCSS 源 | 备注 |
|---|
--lh-spacing-xs | | 沿用明色 | $lh-spacing-xs | |
--lh-spacing-sm | | 沿用明色 | $lh-spacing-sm | |
--lh-spacing-md | | 沿用明色 | $lh-spacing-md | |
--lh-spacing-base | | 沿用明色 | $lh-spacing-base | |
--lh-spacing-lg | | 沿用明色 | $lh-spacing-lg | |
--lh-spacing-xl | | 沿用明色 | $lh-spacing-xl | |
--lh-spacing-xxl | | 沿用明色 | $lh-spacing-xxl | |
层级
| 令牌 | 明色 | 暗色 | SCSS 源 | 备注 |
|---|
--lh-z-index-normal | | 沿用明色 | $lh-z-index-normal | |
--lh-z-index-dropdown | | 沿用明色 | $lh-z-index-dropdown | |
--lh-z-index-sticky | | 沿用明色 | $lh-z-index-sticky | |
--lh-z-index-fixed | | 沿用明色 | $lh-z-index-fixed | |
--lh-z-index-modal-backdrop | | 沿用明色 | $lh-z-index-modal-backdrop | |
--lh-z-index-modal | | 沿用明色 | $lh-z-index-modal | |
--lh-z-index-popover | | 沿用明色 | $lh-z-index-popover | |
--lh-z-index-tooltip | | 沿用明色 | $lh-z-index-tooltip | |
过渡
| 令牌 | 明色 | 暗色 | SCSS 源 | 备注 |
|---|
--lh-transition-duration-fast | | 沿用明色 | $lh-transition-duration-fast | |
--lh-transition-duration-base | | 沿用明色 | $lh-transition-duration-base | |
--lh-transition-duration-slow | | 沿用明色 | $lh-transition-duration-slow | |
--lh-transition-function-ease-in-out | | 沿用明色 | $lh-transition-function-ease-in-out | |
禁用态
| 令牌 | 明色 | 暗色 | SCSS 源 | 备注 |
|---|
--lh-disabled-bg-color | | #242018 | $lh-disabled-bg-color | |
--lh-disabled-text-color | | #5A5250 | $lh-disabled-text-color | |
--lh-disabled-border-color | | #302B28 | $lh-disabled-border-color | |
--lh-disabled-opacity | | 0.5 | $lh-disabled-opacity | |
组件级 · popover
| 令牌 | 明色 | 暗色 | SCSS 源 | 备注 |
|---|
--lh-popover-bg-color | | #2A2520 | $lh-popover-bg-color | |
--lh-popover-border-color | | #3A3530 | $lh-popover-border-color | |
--lh-popover-border-radius | | 沿用明色 | $lh-popover-border-radius | |
--lh-popover-padding | | 沿用明色 | $lh-popover-padding | |
--lh-popover-title-color | | #E8E0D8 | $lh-popover-title-color | |
--lh-popover-content-color | | #C8C0B8 | $lh-popover-content-color | |
--lh-popover-arrow-size | | 沿用明色 | $lh-popover-arrow-size | |
组件级 · popconfirm
| 令牌 | 明色 | 暗色 | SCSS 源 | 备注 |
|---|
--lh-popconfirm-padding | | 沿用明色 | $lh-popconfirm-padding | |
--lh-popconfirm-icon-size | | 沿用明色 | $lh-popconfirm-icon-size | |
组件级 · drawer
| 令牌 | 明色 | 暗色 | SCSS 源 | 备注 |
|---|
--lh-drawer-bg-color | | #2A2520 | $lh-drawer-bg-color | |
--lh-drawer-border-color | | #3A3530 | $lh-drawer-border-color | |
--lh-drawer-header-padding | | 沿用明色 | $lh-drawer-header-padding | |
--lh-drawer-body-padding | | 沿用明色 | $lh-drawer-body-padding | |
--lh-drawer-footer-padding | | 沿用明色 | $lh-drawer-footer-padding | |
--lh-drawer-title-color | | #E8E0D8 | $lh-drawer-title-color | |
--lh-drawer-close-size | | 沿用明色 | $lh-drawer-close-size | |
组件级 · dropdown
| 令牌 | 明色 | 暗色 | SCSS 源 | 备注 |
|---|
--lh-dropdown-bg-color | | #2A2520 | $lh-dropdown-bg-color | |
--lh-dropdown-border-color | | #3A3530 | $lh-dropdown-border-color | |
--lh-dropdown-border-radius | | 沿用明色 | $lh-dropdown-border-radius | |
--lh-dropdown-padding | | 沿用明色 | $lh-dropdown-padding | |
--lh-dropdown-item-height | | 沿用明色 | $lh-dropdown-item-height | |
--lh-dropdown-item-padding | | 沿用明色 | $lh-dropdown-item-padding | |
--lh-dropdown-item-color | | #C8C0B8 | $lh-dropdown-item-color | |
--lh-dropdown-item-hover-bg-color | | var(--lh-hover-bg-color) | $lh-dropdown-item-hover-bg-color | |
--lh-dropdown-divider-color | | #302B28 | $lh-dropdown-divider-color | |
组件级 · message
| 令牌 | 明色 | 暗色 | SCSS 源 | 备注 |
|---|
--lh-message-bg | | #2A2520 | $lh-message-bg-color | |
--lh-message-border | | #3A3530 | $lh-message-border-color | |
--lh-message-color | | #C8C0B8 | $lh-message-content-color | |
--lh-message-shadow | | 沿用明色 | $lh-message-shadow | |
组件级 · notification
| 令牌 | 明色 | 暗色 | SCSS 源 | 备注 |
|---|
--lh-notification-bg | | #2A2520 | $lh-notification-bg-color | |
--lh-notification-border | | #3A3530 | $lh-notification-border-color | |
--lh-notification-title-color | | #E8E0D8 | $lh-notification-title-color | |
--lh-notification-content-color | | #C8C0B8 | $lh-notification-content-color | |
--lh-notification-width | | 沿用明色 | $lh-notification-width | |
--lh-notification-gap | | 沿用明色 | $lh-notification-gap | |
--lh-notification-shadow | | 沿用明色 | $lh-notification-shadow | |
组件级 · loading
| 令牌 | 明色 | 暗色 | SCSS 源 | 备注 |
|---|
--lh-loading-bg | | rgba(10, 8, 6, 62%) | $lh-loading-bg-color | |
--lh-loading-text-color | | #C8C0B8 | $lh-loading-text-color | |
组件级 · timeline
| 令牌 | 明色 | 暗色 | SCSS 源 | 备注 |
|---|
--lh-timeline-line | | #302B28 | $lh-timeline-line | |
--lh-timeline-dot | | var(--lh-info-color) | $lh-timeline-dot | |
--lh-timeline-timestamp | | var(--lh-text-secondary) | $lh-timeline-timestamp | |
组件级 · collapse
| 令牌 | 明色 | 暗色 | SCSS 源 | 备注 |
|---|
--lh-collapse-border | | #302B28 | $lh-collapse-border | |
组件级 · rate
| 令牌 | 明色 | 暗色 | SCSS 源 | 备注 |
|---|
--lh-rate-active | | var(--lh-warning-color) | $lh-rate-active | |
组件级 · segmented
| 令牌 | 明色 | 暗色 | SCSS 源 | 备注 |
|---|
--lh-segmented-bg | | #242018 | $lh-segmented-bg | |
组件级 · calendar
| 令牌 | 明色 | 暗色 | SCSS 源 | 备注 |
|---|
--lh-calendar-border | | var(--lh-border-color-lighter) | $lh-calendar-border | |
组件级 · affix
| 令牌 | 明色 | 暗色 | SCSS 源 | 备注 |
|---|
--lh-affix-z-index | | 沿用明色 | $lh-affix-z-index | |
组件级 · backtop
| 令牌 | 明色 | 暗色 | SCSS 源 | 备注 |
|---|
--lh-backtop-z-index | | 沿用明色 | $lh-backtop-z-index | |
组件级 · menu
| 令牌 | 明色 | 暗色 | SCSS 源 | 备注 |
|---|
--lh-menu-bg | | #242018 | $lh-menu-bg | |
--lh-menu-padding | | 沿用明色 | $lh-menu-padding | |
--lh-menu-border | | #302B28 | $lh-menu-border | |
--lh-menu-height | | 沿用明色 | $lh-menu-height | |
--lh-menu-collapse-width | | 沿用明色 | $lh-menu-collapse-width | |
--lh-menu-item-height | | 沿用明色 | $lh-menu-item-height | |
--lh-menu-item-padding | | 沿用明色 | $lh-menu-item-padding | |
--lh-menu-item-color | | var(--lh-text-primary) | $lh-menu-item-color | |
--lh-menu-item-hover-bg | | #302B28 | $lh-menu-item-hover-bg | |
--lh-menu-item-active-color | | var(--lh-selected-color) | $lh-menu-item-active-color | |
--lh-menu-item-active-bg | | var(--lh-primary-color-bg) | $lh-menu-item-active-bg | |
--lh-menu-arrow-color | | var(--lh-text-secondary) | $lh-menu-arrow-color | |
--lh-menu-z-index | | 沿用明色 | $lh-menu-z-index | |
组件级 · table
| 令牌 | 明色 | 暗色 | SCSS 源 | 备注 |
|---|
--lh-table-bg | | #242018 | $lh-table-bg | |
--lh-table-color | | var(--lh-text-regular) | $lh-table-color | |
--lh-table-header-bg | | #2A2420 | $lh-table-header-bg | |
--lh-table-header-color | | var(--lh-text-primary) | $lh-table-header-color | |
--lh-table-border | | #302B28 | $lh-table-border | |
--lh-table-row-hover-bg | | #2A2420 | $lh-table-row-hover-bg | |
--lh-table-stripe-bg | | color-mix(in srgb, var(--lh-bg-color-page) 60%, transparent) | $lh-table-stripe-bg | |
--lh-table-cell-padding | | 沿用明色 | $lh-table-cell-padding | |
组件级 · layout
| 令牌 | 明色 | 暗色 | SCSS 源 | 备注 |
|---|
--lh-layout-header-height | | 沿用明色 | $lh-layout-header-height | |
--lh-layout-header-bg | | #242018 | $lh-layout-header-bg | |
--lh-layout-aside-width | | 沿用明色 | $lh-layout-aside-width | |
--lh-layout-aside-bg | | #242018 | $lh-layout-aside-bg | |
--lh-layout-main-bg | | #1C1815 | $lh-layout-main-bg | |
--lh-layout-border | | #302B28 | $lh-layout-border | |
组件级 · row
| 令牌 | 明色 | 暗色 | SCSS 源 | 备注 |
|---|
--lh-row-gutter | | 沿用明色 | $lh-row-gutter | |
组件级 · tree
| 令牌 | 明色 | 暗色 | SCSS 源 | 备注 |
|---|
--lh-tree-color | | #D8D0C8 | $lh-tree-color | |
--lh-tree-padding | | 沿用明色 | $lh-tree-padding | |
--lh-tree-node-height | | 沿用明色 | $lh-tree-node-height | |
--lh-tree-node-hover-bg | | #2A2420 | $lh-tree-node-hover-bg | |
--lh-tree-node-selected-color | | var(--lh-selected-color) | $lh-tree-node-selected-color | |
--lh-tree-node-selected-bg | | var(--lh-primary-color-bg) | $lh-tree-node-selected-bg | |
--lh-tree-arrow-color | | var(--lh-text-secondary) | $lh-tree-arrow-color | |
组件级 · transfer
| 令牌 | 明色 | 暗色 | SCSS 源 | 备注 |
|---|
--lh-transfer-panel-width | | 沿用明色 | $lh-transfer-panel-width | |
--lh-transfer-panel-height | | 沿用明色 | $lh-transfer-panel-height | |
--lh-transfer-bg | | #242018 | $lh-transfer-bg | |
--lh-transfer-border | | #302B28 | $lh-transfer-border | |
组件级 · upload
| 令牌 | 明色 | 暗色 | SCSS 源 | 备注 |
|---|
--lh-upload-bg | | #242018 | $lh-upload-bg | |
--lh-upload-border | | var(--lh-border-color-base) | $lh-upload-border | |
--lh-upload-trigger-padding | | 沿用明色 | $lh-upload-trigger-padding | |
组件级 · anchor
| 令牌 | 明色 | 暗色 | SCSS 源 | 备注 |
|---|
--lh-anchor-color | | var(--lh-text-regular) | $lh-anchor-color | |
组件级 · image
| 令牌 | 明色 | 暗色 | SCSS 源 | 备注 |
|---|
--lh-image-viewer-mask | | rgba(0, 0, 0, 80%) | — | |
无障碍焦点环
| 令牌 | 明色 | 暗色 | SCSS 源 | 备注 |
|---|
--lh-focus-color | | #7FA6D9 | $lh-primary | |
--lh-focus-ring-width | | 沿用明色 | — | |
--lh-focus-ring-offset | | 沿用明色 | — | |
三个手段,从弱到强:
pnpm verify:styles 第 7 项(暗色可达性):静态扫描,找出「明色烤死但 var-dark 无覆盖」的令牌。这是 CI 门禁。
pnpm verify:theme(AA 4.5:1):读 es/ 产物,算预设主题在明暗两色下的对比度。需先 pnpm build。CI 不跑,手动把守。
- 目视检查:docs 站 / 各库 playground 切暗色模式逐组件看。
新增令牌时的自查清单:
暗色完备性在 0.4.0 做过一次系统性补缺(菜单 / 表格 / 时间线等文字不可见问题,见 changeset)。剩余已知项随「高度令牌专项」待后续处理。