テーマシステム概要
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 の部分木に注入し、コンポーネントが再コンパイルなしでスキンを切り替えられるようにすることです。
2 つのパス
Section titled “2 つのパス”1. プリセットテーマ(すぐに使える)
Section titled “1. プリセットテーマ(すぐに使える)”5 つの内蔵プリセット:青花瓷(せいがじ)/ 松花绿(しょうかりょく)/ 朱砂(しゅしゃ)/ 水墨(すいぼく)/ 藤黄(とうおう)。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>詳細はランタイムカスタマイズを参照。
5 つのランタイムプリミティブ
Section titled “5 つのランタイムプリミティブ”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 のテクニックです:最初の 5 つのリテラルの自動補完を保ちつつ、registerTheme({ name: 'my-brand', ... }) で任意の文字列を渡せるようにします。未登録の名前は getThemePreset / getThemeVars で undefined を返します。
注入制約:style 要素のみ許可
Section titled “注入制約: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 の暗色導出”3 つのルール:
-
2 つの変数を無条件に再定義(
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 でなければなりません。
デュアルソース同期
Section titled “デュアルソース同期”内蔵テーマの主色を変更するときは、両方を同期して変更します:
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 構造は変わりません。
- 紋様は独立したシステムです:中華風紋様を参照。
プリセットプレビュー
Section titled “プリセットプレビュー”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
関連ドキュメント
Section titled “関連ドキュメント”- テーマプリセット —— 5 つの内蔵プリセットの詳解
- デザイントークン —— 明色 185 + 暗色 118 の全表
- ランタイムカスタマイズ —— themeVars / componentTheme / patternComponents
- ダークモード —— 仕組みとコンパイル期にベイクされる罠
- 中華風紋様 —— 6 種の内蔵テクスチャ
Copyright © 2024-2026 FengMo WenXue