Aller au contenu

Mode sombre

Tous les composants de lh-ui prennent en charge le mode clair et le mode sombre par défaut. Ce n’est pas un thème ajouté après coup, c’est un postulat de conception du système de jetons.

L’interrupteur du mode sombre est un sélecteur CSS, pas du JS :

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

Deux façons de le déclencher :

<!-- Méthode 1 : l'attribut data (recommandé, la sémantique est claire) -->
<html data-theme="dark">
<!-- Méthode 2 : la classe (compatibilité avec le code ancien / les bibliothèques de bascule tierces) -->
<body class="dark">

La couche UI (le ConfigProvider de chaque bibliothèque, ou le code métier) se contente de basculer cet attribut / cette classe : tous les composants changent de couleur immédiatement — les variables CSS sont évaluées à l’exécution, sans aucun coût de nouveau rendu.

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

Le fichier sombre ne définit que 118 entrées, et non l’ensemble complet des 185. Il ne surcharge que les jetons qui doivent se retourner ; les autres conservent la valeur claire.

Le principe directeur est la redirection des jetons sémantiques, et non la réécriture composant par composant :

// ✅ La bonne pratique : en mode sombre, pointer vers un jeton sémantique sombre
[data-theme='dark'],
.dark {
--lh-menu-item-color: var(--lh-text-primary);
--lh-table-cell-color: var(--lh-text-primary);
}
// ❌ La mauvaise pratique : inscrire en dur une couleur concrète en mode sombre
[data-theme='dark'],
.dark {
--lh-menu-item-color: rgba(255, 255, 255, 0.85); // Déconnecté de text-primary ; le prochain réglage de palette devra changer deux endroits
}

C’est le piège le plus facile à rencontrer en mode sombre, et la raison d’être de la vérification 7 de verify:styles.

Dans var.scss, les jetons dont la valeur est de la forme #{$lh-foo} :

var.scss
:root {
--lh-menu-item-color: #{$lh-menu-item-color}; // La variable SCSS est remplacée par la valeur claire concrète à la compilation
}

La variable SCSS est cuite à la compilation en une valeur littérale, ce qui équivaut à :

:root {
--lh-menu-item-color: rgba(0, 0, 0, 0.88); // Ne se retourne pas automatiquement en mode sombre !
}

Résultat : en mode sombre, les textes des menus / tableaux / timelines conservent la valeur claire, texte noir sur fond noir, invisible. Une telle lacune s’est réellement produite ; le compte-rendu de la réparation est dans le changeset de 0.4.0.

Rediriger explicitement, dans var-dark.scss, vers un jeton sémantique sombre :

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

Le repli en chaîne est évalué à l’exécution, il est sûr

Section intitulée « Le repli en chaîne est évalué à l’exécution, il est sûr »

Le repli en chaîne dans les SCSS des composants :

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

Le repli d’un var() est évalué à l’exécution : dès qu’un seul jeton de la chaîne possède une surcharge sombre, toute la chaîne se retourne. Aucune déclaration redondante n’est nécessaire.

La table de comparaison :

Écriture Le moment de l’évaluation Comportement en mode sombre
var(--lh-foo, fallback) À l’exécution ✅ Se retourne dès qu’un jeton de la chaîne possède une surcharge sombre
#{$lh-foo} (interpolation SCSS) À la compilation ❌ Cuit en valeur claire ; nécessite une redirection explicite dans var-dark
mix($lh-a, $lh-b) et autres fonctions SCSS À la compilation ❌ Idem

L’éclaircissement en mode sombre des préréglages à primaire profonde

Section intitulée « L’éclaircissement en mode sombre des préréglages à primaire profonde »

Parmi les cinq préréglages intégrés, ceux dont la couleur primaire est profonde (青花瓷 (porcelaine bleue et blanche) / 朱砂 (cinabre) / 水墨 (encre de Chine)) voient leur couleur primaire « s’enfoncer » dans la couleur de fond de la page en mode sombre. buildPresetDarkVars s’en charge automatiquement :

La règle de décision : si le contraste de --lh-primary-color par rapport au fond de page sombre #1A1A2E (DARK_PAGE_BG) est inférieur à 3 (DARK_PRIMARY_CONTRAST_THRESHOLD), la couleur primaire est jugée profonde, puis :

[data-theme='dark'],
.dark {
--lh-primary-color: color-mix(in srgb, <la valeur littérale> 55%, #fff); /* Éclaircie à 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; /* Une fois éclaircie, le texte blanc n'a plus assez de contraste ; on passe au texte encre */
--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);
}

Pourquoi l’éclaircissement de la couleur primaire utilise la valeur littérale et non var() : color-mix(in srgb, var(--lh-primary-color) 55%, #fff) créerait une boucle d’auto-référence CSS (--lh-primary-color se référençant lui-même dans sa propre valeur). Cette ligne substitue donc la valeur littérale, les autres suivent la couleur primaire courante via var().

Les préréglages à primaire claire (松花绿 (vert pin) / 藤黄 (gamboge)) n’ont pas besoin d’éclaircissement, mais ils reçoivent eux aussi la redéfinition sombre de --lh-primary-color-bg et --lh-selected-color.

La sémantique d’ordre de l’injection des préréglages

Section intitulée « La sémantique d’ordre de l’injection des préréglages »

Le texte généré par buildPresetStyleText met le bloc :root { ... } en premier et le bloc [data-theme='dark'], .dark { ... } ensuite. À spécificité égale, le second gagne ; la valeur claire de --lh-selected-color déclarée dans :root par un préréglage à primaire claire ne fuit donc pas dans le mode sombre.

C’est aussi la cause première du « uniquement des <style>, jamais de style inline » — la spécificité d’un style inline, 1,0,0,0, écrase tout le bloc de variables sombres de var-dark.scss et le mode sombre est purement et simplement désactivé (voir Vue d’ensemble du système de thèmes).

主色与衍生语义

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

Comment vérifier l’exhaustivité du mode sombre

Section intitulée « Comment vérifier l’exhaustivité du mode sombre »

Trois moyens, du plus faible au plus fort :

  1. La vérification 7 de pnpm verify:styles (l’accessibilité du mode sombre) : un scan statique, qui retrouve les jetons « cuits en clair mais sans surcharge dans var-dark ». C’est la gate de CI.
  2. pnpm verify:theme (AA 4.5:1) : lit les artefacts es/ et calcule le contraste des thèmes des préréglages, en clair comme en sombre. Nécessite un pnpm build préalable. La CI ne la lance pas ; à manualiser.
  3. L’inspection visuelle : sur le site de documentation / les playground de chaque bibliothèque, basculer en mode sombre et passer les composants en revue un par un.

La check-list à soi-même lors de l’ajout d’un nouveau jeton :

  • Ce jeton doit-il se retourner en mode sombre ?
  • Si oui, la surcharge a-t-elle été ajoutée dans var-dark.scss ?
  • Sa valeur est-elle une interpolation #{$lh-*} ? (Si oui → la surcharge sombre est obligatoire)
  • Le composant qui le consomme utilise-t-il bien la chaîne var(--lh-x, fallback) ?

L’exhaustivité du mode sombre a fait l’objet d’un rattrapage systématique en 0.4.0 (le problème des textes invisibles des menus / tableaux / timelines, voir le changeset). Les éléments restants connus attendent le « chantier des jetons de hauteur » dans un changeset ultérieur.

Copyright © 2024-2026 FengMo WenXue