Managing Specificity with CSS Variables
A recent project at work had me defining some shared button styles for us to use in conjunction with Tailwind CSS. The styling is much like you might expect; a base button
class with some specific “types” of buttons in different styles. For the purpose of illustration, imagine something like this:
.button {
color: black;
}
.button.type-plain {
color: blue;
}
To render a “plain” button, you use the classes together on an element:
<button class="button type-plain">Click me!</button>
While our design system system dictated that all “plain” buttons use blue text, the reality is that sometimes the buttons need another color. Since we use Tailwind CSS, it would be great if we could one of Tailwind’s text-color
helper functions to override the default and provide a custom color.
<button class="button type-plain text-red">Click me!</button>
However, this led to a problem of specificity; The text-red
selector has a specificity of 1 and the compound selector .button.type-plain
has a specificity of 2, so our button – which should be red – was actually blue!
The problem lies in the fact that we set color
directly in a compound selector, which will have a higher specificity than any of our utilities. What if we could avoid setting color
in the .button.type-plain
selector? If only .button
defines the color
property, then our utilities will be able to override it again1!
The fix I found is to use a CSS variable to define the color to apply, and only actually set the color
property from the .button
selector.
.button {
--button-text-color: black;
color: var(--button-text-color);
}
.button.type-plain {
--button-text-color: blue;
}
Now, .type-plain
will set the color when .button
is the class controlling the color. If a utility like text-red
is present, though, the color
will still be overwritten to our desired value!
-
This works as long as
.text-red
is defined after.button
in your stylesheet. When two selectors on an element have the same specificity, the latter definition is applied. ↩︎