Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overriding :root CSS variables from inner scopes [duplicate]

In our design system at Stack Overflow, we use Less to compile CSS color values.

We have global Less variables like @orange-500 that are frequently modified for hover states, building border styling, background colors, etc.

In Less, this is written as darken(@orange-500, 5%). I'm trying to achieve something similar using native CSS variables. Switching to CSS variables will allow us to ship features that rely on theming (Stack Exchange Network, Dark Mode, etc.) much faster, with way fewer lines of CSS, while enabling swapping variables on media query (high contrast, dark mode, etc).

This example of overriding our color’s lightness value in hsl works when the variables are scoped to a CSS class:

.card {
  --orange: hsl(255, 72%, var(--lightness, 68%));
  background: var(--orange);
}
.card:hover {
  --lightness: 45%;
}
<div class="card">
  Hello world
</div>

However, we need to specify our color variables globally in a single, swappable place to support global theming, but this doesn't work as expected:

:root {
  --orange: hsl(255, 72%, var(--lightness, 68%));
}
.card {
  background: var(--orange);
}
.card:hover {
  --lightness: 45%;
}
<div class="card">
  Hello world
</div>

I've tried switching from :root to html or body without any luck. Any workarounds to this?

like image 888
Aaron Shekey Avatar asked Oct 02 '19 17:10

Aaron Shekey


People also ask

Can you override CSS variables?

You can avoid writing down repeated CSS values by using these variables, furthermore, these are easier to understand. It is also possible to override CSS variables with one another. In this write-up, we have discussed CSS variables, and how to override them in detail using various examples.

How do you override attributes in CSS?

To override the CSS properties of a class using another class, we can use the ! important directive. In CSS, ! important means “this is important”, and the property:value pair that has this directive is always applied even if the other element has higher specificity.

Do CSS variables need to be in root?

Not really. All it does is select html with a higher specificity, the same way a class selector has higher specificity than an element selector when selecting a div . The main reason that :root is suggested is because CSS isn't only used to style HTML documents.

How do you overwrite colors in CSS?

Example of overriding CSS style with the Class selector: If that class has a background-color of blue, and you want your <div> to have a red background instead, try to change the color from blue to red in the class itself.


1 Answers

This is a scoping issue. The way you're doing it, you're inheriting --orange from the :root, and --orange in the :root has a lightness of 68%.

In order to change it, you'll want to re-scope the --orange variable to an element that will look up the new --lightness value. There's a few ways to pull this off:

Option 1: duplicate the --orange variable on the element:

:root {
  --lightness: 68%;
  --orange: hsl(255, 72%, var(--lightness));
}
.card {
  background: var(--orange);
  --orange: hsl(255, 72%, var(--lightness));
}
.card:hover {

  --lightness: 45%;
}
<div class="card">
  Hello world
</div>

Obviously this kinda stinks, because you're going to have to duplicate that --orange variable.

Option 2: You could abstract the other parameters of --orange so that it's not as duplicative. I'd be a fan of this approach despite the fact that it's more text:

:root {
  --lightness: 68%;
  --orangeHue: 255;
  --orangeSat: 72%;
  --orange: hsl(var(--orangeHue), var(--orangeSat), var(--lightness));
}
.card {
  background: var(--orange);
  --orange: hsl(var(--orangeHue), var(--orangeSat), var(--lightness));
}
.card:hover {

  --lightness: 45%;
}
<div class="card">
  Hello world
</div>

What you could also do is scope this specifically to a .darkMode class that might be applied to the HTML element or the body. This could also make sense because it's clear what the intent is from the code:

Option 3

:root {
  --lightness: 68%;
  --orangeHue: 255;
  --orangeSat: 72%;
  --orange: hsl(var(--orangeHue), var(--orangeSat), var(--lightness));
}

.card {
  background: var(--orange);

}
.card:hover {
  --lightness: 45%;
}
.darkMode .card {
  --orange: hsl(var(--orangeHue), var(--orangeSat), var(--lightness));
}
  <div class="darkMode">
    <div class="card">
      Hello world
    </div>
  </div>

Regardless of how you go, the issue is that the --orange variable is inheriting from its original scope where --lightness is set. Think of it as "inheriting a computed value".

In order to get --orange to get the new lightness, you need a new --orange somewhere.

Option 4

I'm not sure what your theme pattern is, but I can explain how I created a dark mode on my own blog . If you look at the CSS What you'll see is that I've created two complete themes that follow the same naming convention:

--themeLightTextColor: rgb(55, 55, 55);
--themeLightBGColor: rgb(255, 255, 255);
--themeLightAccentColor: rgb(248, 248, 248);
--themeLightTrimColor: rgb(238, 238, 238);
--themeDarkTextColor: rgb(220, 220, 220);
--themeDarkBGColor: rgb(23, 23, 23);
--themeDarkAccentColor: rgb(55, 55, 55);
--themeDarkTrimColor: rgb(40, 40, 40);

What I then do is create a third set of variables whose job it is to be the "active" managers:

--themeActiveLinkColor: var(--linkColor);
--themeActiveLinkColorHover: var(--linkColorHover);
--themeActiveTextColor: var(--themeLightTextColor);
--themeActiveEditorialTextColor: var(--themeLightPltNLow);
--themeActiveBGColor: var(--themeLightBGColor);
--themeActiveAccentColor: var(--themeLightAccentColor);
--themeActiveTrimColor: var(--themeLightTrimColor);

Then, I scope the active theme settings under a single class:

.theme--dark {
   --themeActiveTextColor: var(--themeDarkTextColor);
   --themeActiveEditorialTextColor: var(--themeDarkPltNLow);
   --themeActiveBGColor: var(--themeDarkBGColor);
   --themeActiveAccentColor: var(--themeDarkAccentColor);
   --themeActiveTrimColor: var(--themeDarkTrimColor);
}

It seems like maybe your intent is to not have to explicitly declare a theme, but rather tweak some "root variables" to adjust it. But I would suggest that maybe you have a pattern in place where a single class can change an active theme. The advantage to this pattern is that you would be able to also adjust any "root variables" on the class name.

like image 185
paceaux Avatar answered Sep 18 '22 07:09

paceaux