From fa678a7a482a9f5b2206d32f7f8853f8682e9e02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Kuna?= <1282324+codecalm@users.noreply.github.com> Date: Mon, 3 Aug 2026 01:47:28 +0200 Subject: [PATCH] Migrate dark-mode root color tokens to `light-dark()` (#2754) --- .changeset/light-dark-root-color-tokens.md | 5 + core/scss/_variables-dark.scss | 1 - core/scss/bootstrap/_root.scss | 109 +++++++++------------ core/scss/layout/_dark.scss | 30 +----- core/scss/layout/_root.scss | 33 ++++--- shared/layouts/RedirectLayout.astro | 1 + 6 files changed, 74 insertions(+), 105 deletions(-) create mode 100644 .changeset/light-dark-root-color-tokens.md diff --git a/.changeset/light-dark-root-color-tokens.md b/.changeset/light-dark-root-color-tokens.md new file mode 100644 index 000000000..2857ab184 --- /dev/null +++ b/.changeset/light-dark-root-color-tokens.md @@ -0,0 +1,5 @@ +--- +"@tabler/core": patch +--- + +Updated root color tokens to use CSS `light-dark()` so paired light and dark values live in one `:root` declaration instead of separate dark-mode overrides. diff --git a/core/scss/_variables-dark.scss b/core/scss/_variables-dark.scss index 0891f6719..31fb2eac2 100644 --- a/core/scss/_variables-dark.scss +++ b/core/scss/_variables-dark.scss @@ -17,7 +17,6 @@ $border-active-color-dark: color.adjust($dark, $lightness: 12%) !default; $body-color-dark: $gray-200 !default; $body-emphasis-color-dark: $white !default; -$code-color-dark: var(--#{$prefix}gray-300) !default; $text-secondary-dark: rgba(153, 159, 164, 1) !default; // Theme text emphasis dark diff --git a/core/scss/bootstrap/_root.scss b/core/scss/bootstrap/_root.scss index cd29bf2d8..a1b0af2df 100644 --- a/core/scss/bootstrap/_root.scss +++ b/core/scss/bootstrap/_root.scss @@ -1,3 +1,4 @@ +@use 'sass:map'; @use 'sass:meta'; @use '../config' as *; @@ -27,16 +28,31 @@ --#{$prefix}#{$color}-rgb: #{$value}; } - @each $color, $value in $theme-colors-text { - --#{$prefix}#{$color}-text-emphasis: #{$value}; - } + // $theme-colors-*-dark maps are only populated when $enable-dark-mode is true (see _maps.scss) + @if $enable-dark-mode { + @each $color, $value in $theme-colors-text { + --#{$prefix}#{$color}-text-emphasis: light-dark(#{$value}, #{map.get($theme-colors-text-dark, $color)}); + } - @each $color, $value in $theme-colors-bg-subtle { - --#{$prefix}#{$color}-bg-subtle: #{$value}; - } + @each $color, $value in $theme-colors-bg-subtle { + --#{$prefix}#{$color}-bg-subtle: light-dark(#{$value}, #{map.get($theme-colors-bg-subtle-dark, $color)}); + } - @each $color, $value in $theme-colors-border-subtle { - --#{$prefix}#{$color}-border-subtle: #{$value}; + @each $color, $value in $theme-colors-border-subtle { + --#{$prefix}#{$color}-border-subtle: light-dark(#{$value}, #{map.get($theme-colors-border-subtle-dark, $color)}); + } + } @else { + @each $color, $value in $theme-colors-text { + --#{$prefix}#{$color}-text-emphasis: #{$value}; + } + + @each $color, $value in $theme-colors-bg-subtle { + --#{$prefix}#{$color}-bg-subtle: #{$value}; + } + + @each $color, $value in $theme-colors-border-subtle { + --#{$prefix}#{$color}-border-subtle: #{$value}; + } } --#{$prefix}white-rgb: #{to-rgb($white)}; @@ -63,47 +79,51 @@ --#{$prefix}body-text-align: #{$body-text-align}; } - --#{$prefix}body-color: #{$body-color}; + --#{$prefix}body-color: light-dark(#{$body-color}, #{$body-color-dark}); --#{$prefix}body-color-rgb: #{to-rgb($body-color)}; - --#{$prefix}body-bg: #{$body-bg}; + --#{$prefix}body-bg: light-dark(#{$body-bg}, #{$body-bg-dark}); --#{$prefix}body-bg-rgb: #{to-rgb($body-bg)}; - --#{$prefix}emphasis-color: #{$body-emphasis-color}; + --#{$prefix}emphasis-color: light-dark(#{$body-emphasis-color}, #{$body-emphasis-color-dark}); --#{$prefix}emphasis-color-rgb: #{to-rgb($body-emphasis-color)}; - --#{$prefix}secondary-color: #{$body-secondary-color}; + --#{$prefix}secondary-color: light-dark(#{$body-secondary-color}, #{$body-secondary-color-dark}); --#{$prefix}secondary-color-rgb: #{to-rgb($body-secondary-color)}; - --#{$prefix}secondary-bg: #{$body-secondary-bg}; + --#{$prefix}secondary-bg: light-dark(#{$body-secondary-bg}, #{$body-secondary-bg-dark}); --#{$prefix}secondary-bg-rgb: #{to-rgb($body-secondary-bg)}; - --#{$prefix}tertiary-color: #{$body-tertiary-color}; + --#{$prefix}tertiary-color: light-dark(#{$body-tertiary-color}, #{$body-tertiary-color-dark}); --#{$prefix}tertiary-color-rgb: #{to-rgb($body-tertiary-color)}; - --#{$prefix}tertiary-bg: #{$body-tertiary-bg}; + --#{$prefix}tertiary-bg: light-dark(#{$body-tertiary-bg}, #{$body-tertiary-bg-dark}); --#{$prefix}tertiary-bg-rgb: #{to-rgb($body-tertiary-bg)}; // scss-docs-end root-body-variables + // $headings-color is itself light-dark(gray-900, white); the dark-mode block below + // overrides it to `inherit`, which can't be expressed as a light-dark() branch. --#{$prefix}heading-color: #{$headings-color}; - --#{$prefix}link-color: #{$link-color}; + --#{$prefix}link-color: light-dark(#{$link-color}, #{$link-color-dark}); --#{$prefix}link-color-rgb: #{to-rgb($link-color)}; --#{$prefix}link-decoration: #{$link-decoration}; - --#{$prefix}link-hover-color: #{$link-hover-color}; + --#{$prefix}link-hover-color: light-dark(#{$link-hover-color}, #{$link-hover-color-dark}); --#{$prefix}link-hover-color-rgb: #{to-rgb($link-hover-color)}; @if $link-hover-decoration != null { --#{$prefix}link-hover-decoration: #{$link-hover-decoration}; } + // $code-color is itself light-dark(gray-600, gray-400); layout/_root.scss re-declares + // --code-color afterwards and is the one that actually wins in the compiled cascade. --#{$prefix}code-color: #{$code-color}; - --#{$prefix}highlight-color: #{$mark-color}; - --#{$prefix}highlight-bg: #{$mark-bg}; + --#{$prefix}highlight-color: light-dark(#{$mark-color}, #{$mark-color-dark}); + --#{$prefix}highlight-bg: light-dark(#{$mark-bg}, #{$mark-bg-dark}); // scss-docs-start root-border-var --#{$prefix}border-width: #{$border-width}; --#{$prefix}border-style: #{$border-style}; - --#{$prefix}border-color: #{$border-color}; - --#{$prefix}border-color-translucent: #{$border-color-translucent}; + --#{$prefix}border-color: light-dark(#{$border-color}, #{$border-color-dark}); + --#{$prefix}border-color-translucent: light-dark(#{$border-color-translucent}, #{$border-color-translucent-dark}); --#{$prefix}border-radius: #{$border-radius}; --#{$prefix}border-radius-sm: #{$border-radius-sm}; @@ -127,66 +147,29 @@ // scss-docs-end root-focus-variables // scss-docs-start root-form-validation-variables - --#{$prefix}form-valid-color: #{$form-valid-color}; - --#{$prefix}form-valid-border-color: #{$form-valid-border-color}; - --#{$prefix}form-invalid-color: #{$form-invalid-color}; - --#{$prefix}form-invalid-border-color: #{$form-invalid-border-color}; + --#{$prefix}form-valid-color: light-dark(#{$form-valid-color}, #{$form-valid-color-dark}); + --#{$prefix}form-valid-border-color: light-dark(#{$form-valid-border-color}, #{$form-valid-border-color-dark}); + --#{$prefix}form-invalid-color: light-dark(#{$form-invalid-color}, #{$form-invalid-color-dark}); + --#{$prefix}form-invalid-border-color: light-dark(#{$form-invalid-border-color}, #{$form-invalid-border-color-dark}); // scss-docs-end root-form-validation-variables } @if $enable-dark-mode { @include color-mode(dark, true) { - color-scheme: dark; - // scss-docs-start root-dark-mode-vars - --#{$prefix}body-color: #{$body-color-dark}; + // -rgb triplets and non-color overrides can't be expressed inside light-dark(), so they stay here. --#{$prefix}body-color-rgb: #{to-rgb($body-color-dark)}; - --#{$prefix}body-bg: #{$body-bg-dark}; --#{$prefix}body-bg-rgb: #{to-rgb($body-bg-dark)}; - - --#{$prefix}emphasis-color: #{$body-emphasis-color-dark}; --#{$prefix}emphasis-color-rgb: #{to-rgb($body-emphasis-color-dark)}; - - --#{$prefix}secondary-color: #{$body-secondary-color-dark}; --#{$prefix}secondary-color-rgb: #{to-rgb($body-secondary-color-dark)}; - --#{$prefix}secondary-bg: #{$body-secondary-bg-dark}; --#{$prefix}secondary-bg-rgb: #{to-rgb($body-secondary-bg-dark)}; - - --#{$prefix}tertiary-color: #{$body-tertiary-color-dark}; --#{$prefix}tertiary-color-rgb: #{to-rgb($body-tertiary-color-dark)}; - --#{$prefix}tertiary-bg: #{$body-tertiary-bg-dark}; --#{$prefix}tertiary-bg-rgb: #{to-rgb($body-tertiary-bg-dark)}; - @each $color, $value in $theme-colors-text-dark { - --#{$prefix}#{$color}-text-emphasis: #{$value}; - } - - @each $color, $value in $theme-colors-bg-subtle-dark { - --#{$prefix}#{$color}-bg-subtle: #{$value}; - } - - @each $color, $value in $theme-colors-border-subtle-dark { - --#{$prefix}#{$color}-border-subtle: #{$value}; - } - --#{$prefix}heading-color: #{$headings-color-dark}; - --#{$prefix}link-color: #{$link-color-dark}; - --#{$prefix}link-hover-color: #{$link-hover-color-dark}; --#{$prefix}link-color-rgb: #{to-rgb($link-color-dark)}; --#{$prefix}link-hover-color-rgb: #{to-rgb($link-hover-color-dark)}; - - --#{$prefix}code-color: #{$code-color-dark}; - --#{$prefix}highlight-color: #{$mark-color-dark}; - --#{$prefix}highlight-bg: #{$mark-bg-dark}; - - --#{$prefix}border-color: #{$border-color-dark}; - --#{$prefix}border-color-translucent: #{$border-color-translucent-dark}; - - --#{$prefix}form-valid-color: #{$form-valid-color-dark}; - --#{$prefix}form-valid-border-color: #{$form-valid-border-color-dark}; - --#{$prefix}form-invalid-color: #{$form-invalid-color-dark}; - --#{$prefix}form-invalid-border-color: #{$form-invalid-border-color-dark}; // scss-docs-end root-dark-mode-vars } } diff --git a/core/scss/layout/_dark.scss b/core/scss/layout/_dark.scss index 5c2b10f3a..3a787dcdd 100644 --- a/core/scss/layout/_dark.scss +++ b/core/scss/layout/_dark.scss @@ -30,34 +30,12 @@ } @include color-mode(dark, true) { + // Must come after layout/_root's `color-scheme: light` on `:root` (equal specificity, + // later source wins) — this is what actually flips every light-dark() token to dark. color-scheme: dark; - --#{$prefix}body-color: var(--#{$prefix}gray-200); - --#{$prefix}secondary: var(--#{$prefix}gray-400); - - --#{$prefix}body-bg: var(--#{$prefix}gray-900); - --#{$prefix}emphasis-color: #{$body-emphasis-color-dark}; - --#{$prefix}emphasis-color-rgb: #{to-rgb($body-emphasis-color-dark)}; - - --#{$prefix}bg-forms: var(--#{$prefix}gray-900); - --#{$prefix}bg-forms-disabled: var(--#{$prefix}gray-800); - --#{$prefix}bg-surface: var(--#{$prefix}gray-800); - --#{$prefix}bg-surface-inverted: var(--#{$prefix}gray-100); - --#{$prefix}bg-surface-secondary: var(--#{$prefix}gray-900); - --#{$prefix}bg-surface-tertiary: var(--#{$prefix}gray-800); - - --#{$prefix}text-inverted: var(--#{$prefix}gray-800); - - --#{$prefix}link-color: var(--#{$prefix}primary); - --#{$prefix}link-hover-color: color-mix(in srgb, var(--#{$prefix}primary), black 20%); - - --#{$prefix}active-bg: #{$lighten-dark}; - --#{$prefix}disabled-color: #{color-transparent(var(--#{$prefix}body-color), 0.4)}; - - --#{$prefix}border-color: var(--#{$prefix}gray-700); - --#{$prefix}border-color-translucent: var(--#{$prefix}dark-mode-border-color-translucent); - --#{$prefix}border-dark-color: var(--#{$prefix}dark-mode-border-dark-color); - --#{$prefix}border-active-color: var(--#{$prefix}dark-mode-border-active-color); + // Everything else here has been folded into light-dark() declarations in + // layout/_root.scss; only genuinely dark-only, non-paired values remain. --#{$prefix}btn-color: #{$darken-dark}; .navbar-brand-autodark { diff --git a/core/scss/layout/_root.scss b/core/scss/layout/_root.scss index 0f992874a..544c9027d 100644 --- a/core/scss/layout/_root.scss +++ b/core/scss/layout/_root.scss @@ -13,40 +13,43 @@ color-scheme: light; --#{$prefix}spacer: var(--#{$prefix}spacer-2); - --#{$prefix}bg-surface: var(--#{$prefix}bg-surface-primary); + --#{$prefix}bg-surface: light-dark(var(--#{$prefix}bg-surface-primary), var(--#{$prefix}gray-800)); --#{$prefix}bg-surface-primary: var(--#{$prefix}white); - --#{$prefix}bg-surface-secondary: var(--#{$prefix}gray-50); - --#{$prefix}bg-surface-tertiary: var(--#{$prefix}gray-50); + --#{$prefix}bg-surface-secondary: light-dark(var(--#{$prefix}gray-50), var(--#{$prefix}gray-900)); + --#{$prefix}bg-surface-tertiary: light-dark(var(--#{$prefix}gray-50), var(--#{$prefix}gray-800)); --#{$prefix}bg-surface-dark: var(--#{$prefix}gray-900); - --#{$prefix}bg-surface-inverted: var(--#{$prefix}gray-900); - --#{$prefix}bg-forms: var(--#{$prefix}bg-surface); - --#{$prefix}bg-forms-disabled: var(--#{$prefix}bg-surface-secondary); + --#{$prefix}bg-surface-inverted: light-dark(var(--#{$prefix}gray-900), var(--#{$prefix}gray-100)); + --#{$prefix}bg-forms: light-dark(var(--#{$prefix}bg-surface), var(--#{$prefix}gray-900)); + --#{$prefix}bg-forms-disabled: light-dark(var(--#{$prefix}bg-surface-secondary), var(--#{$prefix}gray-800)); - --#{$prefix}text-inverted: var(--#{$prefix}gray-100); + --#{$prefix}text-inverted: light-dark(var(--#{$prefix}gray-100), var(--#{$prefix}gray-800)); - --#{$prefix}body-color: var(--#{$prefix}gray-700); - --#{$prefix}body-bg: var(--#{$prefix}bg-surface-secondary); + --#{$prefix}body-color: light-dark(var(--#{$prefix}gray-700), var(--#{$prefix}gray-200)); + --#{$prefix}body-bg: light-dark(var(--#{$prefix}bg-surface-secondary), var(--#{$prefix}gray-900)); + // Same color-mix() expression in both modes — no light-dark() needed. --#{$prefix}link-color: var(--#{$prefix}primary); --#{$prefix}link-hover-color: color-mix(in srgb, var(--#{$prefix}primary), #000 20%); - --#{$prefix}secondary: var(--#{$prefix}gray-500); + --#{$prefix}secondary: light-dark(var(--#{$prefix}gray-500), var(--#{$prefix}gray-400)); --#{$prefix}tertiary: var(--#{$prefix}gray-400); - --#{$prefix}border-color: #{$border-color}; - --#{$prefix}border-color-translucent: #{$border-color-translucent}; - --#{$prefix}border-dark-color: #{$border-dark-color}; + --#{$prefix}border-color: light-dark(#{$border-color}, var(--#{$prefix}gray-700)); + --#{$prefix}border-color-translucent: light-dark(#{$border-color-translucent}, var(--#{$prefix}dark-mode-border-color-translucent)); + --#{$prefix}border-dark-color: light-dark(#{$border-dark-color}, var(--#{$prefix}dark-mode-border-dark-color)); --#{$prefix}border-dark-color-translucent: #{$border-dark-color-translucent}; --#{$prefix}border-light-color: #{$border-light-color}; --#{$prefix}border-light-color-translucent: #{$border-light-color-translucent}; - --#{$prefix}border-active-color: #{$border-active-color}; + --#{$prefix}border-active-color: light-dark(#{$border-active-color}, var(--#{$prefix}dark-mode-border-active-color)); --#{$prefix}border-active-color-translucent: #{$border-active-color-translucent}; --#{$prefix}icon-color: #{$icon-color}; - --#{$prefix}active-bg: #{$active-bg}; + --#{$prefix}active-bg: light-dark(#{$active-bg}, #{$lighten-dark}); --#{$prefix}disabled-bg: #{$disabled-bg}; + // $disabled-color already derives from var(--body-color) at compute time, so it + // adapts to dark mode on its own — no light-dark() or dark-block override needed. --#{$prefix}disabled-color: #{$disabled-color}; --#{$prefix}code-color: #{$code-color}; diff --git a/shared/layouts/RedirectLayout.astro b/shared/layouts/RedirectLayout.astro index b9f86346c..70cab6c14 100644 --- a/shared/layouts/RedirectLayout.astro +++ b/shared/layouts/RedirectLayout.astro @@ -29,4 +29,5 @@ const target = isAbsolute ? url : `${base}${url}` +