50

I’ve got a website that’s using a few different ‘main’ colors. The general HTML layout stays the same, only the colors change depending on the content.

I was wondering if I could set a color variable depending on the CSS selector. This way I can theme my website with a few variables and let Sass fill in the colors.

For example:

$color-1: #444;
$color-2: #555;
$color-3: #666;
$color-4: #777;

body.class-1 {
  color-default: $color-1;
  color-main: $color-2;
}
body.class-2 {
  color-default: $color-3;
  color-main: $color-4;
}

/* content CSS */
.content {
  background: $color-default;
  color: $color-main;
}

I was thinking of using a mixin for this, but I was wondering if there’s a better way to do this—with a function maybe? I’m not that great with Sass, so any help would be appreciated.

Rory O'Kane
  • 29,210
  • 11
  • 96
  • 131
Jrn
  • 1,185
  • 1
  • 13
  • 27
  • 3
    However you do this, I know variables aren't the answer. Variables are set at compile-time, when the Sass is being turned into CSS. Thus, all your `.content` will have the same colors if they have a variable as the value. – Rory O'Kane Aug 07 '13 at 19:37

8 Answers8

42

I think a mixin is the answer. (As I wrote, variables won’t work.)

@mixin content($color-default, $color-main) {
  background: $color-default;
  color: $color-main;
}

body.class-1 {
  @include content(#444, #555);
}

body.class-2 {
  @include content(#666, #777);
}

That SCSS compiles to this CSS:

body.class-1 {
  background: #444444;
  color: #555555; }

body.class-2 {
  background: #666666;
  color: #777777; }

If you wanted to group the color values together in your SCSS file, you could use variables in conjunction with the mixin:

$color-1: #444;
$color-2: #555;
$color-3: #666;
$color-4: #777;

body.class-1 {
  @include content($color-1, $color-2);
}

body.class-2 {
  @include content($color-3, $color-4);
}
Community
  • 1
  • 1
Rory O'Kane
  • 29,210
  • 11
  • 96
  • 131
  • Allright, I was afraid I had to do it like this. Thanks for the help, appreciated. – Jrn Aug 07 '13 at 20:10
  • 3
    Is there a chance to change 'global' variable by class ? $color: #666; body.class-1 { $color : #fff; } p { $color; } – fearis Nov 19 '15 at 17:32
  • 2020: Sass variables still won't work alone, but you may put a css variable inside a sass variable and have it change depending of body classes, thus achieving OP's needs. see my answer : https://stackoverflow.com/a/62873203/1589851 – Thony Sep 16 '20 at 09:11
30

as sass documentation explain nicely (https://sass-lang.com/documentation/variables):

  • Sass variables are all compiled away by Sass. CSS variables are included in the CSS output.

  • CSS variables can have different values for different elements, but Sass variables only have one value at a time.

  • Sass variables are imperative, which means if you use a variable and then change its value, the earlier use will stay the same. CSS variables are declarative, which means if you change the value, it’ll affect both earlier uses and later uses.

We may take advantage of that using a combination of sass and css variables to achieve what you want:

//theme colors
$red-cosmo: #e01019;
$green-cosmo: #00c398;
$primary-color: var(--primary-color);
body{
  --primary-color: #{$red-cosmo};
}
body.univers-ride{
  --primary-color: #{$green-cosmo};
}

So when I call my sass variable $primary-color, it will print as my css variable "var(--primary-color)" that will expand as $green-cosmo only if my body has the "univers-ride" class else it will be $red-cosmo the default color.

Thony
  • 2,300
  • 1
  • 20
  • 20
  • 4
    This is definitely the best answer. Mixins works fine, but this is exactly as OP wanted it. – Rvervuurt Jun 08 '21 at 08:26
  • The only problem with this approach is that you can't use something like background-color: darken($primary-color, 10%), because technically $primary-color is not a color. – Alexandre Martini Nov 21 '21 at 06:12
  • 1
    @AlexandreMartini I guess you could use another css trick to circumvent that, like to simulate darken => ``filter: brightness(0.x);`` or you could darken it before assigning the color to a css variable – Thony Nov 30 '21 at 16:21
  • Thanks, @Thony. This a great solution IMO. – Mo1 Mar 03 '22 at 15:53
14

If you really want to get hacky you could also define your different color schemes in a single variable like $scheme1: class1 #333 #444, where the first value is always the name, and that is followed by all the colors in that scheme.

You can then use @each:

// Define your schemes with a name and colors
$scheme1: class1 #444 #555;
$scheme2: class2 #666 #777;
$scheme3: class4 #888 #999;

// Here are your color schemes
$schemes: $scheme1 $scheme2 $scheme3;

@each $scheme in $schemes {
  // Here are the rules specific to the colors in the theme
  body.#{nth($scheme, 1)} .content {
    background-color: nth($scheme, 2);
    color: nth($scheme, 3);
  }
}

This will compile to:

body.class1 .content {
  background-color: #444444;
  color: #555555; }

body.class2 .content {
  background-color: #666666;
  color: #777777; }

body.class4 .content {
  background-color: #888888;
  color: #999999; }

Obviously if you don't want to combine body.class1 and .content in your selectors, you could just specify a mixin content($main, $default) and call it inside the @each using nth just like in the above code, but the point is you don't have to write out a rule for each of your classes.

EDIT There are lots of interesting answers on Creating or referencing variables dynamically in Sass and Merge string and variable to a variable with SASS.

Community
  • 1
  • 1
Elise
  • 5,086
  • 4
  • 36
  • 51
  • Thanks for your answer and the links, both very intresting read. Appreciated. – Jrn Aug 09 '13 at 07:53
  • Thank you very much. I had the daunting task of applying eight different colour schemes to the same page, and has saved me a lot of time. Hacky, but it works. Cheers – iamdash Feb 19 '14 at 23:20
2

You can also create a mixing that use the ampersand parent selector. http://codepen.io/juhov/pen/gbmbWJ

@mixin color {
  body.blue & {
    background: blue;
  }
  body.yellow & {
    background: yellow;
  }
}
2

UPDATE: its 2017 and variables does works!

@mixin word_font($page) {
  @font-face {
    font-family: p#{$page};
    src: url('../../static/fonts/ttf/#{$page}.ttf') format('truetype');
    font-weight: normal;
    font-style: normal;
  }

  .p#{$page} {
   font-family: p#{$page};
  }
}

// Loop and define css classes 
@for $i from 1 through 604 {
 @include word_font($i);
}
Naveed
  • 11,057
  • 2
  • 44
  • 63
  • 2
    this is about colors and not about fonts. So not relevant for this question. – Sato Jan 26 '17 at 10:47
  • 1
    @Sato question is about using variable to set values of css properties, color or fonts or whatever. – Naveed Jan 27 '17 at 14:19
  • Valid, but still some more documentation about your suggested answer would be welcome – Sato Jan 30 '17 at 10:09
  • 1
    Actually the question is about using variables outside of their scope. Which I presume would be possible using the `!global` flag (added March 2014). Edit: Nope, it is not. – Samuel Willems Mar 21 '18 at 12:15
1

If you don't want to use a variable for each color, you can use one variable for all kinds of colors. In the mixin you can choose the right color with nth. For instance, if you write the index of the color as 1, then you get the first color in the color variable.

$colors: #444, #555, #666, #777;

@mixin content($color-default-num, $color-main-num) {
  background: nth($colors, $color-default-num);
  color: nth($colors, $color-main-num);
}

body.class-1 {
  @include content(1, 2);
}
Rory O'Kane
  • 29,210
  • 11
  • 96
  • 131
Tim Hartmann
  • 101
  • 1
  • 1
  • 8
  • 1
    What developer is going to remember what order their colors are in? This is an extremely impractical solution. – cimmanon Aug 08 '13 at 11:33
  • 1
    @cimmanon Right. But if you think forward you can now use a for-loop to create classes for each color :) I wanted to show only one way which is possible – Tim Hartmann Feb 14 '14 at 07:19
1

For me the definite answer to my problem was creating a map of maps and loopig through them as follows:

$pallettes: (
  light-theme: (
    container-color: red,
    inner-color: blue,
  ),
  dark-theme: (
    container-color: black,
    inner-color: gray,
  ),
);

@each $pallette, $content in $pallettes {
  .main.#{$pallette} {
    background-color: map-get($content, container-color);
    .inner-div {
      background-color: map-get($content, inner-color);
    }
  }
}
Francesco Meli
  • 2,484
  • 2
  • 21
  • 52
-1

You can simply override your scss variables inside of the class wrapper:

$color1: red;
$color2: yellow;

header { background: $color1; }

.override-class {
  $color1: green;
  header { background: $color1; }
}

Seems to work for me.

  • 3
    This just overrides `$color1` unconditionally from that point. If it works for you, I guess it is coincidental. – Koen Aug 27 '16 at 13:09