コンテンツにスキップ

ダークモード

lh-ui のすべてのコンポーネントはデフォルトで明色と暗色をサポートします。これは後から追加されたテーマではなく、トークンシステムの設計の前提です。

暗色のスイッチは CSS セレクタで、JS ではありません:

styles/theme/common/var-dark.scss
[data-theme='dark'],
.dark {
--lh-bg-color: #2A2520;
--lh-text-primary: #E8E0D8;
// ... 118 個の上書き
}

2 つのトリガ方法:

<!-- 方法 1:data 属性(推奨、セマンティクスが明確) -->
<html data-theme="dark">
<!-- 方法 2:class(古いコード / サードパーティの切り替えライブラリとの互換) -->
<body class="dark">

UI 層(各ライブラリの ConfigProvider か業務コード)はこの属性 / クラス名を切り替えるだけで、すべてのコンポーネントが即座に変色します——CSS 変数はランタイム評価されるため、再レンダリングのコストはありません。

function toggleDark(isDark: boolean) {
document.documentElement.dataset.theme = isDark ? 'dark' : 'light'
}

暗色のファイルが書くのは 118 個だけで、185 個の完全なセットではありません。反転が必要なトークンだけを上書きし、残りは明色の値を流用します。

設計原則はセマンティックトークンの再指向であって、コンポーネントごとの書き直しではないことです:

// ✅ よいやり方:暗色で暗色のセマンティックトークンを指す
[data-theme='dark'],
.dark {
--lh-menu-item-color: var(--lh-text-primary);
--lh-table-cell-color: var(--lh-text-primary);
}
// ❌ 悪いやり方:暗色で具体的な色を固定する
[data-theme='dark'],
.dark {
--lh-menu-item-color: rgba(255, 255, 255, 0.85); // text-primary とリンク切れ、後日の調色で 2 か所直す必要がある
}

コンパイル期にベイクされる罠

Section titled “コンパイル期にベイクされる罠”

これがダークモードで最も踏みやすい罠であり、verify:styles の第 7 項が存在する理由です。

var.scss で値が #{$lh-foo} の形をしているトークン:

var.scss
:root {
--lh-menu-item-color: #{$lh-menu-item-color}; // SCSS 変数はコンパイル期に具体的な明色の値に置き換えられる
}

SCSS 変数はコンパイル期に字面どおりの値にベイクされ固定され、次と等価になります:

:root {
--lh-menu-item-color: rgba(0, 0, 0, 0.88); // 暗色でも自動的には反転しない!
}

結果:ダークモードでメニュー / テーブル / タイムラインの文字が明色の値のままになり、黒地に黒字で見えなくなります。この種の抜けは実際に発生したことがあり、0.4.0 の changeset に修正記録があります。

var-dark.scss で明示的に暗色のセマンティックトークンに再指向します:

[data-theme='dark'],
.dark {
--lh-menu-item-color: var(--lh-text-primary);
}

チェーンフォールバックはランタイム評価で安全

Section titled “チェーンフォールバックはランタイム評価で安全”

コンポーネントの SCSS でのチェーンフォールバック:

.lh-button {
color: var(--lh-foo, var(--lh-bar, #333));
}

var() のフォールバックはランタイム評価です:チェーン上のいずれかのトークンに暗色上書きがあれば、チェーン全体が反転に追従します。重複宣言は不要です。

対応表:

書き方 評価のタイミング 暗色の挙動
var(--lh-foo, fallback) ランタイム ✅ チェーン上のトークンに暗色上書きがあれば反転
#{$lh-foo}(SCSS 補間) コンパイル期 ❌ 明色にベイクされ固定、var-dark での明示的な再指向が必要
mix($lh-a, $lh-b) などの SCSS 関数 コンパイル期 ❌ 同上

深い主色のプリセットの暗色での明るさ補正

Section titled “深い主色のプリセットの暗色での明るさ補正”

5 つの内蔵プリセットのうち、主色が深いもの(青花瓷 / 朱砂 / 水墨)は暗色で主色がページの地色に「沈み込み」ます。buildPresetDarkVars が自動的に処理します:

判定ルール:--lh-primary-color が暗色のページ地 #1A1A2E(DARK_PAGE_BG)に対するコントラスト比が 3 未満(DARK_PRIMARY_CONTRAST_THRESHOLD)なら深い主色と判定し、次を実行します:

[data-theme='dark'],
.dark {
--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 の値が自分自身を参照する)。そのためこの 1 行だけは字面値を代入し、残りは var() で現在の主色に追従させます。

主色が浅いプリセット(松花绿 / 藤黄)は明るさ補正の必要がありませんが、--lh-primary-color-bg と --lh-selected-color の暗色での再定義は受け取ります。

プリセット注入の順序セマンティクス

Section titled “プリセット注入の順序セマンティクス”

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 沿用明色—

3 つの手段を、弱いものから強いものへ:

  1. pnpm verify:styles 第 7 項(ダークモードカバレッジ):静的走査で、「明色にベイクされているのに var-dark に上書きがない」トークンを探し出します。これが CI ゲートです。
  2. pnpm verify:theme(AA 4.5:1):es/ の成果物を読み、プリセットテーマの明色 / 暗色両方でのコントラスト比を計算します。先に pnpm build が必要です。CI は走らせず、手動で把守します。
  3. 目視検査:docs サイト / 各ライブラリの playground でダークモードに切り替えてコンポーネントを逐次確認します。

新規トークンを追加するときの自己チェックリスト:

  • このトークンは暗色で反転が必要ですか?
  • 必要なら var-dark.scss に上書きを補いましたか?
  • 値は #{$lh-*} 補間ですか?(はい → 暗色の上書きが必須)
  • コンポーネントでの消費は var(--lh-x, fallback) のチェーンですか?

暗色の完全性は 0.4.0 で体系的な補修を一度行いました(メニュー / テーブル / タイムラインなどで文字が見えなくなる問題、changeset を参照)。残る既知の項目は「高さトークン専用」とともに今後の対応待ちです。

Copyright © 2024-2026 FengMo WenXue