使用 自定义属性 的一种方法是将它们视为设计标记。颜色、间距、字体等等。您可以在页面的根目录设置它们,并在整个 CSS 中使用它们。非常有用,并且是不仅对于自定义属性,而且对于过去一百万年来的预处理器变量的经典用例。
另一种使用自定义属性的方法,除了设计标记方法外,还可以是更进一步地使用它们来设置任何给定元素上的每个主要独特样式选择。
想象一下,您有一个这样的卡片(为了演示,已简化)。
.card {
background: hsl(200deg 15% 73%);
border: 4px solid rgb(255 255 255 / 0.5);
padding: 2rem;
border-radius: 8px;
}
.card > h2 {
margin: 0 0 1rem 0;
border-bottom: 3px solid rgba(0 0 0 / 0.2);
}
很好。
但是,当您不可避免地对卡片进行变体时,您需要自己覆盖这些规则集。CSS 自定义属性方法可能类似于
.card {
--card-background: hsl(200deg 15% 73%);
--card-border: 4px solid rgb(255 255 255 / 0.5);
--card-padding: 2rem;
--card-border-radius: 8px;
--card-title-margin: 0 0 1rem 0;
--card-title-border: 3px solid rgba(0 0 0 / 0.2);
background: var(--card-background);
border: var(--card-border);
padding: var(--card-padding);
border-radius: var(--card-border-radius);
}
.card > h2 {
margin: var(--card-title-margin);
border-bottom: var(--card-title-border);
}
现在可能有点冗长,但是看看当我们想要进行变体时会发生什么
.card-variation {
--card-background: purple;
--card-padding-block: 2.5rem;
--card-title-margin: 0 0 2rem 0;
}
以下是立即显而易见的三个优势
- 我只更改了我明确设置为更改的值。我的主要卡片原型保持了我想保留的完整性。
- 我可以设置变体的子元素的样式,而无需正确地重新编写这些选择器。
- 现在,我可以从 HTML 中的
style
属性传递样式覆盖,以进行快速的一次性变体。
更简洁的回退
与其在顶部声明自定义属性,然后在下方使用它们,不如这样同时进行
.card {
background: var(--card-background, hsl(200deg 15% 73%));
border: var(--card-border, 4px solid rgb(255 255 255 / 0.5));
padding: var(--card-padding, 2rem);
border-radius: var(--card-border-radius, 8px);
}
.card > h2 {
margin: var(--card-title-margin, 0 0 1rem 0);
border-bottom: var(--card-title-border, 3px solid rgba(0 0 0 / 0.2));
}
现在,如果--card-background
之类的内容恰好被设置,它将覆盖此处的回退值。我不完全喜欢这种方法,因为它意味着位于.card
上方的元素可以覆盖它。这可能正是您想要的,但它与一开始在.card
级别声明值并不完全相同。这里没有强烈的意见。
进一步分解
这里的例子是,您可能想要单独控制填充。
.card {
--card-padding-block: 2rem;
--card-padding-inline: 2rem;
--card-padding: var(--card-padding-block) var(--card-padding-inline);
padding: var(--card-padding);
}
现在,如果我想要的话,变体可以控制填充的一部分
.card-variation {
--card-padding-inline: 3rem;
}
您必须注意最大的问题。也就是说,如果您在根目录中声明所有这些,那么这将不起作用,因为这些嵌套属性已经解析了。但是,只要它首先在.card
上声明,您在这里就可以了。
太远了?
假设您想对值的每个部分进行超级终极控制。例如
html {
--color-1-h: 200deg;
--color-1-s: 15%;
--color-1-l: 73%;
--color-1-hsl: var(--color-1-h) var(--color-1-s) var(--color-1-l);
--color-1: hsl(var(--color-1-hsl));
}
这有点酷,但可能太远了。颜色几乎肯定会是在根目录中声明并保持不变,因此最大的问题将使覆盖低级子属性成为不可能。此外,如果您有一个--color-1
,那么您可能还有 2-9(或更多),这很好,因为颜色系统比简单地对颜色部分进行数学运算更精致的设计魔法。
可交付的设计系统?
毫无疑问,Tailwind 非常受欢迎。它使用原子方法,其中大量 HTML 类别分别控制一个属性。我认为它的一些受欢迎程度是由于,如果您从这些预配置的类别中进行选择,那么设计最终会非常不错。您无法偏离轨道。您只能从有限的选择中选择看起来不错的值。
我不会说基于自定义属性的样式化方法与之完全相同。例如,您仍然需要考虑一个类名抽象,而不是直接将样式应用于 HTML 元素。但是,它可能会享受 Tailwind 和其他原子类方法的相同约束/限制。如果您只能从一组预定义的--spacing-x
值、--color-x
值和--font-x
值中选择,那么您可能会获得比以前更连贯的设计。
我个人发现,逐渐走向一个更依赖自定义属性的设计系统感觉很好——如果没有任何其他原因,也要使变体和覆盖更容易管理。
第三方设计系统将它们提供的内容作为… 仅仅是一组可以随意使用的自定义属性,怎么样?

第三方可交付成果甚至不需要像这样成为整个厨房水槽。例如,Adam Argyle 的 transition.style 提供一个“Hackpack”,它只是过渡动画辅助程序的自定义属性。
可理解性成本
我听到的一个反对这种全面的自定义属性方法的观点是新人可理解性。如果您是编写该系统的人,那么它可能对您来说很有意义。但这是在 CSS 之上的一个额外抽象。所有的人都共享 CSS 知识,而定制系统知识只由积极参与其中的人共享。
新加入一个 heavily使用自定义属性的系统将会有很大的学习曲线。
我注意到 YouTube 使用了这种自定义属性方法。它真的很酷, ngl。
我专门在 BEM 修饰符中使用这种方法。我真的很厌倦不得不这样做
或者甚至将子元素嵌套在修饰符中。无论哪种方式,您都没有在一个规则集中显示修饰符的实际作用,您还需要寻找所有可能受影响的子元素。使用自定义属性可以很好地解决这个问题
我认为您第二个示例中最大的问题——当您将 CSS 规则(.card)中的每个属性都设置为可配置时——在这一点上,您也可以在 HTML 中使用原子类别,或者添加一个新的 .card-variant 和 Tailwind 的 @apply。
一般来说,我认为“可配置组件”方法在组件具有少量变体时才有意义。否则会导致不良设计——在这种情况下,创建一个新组件将是更好、更易于维护的答案。
这就像您在设计代码时,必须查看此特定视觉变体的“域”。
有时域是相同的,例如,h2 标题根据站点部分具有不同的颜色。
但有时您会更改卡片的样式,例如,将其变成“信息框”,并且您已经更改了域。即使您可以将第一个组件配置为看起来像第二个组件,您也创建了一条依赖关系线,当应用程序需要域 1 的行为比域 2 更加不同时,这条依赖关系线将在某个时刻断裂。
老实说,除非有非常特定的用例,否则我会避免使用自定义属性。如果 Sass 变量可以完成这项工作,而且它在运行时不会改变,我会使用它们。
如果某些东西需要知道其他东西的属性,或者某些东西可能在运行时发生改变,我会使用它们,例如。
当头部组件发生变化时,一些 JavaScript 代码来更新根元素上的 `--dynamic-header-height` 可以避免很多麻烦。或者绝对定位的子元素需要知道其父元素的尺寸。
自定义属性很酷,但是它们的语法太丑陋,太污染了,除非有运行时的需要,否则可以省略它们。
哇...我也做过一个关于这个的视频。它与 AlpineJS 配合得很好。我对此方法有55%的认可。我仍然喜欢 Talwind 和 Assembler CSS,但是对于那些不想污染 HTML 代码的人来说,Pollen 可能是不错的选择。 https://www.youtube.com/watch?v=Cl2uFIUXqeQ