自定义属性 不仅使我们能够使代码更高效,而且还允许我们使用 CSS 执行一些真正的魔法。 它们在主题方面具有巨大的潜力。 在 Atomic Smash,我们使用 Tailwind CSS(一个实用程序类框架)来编写我们的样式。 在本文中,我们将探讨如何将自定义属性用于主题,以及如何将它们与 Tailwind 集成以最大程度地提高代码的可重用性。 我们不会介绍如何开始使用 Tailwind——请查看官方文档——但即使您是新手,您也可能会发现其中一些技巧很有用。
主题概述
假设我们有一个带有标题、正文和按钮的“号召性用语”(CTA)组件。

为这种配色方案编写常规(非 Tailwind)CSS 将如下所示
.cta {
background-color: #742a2a; // dark red
color: #ffffff; //white
}
.cta__heading {
background-color: #e53e3e; // medium red
color: #742a2a;
}
.cta__button {
background-color: #e53e3e;
}
使用 Tailwind,我们将在 HTML 中将这些颜色应用为实用程序类
<div class="bg-red-900 text-white">
<h3 class="bg-red-600 text-red-900">Join our mailing list</h3>
<div>
<p>Be the first to hear about our new offerings</p>
<button class="bg-red-600" type="button">Sign up</button>
</div>
</div>
我故意省略了与基本配色方案以外的任何内容相关的类,但您可以在此演示中查看完整示例
现在,如果我们想将不同的配色方案应用于我们的组件,我们需要覆盖原始组件的颜色值。 在没有 Tailwind 的情况下,一种常见的做法是将主题类附加到组件本身,并在级联中较低的位置重新定义颜色值。 因此,对于具有 .cta--blue
修饰符类(使用BEM 约定)的组件,我们将应用蓝色配色方案的 CSS 值
.cta--blue {
background-color: #2a4365; // dark blue
}
.cta--blue .cta__heading {
background-color: #3182ce; // medium blue
color: #2a4365;
}
.cta--blue .cta__button {
background-color: #3182ce;
}

如果我们使用 Sass 或其他预处理器,我们可能会通过使用变量来表示这些颜色名称来使生活更轻松,并且我们可能会嵌套 .cta__heading
和 .cta__body
选择器。 它并不能使我们的代码更简洁,但通过在一个地方更新这些值,它确实使代码更易于管理。
现在,假设我们有 10 种不同的配色方案,就像我最近在一个项目中的经历一样。 我们的代码开始变得更长,因为我们基本上是将上述示例复制了 10 次以更改这些颜色值。 现在想象一下,我们设计系统中的每个组件都需要 10 种配色方案,并且其中许多组件比我们简单的 CTA 复杂得多。 也许我们的主题也需要不同的字体。 突然之间,我们有很多 CSS 需要编写。
使用 Tailwind 进行主题
另一方面,如果我们使用 Tailwind,则需要更改 HTML 本身中的多个类。 即使我们使用 JavaScript 框架(如 React 或 Vue),这也不是一项简单的任务。 为了确保在生产版本中删除未使用的样式,Tailwind 不建议使用字符串连接来创建类名(在撰写本文时)。 因此,构建我们的主题可能意味着将大量逻辑堆积到我们的组件中。
使用自定义属性进行主题
通过对颜色主题使用自定义属性,我们可以大大减少需要编写的代码量,并减轻维护负担。 首先让我们看看如何在常规 CSS 中执行此操作。
我们将自定义属性定义为 :root
选择器上的变量,使其成为全局变量。(body
选择器对我们来说同样有用。)然后我们可以在选择器中使用这些变量,以代替颜色属性值
:root {
--primary: #742a2a; // dark red;
--secondary: #e53e3e; // medium red
}
.cta {
background-color: var(--primary);
color: white;
}
.cta__heading {
background-color: var(--secondary);
color: var(--primary);
}
.cta__button {
background-color: var(--secondary);
}
这就是真正的魔法所在:现在创建每个主题的代码变成了仅更新这些自定义属性值的案例。 新值将在我们应用主题类的位置继承
.th-blue {
--primary: #2a4365; // dark blue
--secondary: #3182ce; // medium blue
}
如果我们想要蓝色配色方案,我们可以将 .th-blue
类应用于组件,甚至可以在 <body>
标签上使用它以应用页面范围的主题,然后可以根据需要在各个组件上覆盖它。 与组件特定类(例如原始代码中的 .cta--blue
)相比,使用实用程序类可能会节省我们编写更多代码,因为它可以应用于我们代码库中的任何位置。
处理旧版浏览器
与许多代理机构一样,Atomic Smash 的许多客户仍然要求我们支持 Internet Explorer 11。 虽然在大多数情况下我都可以接受渐进增强方法(例如,为不支持 CSS Grid 的浏览器提供更简单的回退布局),但我发现主题是一个通常不允许轻松折衷的领域。 客户希望看到他们的品牌颜色和字体,即使是在旧版浏览器上。 使用特性查询提供回退将需要大量额外的工作,这将抵消使用自定义属性的益处。 为了克服这个问题,我们需要一个 polyfill。
在 IE 11 中为自定义属性提供 polyfill 有几个选项。
postcss-custom-properties
第一个是使用名为 postcss-custom-properties 的 PostCSS 插件。 如果您已经在工作流程中使用 PostCSS,则添加此插件非常简单。 它通过处理您的 CSS 并输出变量的结果作为属性值来工作。 因此,如果您有以下 CSS
:root {
--color: red;
}
h1 {
color: var(--color);
}
处理后的结果将是
h1 {
color: red;
color: var(--color);
}
不支持自定义属性的浏览器将忽略第二个规则并回退到常规属性值。 还有一个选项可以在输出中删除具有自定义属性的规则,因此文件大小将更小。 这意味着任何浏览器都不会获得自定义属性——如果您动态更新变量,这是一个问题——但您可以在代码中将它们用于静态值,而不会产生任何不良影响。
不幸的是,此 polyfill 有一些限制
- 您需要在配置中指定定义自定义属性的文件(或文件)。
- 自定义属性只能在
:root
选择器上定义。
第一个限制相对微不足道,但第二个限制不幸地使此 polyfill 完全无法用于我们的主题用例。 这意味着我们无法在选择器上重新定义变量来创建主题。
ie11CustomProperties
此 polyfill 选项涉及提供客户端脚本,而不是预处理 CSS。 我们可以将以下脚本添加到我们的头部以确保 polyfill 仅在 IE 11 中加载
<script>window.MSInputMethodContext && document.documentMode && document.write('<script src="https://cdn.jsdelivr.net.cn/gh/nuxodin/[email protected]/ie11CustomProperties.min.js"><\/script>');</script>
这使我们能够像此处示例中一样充分利用自定义属性,因此这是我决定采用的解决方案。 它有一个限制,即在 style
属性中设置的自定义属性不会被 polyfill。 但我已经针对上述主题示例对其进行了测试,它运行良好。
但这与 Tailwind 有什么关系?
正如我们已经看到的,实用程序类——可以在 HTML 中任何位置应用的单一用途类——可以使我们的代码更具可重用性。这是 Tailwind 和其他实用程序类框架的主要卖点——因此,您发布的 CSS 文件的大小应该会更小。Tailwind 提供了多种颜色类:.bg-red-medium
将为我们提供红色的 background-color
属性值,.text-red-medium
用于 color
,依此类推,用于 border
、box-shadow
或任何您可以想到需要颜色值的地方。
颜色可以在配置文件中定义
module.exports = {
theme: {
colors: {
red: {
medium: '#e53e3e',
dark: '#742a2a'
},
blue: {
medium: '#3182ce',
dark: '#2a4365'
}
}
}
}
如果我们想为我们的 Tailwind 类使用自定义属性值,我们可以在配置中指定它们
module.exports = {
theme: {
colors: {
'th-primary': 'var(--primary)',
'th-secondary': 'var(--secondary)'
}
}
}
我在我的颜色和主题相关的类名之前添加了 th-
前缀,以便很明显它们与主题特别相关,但请随意使用任何适合您的约定。
现在这些类可以通过 Tailwind 使用。使用 .bg-th-primary
等效于编写
.some-element {
background-color: var(--primary);
}
在我们的 CSS 中,我们可以像以前一样定义我们主题的自定义属性
:root {
--primary: #742a2a;
--secondary: #742a2a;
}
.th-blue {
--primary: #2a4365;
--secondary: #3182ce;
}
让我们将这些类应用到我们的 HTML 中。第一个示例为我们提供了一个具有默认主题(在 :root 上定义的变量)的组件。第二个具有我们的蓝色主题。唯一的区别是在组件上添加了 .th-blue
类。(再次,为了简洁和清晰,我省略了与主题无关的类。)
<!--Component with default (red) theme-->
<div class="bg-th-primary">
<h3 class="bg-th-secondary text-th-primary">Join our mailing list</h3>
<div>
<p>Be the first to hear about our new offerings</p>
<button class="bg-th-secondary" type="button">Sign up</button>
</div>
</div>
<!--Component with blue theme-->
<div class="th-blue bg-th-primary">
<h3 class="bg-th-secondary text-th-primary">Join our mailing list</h3>
<div>
<p>Be the first to hear about our new offerings</p>
<button class="bg-th-secondary" type="button">Sign up</button>
</div>
</div>
使用配置作为样式指南
Tailwind 鼓励您在配置中定义所有变量,我个人也认为这是一种更好的方法。这意味着配置文件可以成为唯一的真相来源,而不是(可能)最终在多个地方定义您的颜色和其他主题值。幸运的是,我们还可以将 Tailwind 配置文件中的值用于我们的自定义属性。我们需要首先在配置中定义所有颜色(假设我们不使用 Tailwind 附带的默认调色板)
module.exports = {
theme: {
colors: {
red: {
medium: '#e53e3e',
dark: '#742a2a'
},
blue: {
medium: '#3182ce',
dark: '#2a4365'
},
'th-primary': 'var(--primary)',
'th-secondary': 'var(--secondary)'
}
}
}
然后我们可以在 CSS 中访问主题对象
:root {
--primary: theme('colors.red.dark');
--secondary: theme('colors.red.medium');
}
.th-blue {
--primary: theme('colors.blue.dark');
--secondary: theme('colors.blue.medium');
}
总结
我真的很兴奋能够使用自定义属性而无需担心浏览器支持,更不用说能够将它们与我们现有的工作流程无缝集成。很难夸大它们将为我们节省的主题时间。我希望即使您不是 Tailwind 用户,本文也可能鼓励您尝试将自定义属性用于此用例。
非常喜欢这篇文章。对我来说,最后一步是最重要的,在配置文件中拥有唯一的真相来源。感谢分享。现在,如果我能够理解从 SCSS 到 PostCSS 的切换,那么我就可以按照预期的方式使用 theme() 了……
需要注意的是,Tailwind 有很棒的实用程序,比如 bg-opacity、text-opacity 等,它们将无法工作。
解决方法是保存像
--red:255,0,0
这样的变量,并可能重写默认插件来执行此操作喜欢这篇文章和方法,我很期待尝试一下。感谢您的撰写,Michelle!
很棒的文章!很好地打破了 Tailwind 不能与自定义属性一起使用的迷思。我只是对最后一步有点困惑……在 CSS 中定义的变量不应该称为 –primary/–secondary 而不是 –th-primary/–th-secondary 吗?因为在 Tailwind 配置中,您使用的是 var(–primary)/var(–secondary)。
是的,你说得对——这是一个错别字!我刚刚更新了帖子。
您好,Michelle,
感谢这篇文章和您的见解……非常好的信息!
我是一位新的 Tailwind 用户。在我的学习过程中,我刚刚发布了一个名为 tw-themes 的颜色主题实用程序,它与您的文章一致。
您可以在此处找到文档:https://tw-themes.js.org/
它促进了可在运行时选择的动态颜色主题。最棒的是它通过“阴影反转”自动执行您的暗模式!
当您有时间时,我想知道您的想法和/或推广。
提前感谢。