From 0647af1b9be59454a00adf012663b5ec58d44b24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Kuna?= <1282324+codecalm@users.noreply.github.com> Date: Sun, 18 Oct 2026 23:46:06 +0200 Subject: [PATCH] Add the neutral `--tblr-fill-*` tokens and `.bg-fill-*` utilities (#3314) --- .changeset/fill-colors.md | 6 ++++ core/scss/_variables.scss | 24 ++++++++++---- core/scss/layout/_root.scss | 6 ++++ .../css-var-prefix.test.mjs.snap | 1 + core/scss/tests/fill-colors.test.mjs | 32 +++++++++++++++++++ core/scss/ui/_badges.scss | 2 +- core/scss/ui/_chat.scss | 2 +- core/scss/utils/_colors.scss | 11 +++++++ docs/content/ui/base/colors.mdx | 20 +++++++++++- 9 files changed, 94 insertions(+), 10 deletions(-) create mode 100644 .changeset/fill-colors.md create mode 100644 core/scss/tests/fill-colors.test.mjs diff --git a/.changeset/fill-colors.md b/.changeset/fill-colors.md new file mode 100644 index 000000000..5e3945910 --- /dev/null +++ b/.changeset/fill-colors.md @@ -0,0 +1,6 @@ +--- +"@tabler/core": minor +"@tabler/docs": patch +--- + +Added the `--tblr-fill-*` tokens and `.bg-fill-*` utilities, used by badges, avatars, code, progress and range tracks and the hover background. diff --git a/core/scss/_variables.scss b/core/scss/_variables.scss index 2a7534364..dc44b7357 100644 --- a/core/scss/_variables.scss +++ b/core/scss/_variables.scss @@ -575,7 +575,17 @@ $active-border-color: var(--primary) !default; $selection-bg: light-dark(color-transparent(var(--primary), 0.1), color-transparent(var(--primary), 0.4)) !default; -$hover-bg: light-dark(color-transparent(var(--body-color), 0.06), color-transparent(var(--body-color), 0.08)) !default; +// Neutral fills: the text color over transparent, so one tint reads the same +// on the page, a card, a dropdown or a modal, in both color modes. +// scss-docs-start fill-variables +$fill-primary: color-transparent(var(--body-color), 0.16) !default; +$fill-secondary: color-transparent(var(--body-color), 0.08) !default; +$fill-tertiary: light-dark(color-transparent(var(--body-color), 0.04), color-transparent(var(--body-color), 0.06)) !default; +$fill-quaternary: color-transparent(var(--body-color), 0.02) !default; + +// scss-docs-end fill-variables + +$hover-bg: var(--fill-tertiary) !default; $disabled-bg: var(--bg-surface-secondary) !default; $disabled-color: color-transparent(var(--body-color), 0.4) !default; @@ -744,7 +754,7 @@ $icon-color: var(--gray-400) !default; // Code $code-color: light-dark(var(--gray-600), var(--gray-400)) !default; -$code-bg: light-dark(var(--gray-100), var(--gray-900)) !default; +$code-bg: var(--fill-tertiary) !default; $code-font-size: $font-size-reative-sm !default; $pre-font-size: 0.92857143em !default; @@ -777,7 +787,7 @@ $avatar-upload-icon-size: 1.5rem !default; $avatar-upload-text-margin-top: 0.25rem !default; $avatar-cover-ring-width: 0.25rem !default; $avatar-brand-offset: -2px !default; -$avatar-bg: var(--bg-surface-secondary) !default; +$avatar-bg: var(--fill-tertiary) !default; $avatar-sizes: ( 'xxs': ( size: 1rem, @@ -1056,7 +1066,7 @@ $badge-padding-x: 0.5em !default; $badge-border-radius: var(--border-radius) !default; $badge-empty-size: 0.5rem !default; $badge-color: var(--secondary) !default; -$badge-bg-color: var(--bg-surface-secondary) !default; +$badge-bg-color: var(--fill-tertiary) !default; $badge-gap: 0.25rem !default; $badge-padding-x-sm: 0.25rem !default; $badge-padding-y-lg: 0.25rem !default; @@ -1501,7 +1511,7 @@ $tooltip-arrow-height: 0.4rem !default; // Loader $loader-size: 2.5rem !default; -$list-group-header-bg: var(--bg-surface-tertiary) !default; +$list-group-header-bg: var(--fill-tertiary) !default; $list-group-header-color: var(--secondary-color) !default; // Modals @@ -1964,7 +1974,7 @@ $sortable-drag-box-shadow: $box-shadow-lg !default; // scss-docs-start progress-variables $progress-height: 0.5rem !default; $progress-font-size: $font-size-base * 0.75 !default; -$progress-bg: var(--border-color) !default; +$progress-bg: var(--fill-secondary) !default; $progress-border-radius: var(--border-radius) !default; $progress-box-shadow: var(--box-shadow-inset) !default; $progress-bar-color: $white !default; @@ -2140,7 +2150,7 @@ $form-switch-disabled-color: light-dark(var(--gray-400), var(--gray-600)) !defau $form-range-track-width: 100% !default; $form-range-track-height: 0.25rem !default; $form-range-track-cursor: pointer !default; -$form-range-track-bg: var(--border-color) !default; +$form-range-track-bg: var(--fill-primary) !default; $form-range-track-border-radius: 1rem !default; $form-range-track-box-shadow: var(--box-shadow-inset) !default; diff --git a/core/scss/layout/_root.scss b/core/scss/layout/_root.scss index eea0b9ccc..6cce81752 100644 --- a/core/scss/layout/_root.scss +++ b/core/scss/layout/_root.scss @@ -50,6 +50,12 @@ --icon-color: #{$icon-color}; + --fill-primary: #{$fill-primary}; + --fill-secondary: #{$fill-secondary}; + --fill-tertiary: #{$fill-tertiary}; + --fill-quaternary: #{$fill-quaternary}; + --fill: var(--fill-primary); + --active-bg: #{$active-bg}; --hover-bg: #{$hover-bg}; diff --git a/core/scss/tests/__snapshots__/css-var-prefix.test.mjs.snap b/core/scss/tests/__snapshots__/css-var-prefix.test.mjs.snap index 72718e27f..a5692898f 100644 --- a/core/scss/tests/__snapshots__/css-var-prefix.test.mjs.snap +++ b/core/scss/tests/__snapshots__/css-var-prefix.test.mjs.snap @@ -62,6 +62,7 @@ exports[`css custom-property prefixing > leaves vendor-owned names alone and pre "--tblr-control-padding-y", "--tblr-control-placeholder-color", "--tblr-danger", + "--tblr-fill-primary", "--tblr-focus-ring", "--tblr-focus-ring-color", "--tblr-focus-ring-offset", diff --git a/core/scss/tests/fill-colors.test.mjs b/core/scss/tests/fill-colors.test.mjs new file mode 100644 index 000000000..19f76f3c1 --- /dev/null +++ b/core/scss/tests/fill-colors.test.mjs @@ -0,0 +1,32 @@ +// Guards the neutral fill tokens. They are tuned to match the design file, so +// a lost light-dark() pair or a changed amount shifts badges, avatars, code, +// progress tracks and the hover background without any other test noticing. +import { describe, expect, it } from 'vitest' +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import { compile as compileSass } from 'sass' + +const scssDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..') +const { css } = compileSass(path.join(scssDir, 'tabler.scss'), { loadPaths: ['node_modules'], style: 'expanded' }) + +// Custom properties are bare here: the `--tblr-` prefix is a build-time postcss pass. +const flat = css.replace(/\s+/g, ' ') + +describe('neutral fills', () => { + it('declares the four levels and the --fill alias', () => { + expect(flat).toContain('--fill-primary: color-mix(in oklab, var(--body-color) 16%, transparent);') + expect(flat).toContain('--fill-secondary: color-mix(in oklab, var(--body-color) 8%, transparent);') + expect(flat).toContain('--fill-tertiary: light-dark(color-mix(in oklab, var(--body-color) 4%, transparent), color-mix(in oklab, var(--body-color) 6%, transparent));') + expect(flat).toContain('--fill-quaternary: color-mix(in oklab, var(--body-color) 2%, transparent);') + expect(flat).toContain('--fill: var(--fill-primary);') + }) + + it('points the hover background at the tertiary fill', () => { + expect(flat).toContain('--hover-bg: var(--fill-tertiary);') + }) + + it('ships .bg-fill as an alias of .bg-fill-primary', () => { + expect(flat).toContain('.bg-fill { background-color: var(--fill) !important; }') + expect(flat).toContain('.bg-fill-quaternary { background-color: var(--fill-quaternary) !important; }') + }) +}) diff --git a/core/scss/ui/_badges.scss b/core/scss/ui/_badges.scss index a29128215..df9125e84 100644 --- a/core/scss/ui/_badges.scss +++ b/core/scss/ui/_badges.scss @@ -33,7 +33,7 @@ @include border-radius(var(--badge-border-radius)); @at-root a#{&} { - background: var(--bg-surface-secondary); + background: var(--fill-secondary); } .icon { diff --git a/core/scss/ui/_chat.scss b/core/scss/ui/_chat.scss index d9a1633d6..8f9d5dbe9 100644 --- a/core/scss/ui/_chat.scss +++ b/core/scss/ui/_chat.scss @@ -17,7 +17,7 @@ .chat-bubble { position: relative; padding: var(--chat-bubble-padding); - background: var(--bg-surface-secondary); + background: var(--fill-tertiary); @include border-radius(var(--border-radius-lg)); } diff --git a/core/scss/utils/_colors.scss b/core/scss/utils/_colors.scss index c3d86e062..913f99cb6 100644 --- a/core/scss/utils/_colors.scss +++ b/core/scss/utils/_colors.scss @@ -61,6 +61,17 @@ } } +// Fills +.bg-fill { + background-color: var(--fill) !important; +} + +@each $level in (primary, secondary, tertiary, quaternary) { + .bg-fill-#{$level} { + background-color: var(--fill-#{$level}) !important; + } +} + @each $color, $value in $theme-colors { .text-#{'' + $color} { --text-opacity: 1; diff --git a/docs/content/ui/base/colors.mdx b/docs/content/ui/base/colors.mdx index 6657d4791..3b43762e0 100644 --- a/docs/content/ui/base/colors.mdx +++ b/docs/content/ui/base/colors.mdx @@ -1,6 +1,6 @@ --- title: Colors -summary: The Tabler color palette with base colors, light variants, the gray scale and social brand colors, each with background and text utilities. +summary: The Tabler color palette with base colors, light variants, neutral fills, the gray scale and social brand colors, each with background and text utilities. description: The Tabler color palette, with base and light shades, the gray scale and social brand colors. --- @@ -20,6 +20,24 @@ Every base color also has a light shade with the `-lt` suffix. It works as a bac +## Fill colors + +Fills are background tints for controls and small surfaces. They are the text color over a transparent background, so the same fill reads the same on the page, a card, a dropdown or a modal, in both color modes. Use them from the strongest to the faintest: + +- `--tblr-fill-primary` (16%) for the inactive track of a slider and other strong neutral shapes. `--tblr-fill` is an alias for it. +- `--tblr-fill-secondary` (8%) for progress tracks and the hover state of neutral badges. +- `--tblr-fill-tertiary` (4% in light mode, 6% in dark) for neutral badges, avatars, code and the hover state of quiet controls. `--tblr-hover-bg` points at it. +- `--tblr-fill-quaternary` (2%) for faint section backgrounds. + + +
+
bg-fill-primary
+
bg-fill-secondary
+
bg-fill-tertiary
+
bg-fill-quaternary
+
+
+ ## Gray palette The gray scale is used for backgrounds, borders and muted text. Tabler ships several gray palettes and switches between them with `data-bs-theme-base`; see [theme base colors](/ui/plugins/theme-base-colors).