关于如何建立动态且可配置的配色方案的基础性概览
在这篇博文中,我想分享一下关于如何在 CSS 中管理多种配色方案的想法。试用演示版。
如果您更喜欢视频,可以观看此帖子的 YouTube 版本:
概览
我们将使用自定义属性和 calc() 构建一个无障碍的颜色系统,以制作一个可适应用户偏好设置的网页,同时尽可能简化创作体验。我们从基础品牌颜色开始,并由此构建一个变体系统:2 种文字颜色、4 种表面颜色和一种匹配的阴影。
本指南首先定义每个配色方案的所有颜色。直到最后,它们才用于更改网页。
品牌
通常,品牌颜色已确定,并以 hex 或 rgb 格式提供。此 GUI 挑战赛的基准品牌颜色为 #0af。首先,对于此颜色系统,十六进制值需要转换为 hsl。
* {
--brand: #0af;
--brand: hsl(200 100% 50%);
}
为了实现将品牌颜色调暗或调亮(例如调暗 20%)的概念,需要将 HSL 颜色值的 3 个渠道提取到各自的自定义属性中,如下所示:
* {
--brand-hue: 200;
--brand-saturation: 100%;
--brand-lightness: 50%;
}
CSS 可以对这些颜色属性执行数学运算,例如 calc(var(--brand-lightness) -
20%) 可将亮度值降低 20%。这是构建配色方案的基础,因为 CSS 可以通过调整 HSL 饱和度和亮度值,使所有颜色保持在同一色调系列中。
浅色主题
每种颜色变体都会标上其匹配方案,在本例中,每种颜色变体都会附加 -light。

品牌
从品牌颜色开始,通过将 --brand-hue、--brand-saturation 和 --brand-lightness 自定义属性封装在 hsl () 函数的圆括号内来重建,无需进行任何计算:
* {
--brand-light: hsl(var(--brand-hue) var(--brand-saturation) var(--brand-lightness));
}
文字颜色
接下来,配色方案的基本要素需要文本颜色。在浅色主题中,文字应为深色。请注意,以下颜色的明度较低,远低于 50%。
* {
--text1-light: hsl(var(--brand-hue) var(--brand-saturation) 10%);
--text2-light: hsl(var(--brand-hue) 30% 30%);
}
--text1-light,由于 10% 的亮度非常暗,因此保持 100% 的高饱和度,以便品牌颜色仍能隐约显现在深海军蓝色中。
--text2-light,它不像第 1 种颜色那么深,这很好,因为它是辅助颜色,而且饱和度也低得多。
Surface 颜色
界面颜色是指文字所位于或所处的背景、边框和其他装饰性界面。在浅色主题中,这些是浅色,与深色的文字颜色相反。如需使用 hsl 创建浅色,我们将在第三个亮度值中使用较高的百分比值。我们还会降低饱和度,以免浅灰色看起来过于着色。
* {
--surface1-light: hsl(var(--brand-hue) 25% 90%);
--surface2-light: hsl(var(--brand-hue) 20% 99%);
--surface3-light: hsl(var(--brand-hue) 20% 92%);
--surface4-light: hsl(var(--brand-hue) 20% 85%);
}
由于装饰性颜色往往需要更多变体,因此我们创建了 4 种界面颜色,以用于 :focus 或 :hover 等互动时刻,或营造纸张分层的外观。在这些场景中,最好在悬停时将 --surface2-light 过渡到 --surface3-light,这样悬停就会导致对比度增加(从 99% 的亮度变为 92% 的亮度;使其变暗)。
阴影
配色方案中的阴影是锦上添花,但可为效果增添逼真感,并使其从不真实的黑色阴影中脱颖而出。为此,阴影的颜色将使用色调自定义属性,并略微饱和,但仍非常暗。基本上是构建一个非常暗的略带蓝色的阴影。
* {
--surface-shadow-light: var(--brand-hue) 10% 20%;
--shadow-strength-light: .02;
}
--surface-shadow-light 未封装在 hsl 函数中。这是因为 --shadow-strength 值将组合起来以创建某种不透明度,而 CSS 需要这些部分才能执行计算。如需了解详情,请跳至径向阴影部分。
浅色系
您无需四处寻找任何浅色是如何制作的,它们都在 CSS 中。
* {
--brand-light: hsl(var(--brand-hue) var(--brand-saturation) var(--brand-lightness));
--text1-light: hsl(var(--brand-hue) var(--brand-saturation) 10%);
--text2-light: hsl(var(--brand-hue) 30% 30%);
--surface1-light: hsl(var(--brand-hue) 25% 90%);
--surface2-light: hsl(var(--brand-hue) 20% 99%);
--surface3-light: hsl(var(--brand-hue) 20% 92%);
--surface4-light: hsl(var(--brand-hue) 20% 85%);
--surface-shadow-light: var(--brand-hue) 10% calc(var(--brand-lightness) / 5);
--shadow-strength-light: .02;
}
深色主题
大多数品牌都不是从深色主题开始的,深色主题是其主要主题(通常是浅色主题)的变体。另一方面,用户通常会在不同情境(例如夜间)选择深色主题。鉴于这些因素,我在使用深色主题时会注意以下两点:
- 用户在使用此主题时通常处于黑暗环境中,因此请在黑暗环境中进行测试。
- 颜色应降低饱和度,以免因过于浓烈而在屏幕上产生振动感。

品牌
浅色主题使用 3 个品牌 HSL 颜色通道值,且未进行更改;深色主题则不然。饱和度减半,明度降低了相对 50%。
* {
--brand-dark: hsl(
var(--brand-hue)
calc(var(--brand-saturation) / 2)
calc(var(--brand-lightness) / 1.5)
);
}
文字颜色
在深色主题中,文字颜色应为浅色。以下颜色的明度值较高,因此更接近白色。
* {
--text1-dark: hsl(var(--brand-hue) 15% 85%);
--text2-dark: hsl(var(--brand-hue) 5% 65%);
}
Surface 颜色
在深色主题中,界面颜色应为深色。以下颜色具有较低的明度和饱和度,其中第 1 个 Surface 最暗,为 10%。
* {
--surface1-dark: hsl(var(--brand-hue) 10% 10%);
--surface2-dark: hsl(var(--brand-hue) 10% 15%);
--surface3-dark: hsl(var(--brand-hue) 5% 20%);
--surface4-dark: hsl(var(--brand-hue) 5% 25%);
}
阴影
在深色主题中,阴影可能很难看清。这是有道理的,因为很难使已经相当暗的物体变得更暗。此时,--shadow-strength-dark 就派上了大用场,因为我们可以通过更改一个变量来加深阴影。
* {
--surface-shadow-dark: var(--brand-hue) 50% 3%;
--shadow-strength-dark: .8;
}
此外,还要看看阴影中的饱和度。您在查看界面时是否注意到颜色?尝试从开发者工具中移除饱和度,您更喜欢哪种效果?
深色系
* {
--brand-dark: hsl(var(--brand-hue) calc(var(--brand-saturation) / 2) calc(var(--brand-lightness) / 1.5));
--text1-dark: hsl(var(--brand-hue) 15% 85%);
--text2-dark: hsl(var(--brand-hue) 5% 65%);
--surface1-dark: hsl(var(--brand-hue) 10% 10%);
--surface2-dark: hsl(var(--brand-hue) 10% 15%);
--surface3-dark: hsl(var(--brand-hue) 5% 20%);
--surface4-dark: hsl(var(--brand-hue) 5% 25%);
--surface-shadow-dark: var(--brand-hue) 50% 3%;
--shadow-strength-dark: .8;
}
调暗主题
此配色方案旨在协调亮度与饱和度。饱和度应足以让色调可见,但由于其本身就旨在实现昏暗和低对比度效果,因此也应仅略微通过对比度得分。

品牌
* {
--brand-dim: hsl(
var(--brand-hue)
calc(var(--brand-saturation) / 1.25)
calc(var(--brand-lightness) / 1.25)
);
}
文字颜色
* {
--text1-dim: hsl(var(--brand-hue) 15% 75%);
--text2-dim: hsl(var(--brand-hue) 10% 61%);
}
Surface 颜色
* {
--surface1-dim: hsl(var(--brand-hue) 10% 20%);
--surface2-dim: hsl(var(--brand-hue) 10% 25%);
--surface3-dim: hsl(var(--brand-hue) 5% 30%);
--surface4-dim: hsl(var(--brand-hue) 5% 35%);
}
阴影
* {
--surface-shadow-dim: var(--brand-hue) 30% 13%;
--shadow-strength-dim: .2;
}
同时调暗所有颜色
* {
--brand-dim: hsl(var(--brand-hue) calc(var(--brand-saturation) / 1.25) calc(var(--brand-lightness) / 1.25));
--text1-dim: hsl(var(--brand-hue) 15% 75%);
--text2-dim: hsl(var(--brand-hue) 10% 61%);
--surface1-dim: hsl(var(--brand-hue) 10% 20%);
--surface2-dim: hsl(var(--brand-hue) 10% 25%);
--surface3-dim: hsl(var(--brand-hue) 5% 30%);
--surface4-dim: hsl(var(--brand-hue) 5% 35%);
--surface-shadow-dim: var(--brand-hue) 30% 13%;
--shadow-strength-dim: .2;
}
无障碍颜色
请注意,深色文字颜色集中的最低明度为 65%,而深色表面的最高明度为 25%。这表示两者之间有 40% 的亮度空间。在浅色主题中,浅色主题的留白空间为 55%。将文字颜色与表面颜色之间的亮度差异保持在 40-50% 左右,有助于保持较高的色彩对比度,同时也是在得分较低时可进行调整的细微杠杆。
我称之为“撞撞直到通过”,即不断调整亮度值,直到工具显示我通过为止。
在此挑战中创建的每个主题都通过了对比度得分测试。暗淡配色方案的对比度最低,但仍符合最低要求。为了帮助团队中的其他成员使用对比度良好的颜色,最好创建一个将表面颜色与无障碍文本颜色配对的类名。
.surface1 {
background-color: var(--surface1);
color: var(--text2);
}
.surface2 {
background-color: var(--surface2);
color: var(--text2);
}
.surface3 {
background-color: var(--surface3);
color: var(--text1);
}
.surface4 {
background-color: var(--surface4);
color: var(--text1);
}
Rad Shadow
主题使用名为 .rad-shadow 的实用工具类。此阴影是在 Smooth Shadow 工具中生成的,我非常喜欢。我采用了它生成的代码段,并使用自己的颜色和不透明度计算对其进行了自定义。这样做的目的是为了在每个配色方案中创建可调整的阴影。

为此,我为要调整的每种配色方案创建了 2 个变量:阴影颜色和阴影强度。颜色用于调整饱和度和暗度,而强度则提供了一种简单的方法来提高深色主题的阴影强度。最终结果如下所示。
:root {
--surface-shadow-light: var(--brand-hue) 10% 20%;
--shadow-strength-light: .02;
}
.rad-shadow {
box-shadow:
0 2.8px 2.2px hsl(var(--surface-shadow) / calc(var(--shadow-strength) + .03)),
0 6.7px 5.3px hsl(var(--surface-shadow) / calc(var(--shadow-strength) + .01)),
0 12.5px 10px hsl(var(--surface-shadow) / calc(var(--shadow-strength) + .02)),
0 22.3px 17.9px hsl(var(--surface-shadow) / calc(var(--shadow-strength) + .02)),
0 41.8px 33.4px hsl(var(--surface-shadow) / calc(var(--shadow-strength) + .03)),
0 100px 80px hsl(var(--surface-shadow) / var(--shadow-strength))
;
}
如果要在配色方案中进一步使用阴影,我会将阴影角度也设为设计令牌常量,因为设计中所有阴影的光线方向都应相同。
使用配色方案
预定义颜色完成后,接下来需要将它们转换为与方案无关的属性。我的意思是,作为此配色方案项目中的 CSS 作者,您应该很少需要访问特定配色方案的值。我想让用户轻松保持主题一致性。
为此,应仅通过通用自定义属性(我们稍后将定义)来使用配色方案。这样,使用设计变量的人员就无需担心当前设置的是哪种配色方案,只需使用 Surface 和文本颜色即可。请使用 color: var(--text1) 而不是 color: var(--text1-light)。所有颜色自适应和透视处理都在 CSS 中以更高级别完成。
深入了解一下,以下代码块中的浅色主题连接样式将通用自定义属性与浅色主题专用颜色相关联。现在,所有使用 var(--brand) 的位置都将使用浅色品牌颜色。
浅色主题(自动)
:root {
color-scheme: light;
--brand: var(--brand-light);
--text1: var(--text1-light);
--text2: var(--text2-light);
--surface1: var(--surface1-light);
--surface2: var(--surface2-light);
--surface3: var(--surface3-light);
--surface4: var(--surface4-light);
--surface-shadow: var(--surface-shadow-light);
--shadow-strength: var(--shadow-strength-light);
}
该网站目前使用的是浅色主题。这是一个非常有趣且成功的时刻! 接下来,我们将在其他配色方案上下文中使用预定义的颜色,再体验几次这样的时刻。
深色主题(自动)
@media (prefers-color-scheme: dark) {
:root {
color-scheme: dark;
--brand: var(--brand-dark);
--text1: var(--text1-dark);
--text2: var(--text2-dark);
--surface1: var(--surface1-dark);
--surface2: var(--surface2-dark);
--surface3: var(--surface3-dark);
--surface4: var(--surface4-dark);
--surface-shadow: var(--surface-shadow-dark);
--shadow-strength: var(--shadow-strength-dark);
}
}
浅色主题
[color-scheme="light"] {
color-scheme: light;
--brand: var(--brand-light);
--text1: var(--text1-light);
--text2: var(--text2-light);
--surface1: var(--surface1-light);
--surface2: var(--surface2-light);
--surface3: var(--surface3-light);
--surface4: var(--surface4-light);
--surface-shadow: var(--surface-shadow-light);
--shadow-strength: var(--shadow-strength-light);
}
深色主题
[color-scheme="dark"] {
color-scheme: dark;
--brand: var(--brand-dark);
--text1: var(--text1-dark);
--text2: var(--text2-dark);
--surface1: var(--surface1-dark);
--surface2: var(--surface2-dark);
--surface3: var(--surface3-dark);
--surface4: var(--surface4-dark);
--surface-shadow: var(--surface-shadow-dark);
--shadow-strength: var(--shadow-strength-dark);
}
调暗主题
[color-scheme="dim"] {
color-scheme: dark;
--brand: var(--brand-dim);
--text1: var(--text1-dim);
--text2: var(--text2-dim);
--surface1: var(--surface1-dim);
--surface2: var(--surface2-dim);
--surface3: var(--surface3-dim);
--surface4: var(--surface4-dim);
--surface-shadow: var(--surface-shadow-dim);
--shadow-strength: var(--shadow-strength-dim);
}
此时,作者可以根据需要随意使用提供的配色方案泛型,并且永远不必再担心主题问题。
总结
现在您已经知道我是如何做到的,那么您会怎么做呢?🙂
让我们丰富方法,了解在网络上构建内容的所有方式。 创建 Codepen 或自行托管演示,通过 Twitter 私信发送给我,我会将其添加到下方的“社区混音”部分。
来源
社区混音版 - @chris-kruining 为 no-preference、more 和 less 添加了色调滑块、状态颜色和对比度模式:演示。