Less CSS - Change Variable Value for Theme Colors Depending on Body Class

LESS CSS - Change variable value for theme colors depending on body class

Assuming you remain with wanting to theme it within one style sheet (and not multiple sheets as cimmanon noted in the comments), and assuming you are using LESS 1.3.2+, then the following code works to reduce the amount of duplication by setting up a loop through the classes that need theme changes.

Note that this does not work on Codepen (it is throwing an error uncaught throw #, perhaps because they are running an earlier version of LESS), but you can see it compiling correctly by putting the code into LESS's compiler.

LESS (based off your Codepen code with an added theme for demo)

//////////////////////////////////////////////////////
// CONSTANTS

@lightColour: #fff;
@darkColour: #000;
@lightBg: #fff;
@darkBg: #000;
@numberOfThemes: 3; //controls theme loop

//////////////////////////////////////////////////////
// MIXINS

//Theme Definitions by parametric mixin numbers (1), (2), etc.
.themeDefs(1) {
@lightColour: #f00;
@darkColour: #fff;
@lightBg: #f00;
@darkBg: #fff;
}

.themeDefs(2) {
//inverse of 1
@lightColour: #fff;
@darkColour: #f00;
@lightBg: #fff;
@darkBg: #f00;
}

.themeDefs(3) {
@lightColour: #cfc;
@darkColour: #363;
@lightBg: #cfc;
@darkBg: #363;
}

.curvy {
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
}

//////////////////////////////////////////////////////
// GENERAL STYLING

* {padding: 0;margin: 0;}
html {text-align: center;}
h2 {padding: 20px 0;}

.box {
.curvy;
color: @lightColour;
background: @darkBg;
display:inline-block; width:10%; padding:20px 5%; margin:0 1% 20px 1%;
}

//////////////////////////////////////////////////////
// THEME BUILDING

.buildThemes(@index) when (@index < @numberOfThemes + 1) {

.theme-@{index} {
.themeDefs(@index);
color: @lightColour;
background: @darkBg;

.box {
color: @darkColour;
background: @lightBg;
}
}
.buildThemes(@index + 1);
}
//stop loop
.buildThemes(@index) {}
//start theme building loop
.buildThemes(1);

CSS Output (only showing the looped theme css for brevity)

.theme-1 {
color: #ff0000;
background: #ffffff;
}
.theme-1 .box {
color: #ffffff;
background: #ff0000;
}
.theme-2 {
color: #ffffff;
background: #ff0000;
}
.theme-2 .box {
color: #ff0000;
background: #ffffff;
}
.theme-3 {
color: #ccffcc;
background: #336633;
}
.theme-3 .box {
color: #336633;
background: #ccffcc;
}

LESS condition based on CSS class to set a LESS variable

The LESS file cannot read the actual class applied to the html body element at run time (you would probably need to implement a javascript solution to do something like that).

If you just want to have all themed css ready for use based on the body class, the best way to implement this to have all the necessary theme based css in a mixin, then apply it under the theme classes. This reduces code duplication. For example:

LESS

//mixin with all css that depends on your color
.mainThemeDependentCss() {
@contrast: lighten(@themeColor, 20%);
h1 {color: @themeColor;}
p {background-color: @contrast;}
}

//use the mixin in the themes
body.themeBlue {
@themeColor: blue;
.mainThemeDependentCss();
}

body.themeRed {
@themeColor: red;
.mainThemeDependentCss();
}

CSS Output

body.themeBlue h1 {
color: #0000ff;
}
body.themeBlue p {
background-color: #6666ff;
}
body.themeRed h1 {
color: #ff0000;
}
body.themeRed p {
background-color: #ff6666;
}

For some other answers that deal with aspects or ways of theming, see:

  • LESS CSS - Change variable value for theme colors depending on body class
  • LESS.css variable depending on class
  • LESS CSS: abusing the & Operator when nesting?

LESS.css variable depending on class

You cannot overwrite variables in LESS (within the same scope). The documentation specifically says:

Note that variables in LESS are actually ‘constants’ in that they can only be defined once.

For what you desire, you need to do a mixin:

Example LESS Code

.colorDefs(@c1: #222, @c2: #fff) {
@colorOne: @c1;
@colorTwo: @c2;
@darkGradientStart: lighten(@colorOne, 10%);
@darkGradientStop: lighten(@colorOne, 5%);
@lightGradientStart: @colorTwo;
@lightGradientStop: darken(@colorTwo, 7%);
}

.theme-blue {
//import color definitions
.colorDefs(blue, yellow);

// use them
color: @colorOne;
background-color: @colorTwo;

.gradient1 {
background-image: linear-gradient(top, @darkGradientStart, @darkGradientStop);
}

.gradient1 {
background-image: linear-gradient(top, @lightGradientStart, @lightGradientStop);
}
}

.theme-green {
//import different color definitions
.colorDefs(green, red);

// use them
color: @colorOne;
background-color: @colorTwo;

.gradient1 {
background-image: linear-gradient(top, @darkGradientStart, @darkGradientStop);
}

.gradient1 {
background-image: linear-gradient(top, @lightGradientStart, @lightGradientStop);
}
}

Example CSS Output

.theme-blue {
color: #0000ff;
background-color: #ffff00;
}
.theme-blue .gradient1 {
background-image: linear-gradient(top, #3333ff, #1a1aff);
}
.theme-blue .gradient1 {
background-image: linear-gradient(top, #ffff00, #dbdb00);
}
.theme-green {
color: #008000;
background-color: #ff0000;
}
.theme-green .gradient1 {
background-image: linear-gradient(top, #00b300, #009a00);
}
.theme-green .gradient1 {
background-image: linear-gradient(top, #ff0000, #db0000);
}

Solving 4K (i.e. a lot of) Lines of Code

ed1nh0 commented about having 4K lines of code using the color variables, and not being able to "put that in a mixin." Let me make a few comments on that:

  1. If 4K lines of code depend upon the body class to define the colors, then it is probably best to split each color into its own css file, and only load that file as needed (i.e. not grouping every code color into one file). This then calls into question whether you really want to be controlling color by body class.
  2. Regardless of whether one does what is recommended in 1., I believe one could still handle this with 4K of lines that use the colors. I believe the issue is not in using a mixin to define the color values themselves (i.e. not 4K lines of color variable definitions), but rather in the 4K lines of properties, classes, etc. that need repeating that are using the colors. But that repetition can be handled just as easily by wrapping it all in a mixin also. So my original answer above could be abstracted further to this (note that .colorDefs is the same as above and not repeated here):

LESS

.themeProperties() { // Imagine inside here the 4K lines of code
// use them
color: @colorOne;
background-color: @colorTwo;

.gradient1 {
background-image: linear-gradient(top, @darkGradientStart, @darkGradientStop);
}

.gradient1 {
background-image: linear-gradient(top, @lightGradientStart, @lightGradientStop);
}
}

.theme-blue {
//import color definitions
.colorDefs(blue, yellow);
.themeProperties(); //4K lines repeated here
}

.theme-green {
//import different color definitions
.colorDefs(green, red);
.themeProperties(); //4K lines repeated here
}

The above does assume that there are not differences in how the variables are used by the properties, just what the values of those properties are. If there were any "differences," then some tweaking mixins may need to be done for certain situations, but the concept should still hold.

LessCss dynamic variables based on ancestor class

Well, no, you can't use class name to determine a variable or a return value. So it's usually done in reverse, for example like this:

@brand-default: #649d84;
@brand-africa: #df6f20;
@brand-nz: #444444;

h1 {
.brand-colors();
}

h2 {
.brand-colors(background-color);
}

.brand-colors(@property: color) {
.color(default);
.color(africa);
.color(nz);

.color(@name) {
.brand-@{name} & {
@value: 'brand-@{name}';
@{property}: @@value;
}
}
}

Or like this:

@brand-default: #649d84;
@brand-africa: #df6f20;
@brand-nz: #444444;

h1 {
.brand-colors({
color: @color;
});
}

h2 {
.brand-colors({
background-color: @color;
});
}

.brand-colors(@style) {
.brand-color(default);
.brand-color(africa);
.brand-color(nz);
}

.brand-color(@name) {
.brand-@{name} & {
@value: ~'brand-@{name}';
@color: @@value;
@style();
}
}

Or even like this:

.brand(default) {@{color}: #649d84}
.brand(africa) {@{color}: #df6f20}
.brand(nz) {@{color}: #444444}

h1 {
.brand-colors();
}

h2 {
.brand-colors(background-color);
}

.brand-colors(@color: color) {
.-(default);
.-(africa);
.-(nz);

.-(@name) {
.brand-@{name} & {
.brand(@name);
}
}
}

Or something in between. Or... oh wait, there's whole family of methods for this stuff (incl. various combinations), see for example:

  • https://stackoverflow.com/a/23660124
  • How to thematize in lesscss
  • https://stackoverflow.com/a/20072967
  • etc.

Usually list/array/loop based methods are more compact, though personally I prefer something dumb like this:

.themed({

h1 {
color: @color;
}

h2 {
background-color: @color;
}

});

.themed(@styles) {
.-(default, #649d84);
.-(africa, #df6f20);
.-(nz, #444444);

.-(@name, @color) {
.brand-@{name} {
@styles();
}
}
}

Defining Variable Variables using LESS CSS

Use interpolation and escaping, parentheses in the selector and parametric mixins to get the desired effect:

  • Dynamic variables by interpolation: In a string, "@{variable}" is replaced with the value of the variable. They can also be nested: Given @{@{var}-foo} and @var: bar;, the result is "barfoo".

    The resulting value is quoted. To remove these quotes, prefix ~.
  • Dynamic selectors by Selector interpolation: body.@{var} turns into body.bar.

Example:

@red-md:   #232;
@red-dk: #343;

.setColor(@color) {
body.@{color} { background-color: ~"@{@{color}-dk}";
#container { background-color: ~"@{@{color}-md}";
p { color: ~"@{@{color}-md}"; }
}
}
}
.setColor(~"red"); // Escape to prevent "red" turning "#FF0000"
//.setColor(~"blue"); etc..

Turns into:

body.red {
background-color: #334433;
}
body.red #container {
background-color: #223322;
}
body.red #container p {
color: #223322;
}

Note: When the answer was originally written, selector interpolation did not exist. See the previous revision for the solution if you're working with an old LESS compiler (before LESS 1.3.1a). Support for the old method will be dropped in LESS 1.4.0.

Change SASS or LESS variable value at runtime

If you're targeting only users with modern browsers, CSS has experimental variable support (see Can I use.. data). Here's an example of from MDN:

:root {
--main-bg-color: pink;
}

body {
background-color: var(--main-bg-color);
}

If you took this approach, you would only have to insert the colour variable into a simple stylesheet, and CSS would do the rest:

<style>
:root {
--group-colour: {{ groupColour }}
}
</style>
<link rel="stylesheet" href="/path/to/stylesheet/with/variables.css">

For more information on CSS variables, see MDN's CSS var() documentation.



Related Topics



Leave a reply



Submit