16

Is it possible to add a classname to a CSS variable or is there some other way to set it up so that I don't have to manipulate each individual variable directly via javascript? I'd like to keep all my styles in CSS and simply turn on relevant classes with JS. For example, If something like this was possible in CSS:

:root.white{ --bgcol:#FFF; --col:#000; }
:root.black{ --bgcol:#000; --col:#FFF; }

Then I could then just toggle the .black or .white class from javascript to trigger all vars to change. What's the best approach for this type of setup?

cronoklee
  • 6,482
  • 9
  • 52
  • 80
  • 1
    apply this to the `body` instead of the root element and your done. CSS variable doesn't need to be defined in root – Temani Afif Mar 31 '19 at 09:13
  • appy to `body` or some *wrappping div* instead of `:root`... – kukkuz Mar 31 '19 at 09:13
  • The convention has always been to define global custom properties in :root, even if [nobody knows or has ever questioned why](https://twitter.com/NOVALISTIC/status/998844347343822849). Deviating from convention presents no benefits whatsoever. – BoltClock Mar 31 '19 at 09:17
  • @BoltClock true, but in this case where we need to consider class switching better target body or html where it's easy to manipulate with JS. – Temani Afif Mar 31 '19 at 09:19
  • @Temani Afif: :root is html. I don't see how `document.querySelector('body').className = ...` or `document.body.className = ...` would be any easier than `document.querySelector(':root').className = ...` or `document.documentElement.className = ...` in JS. – BoltClock Mar 31 '19 at 09:20
  • @BoltClock probably because it's not intuitive. The OP is not aware it's possible thus the question (I my self never played with `:root` using JS by the way) – Temani Afif Mar 31 '19 at 09:23
  • 4
    @Temani Afif: Not understanding :root is not a reason to avoid using it or to break convention in ways that could potentially break stylesheets (specificity, inheritance, etc). It is a reason to learn about it so you can make the most out of it. – BoltClock Mar 31 '19 at 09:36

2 Answers2

19

That's frankly the best (as in most idiomatic) approach — the use of class names, if not altogether separate stylesheets (as has been tradition for many, many years), to theme entire layouts via custom properties. It's the most "fundamentally CSS" approach with JavaScript merely being the glue that makes the theme switching work. You really can't do much better than that.

For those unaware what :root means and wondering where exactly the class names are being applied, it's the html element (the parent of body). So there is nothing special going on here — you're simply switching class names on the html element. It just happens that global custom properties are conventionally defined for the document root element since it's at the top level of the inheritance chain.

If you have any theme-agnostic custom properties, as well as style properties (i.e. not custom properties) that apply to the root element, keep them in their own unqualified :root rule, separate from your themed custom properties, so they won't be affected by theme switching. Here's an example:

const root = document.documentElement;

// Default theme - should assign declaratively in markup, not JS
// For a classless default theme, move its custom properties to unqualified :root
// Again, keep it separate from the other :root rule that contains non-theme props
// Remember, the cascade is your friend, not the enemy
root.classList.add('white');

document.querySelector('button').addEventListener('click', function() {
  root.classList.toggle('white');
  root.classList.toggle('black');
});
:root {
  --spacing: 1rem;
  color: var(--col);
  background-color: var(--bgcol);
}

:root.white {
  --bgcol: #FFF;
  --col: #000;
}

:root.black {
  --bgcol: #000;
  --col: #FFF;
}

p {
  margin: var(--spacing);
  border: thin dashed;
  padding: var(--spacing);
}
<button>Switch themes</button>
<p>Hello world!
BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
  • Very cool and exactly what I wanted. It's funny I was so close with my hypothetical. I had no idea `:root` was just the `html` tag. Thanks! – cronoklee Mar 31 '19 at 10:00
  • I considered that after the conversation under your question, so I figured it wouldn't hurt to add a primer. I'm glad I did now, and glad you learned something new. – BoltClock Mar 31 '19 at 11:11
  • @BoltClock Shouldn't you use `classList.toggle` in order to avoid breaking other class use on the element? – wizzwizz4 Mar 31 '19 at 11:57
  • 2
    @wizzwizz4: Sure. I wasn't thinking too much about JS robustness when putting this together, but I've updated it. – BoltClock Mar 31 '19 at 12:40
4

Using :root selector is identical to using html, except its specifity is higher, thus there is no issues in using this approach.

For example:

:root {
  --bg: red;
}
:root.blue {
  --bg: blue;
}
// ...
div {
  background: var(--bg);
}

Later, you should just change html's class and variables will change.

You can see an example in this fiddle.

Styx
  • 9,863
  • 8
  • 43
  • 53