24

In bootstrap 4 you can define and/or override default colors using the following array: (as documented here)

$theme-colors: (
    "primary": #001122, /*override*/
    "color-1": red, /*new color*/
    "color-2": black, /*new color*/
    "color-3": white /*new color*/
);

I want to make custom theme colors for different users. Users can choose different color palettes which I have defined based on the class of the body.

I made a new file to make my own styles, defining my theme colors in CSS variables in the body tag:

body {
  &.color-pallette-red {
    --theme-color-1: red;
    --theme-color-2: black;
    --theme-color-3: white;

    color: var(--theme-color-1); /*red*/
  }

  &.color-pallette-yellow {
    --theme-color-1: yellow;
    --theme-color-2: black;
    --theme-color-3: white;

    color: var(--theme-color-1); /*yellow*/
  }
}

But in this way, the bootstrap colors are of course not overwritten...

I tried to paste my CSS variables in the bootstrap array mentioned earlier, to make sure that bootstrap used my colors:

$theme-colors: (
    "color-1": var(--theme-color-1),
    "color-2": var(--theme-color-2),
    "color-3": var(--theme-color-3)
);

But this returns Error: $color2: var(--theme-color-1) is not a color. @return mix($color-base, $color, $level * $theme-color-interval); after running the compiling script. So bootstrap/compiler didn't recognize my CSS variables as a color...

Summarized what I want is: Change bootstrap colors based on body class. Does anyone know a way to achieve this?

Dirk Jan
  • 2,355
  • 3
  • 20
  • 38
  • Provide a [mcve] so we can see how it doesn't work. – Asons Feb 11 '19 at 17:10
  • did you try `"color-1":#{var(--theme-color-1)}` – Temani Afif Feb 15 '19 at 15:44
  • bootstrap variables are sass based, it means they'll be generated at build time. So you can't inject css variables into it. All you can do is include whole bootstrap multiple times with different variables inside root classes. – Sasan Farrokh Feb 15 '19 at 22:25
  • @TemaniAfif I tried that but this gives the same error. – Dirk Jan Feb 18 '19 at 08:54
  • @Sasan Farrokh I thought about that solution but it is much less maintainable... – Dirk Jan Feb 18 '19 at 08:54
  • There is a proposal to move expressions outside from SCSS styles and allow to set it by separate variables https://github.com/twbs/bootstrap/issues/31538 , e.g. you can see a PR which shows how to enable CSS variables mode https://github.com/twbs/bootstrap/pull/31753 – Dmitry Yaremenko Sep 28 '20 at 07:14

3 Answers3

8

You can't set CSS variables in the maps directly, because that variable would not be compiled at the time as SASS is a preprocessor, additionally bootstrap turns around and converts those theme KVPs into :root variables anyways, so you're essentially doing the same thing twice. One way around this is you can just tap into the map itself via generic SASS methods:

  ...
  property: map-get(map-merge($theme-colors,($key : $color)),$key)
  ...

Once you have set that particular KVP, it will then be available globally to use elsewhere.

At this point, it's arbitrary to use CSS var(), but just to cover all my bases, you can set the theme, and then alter the variable, as opposed to re-setting & calling theme-color("color-2").

A full example of this would be the following:

@import "bootstrap";

$theme-colors: (
    "primary": #001122,
    "color-1": black, 
    "color-2": black, 
    "color-3": black 
);

@function set-theme-color($key, $color) {
  @return map-get(map-merge($theme-colors,($key : $color)),$key);
}

@mixin theme($color1, $color2, $color3) {
  background-color: set-theme-color("color-1", $color1);

  h1 {
    color: set-theme-color("color-2", $color2);
  }
  h2 {
    color: set-theme-color("color-3", $color3);
  }
}

main {
  &.theme-purple {
    @include theme(purple,cornflowerblue,white);
    /* example demonstrating color3 can change */
    --color-3: orange;
  }

  &.theme-red {
    @include theme(red,black,white);
  }
}

h2 { 
  color: theme-color("color-2"); 
}
h3 {
/* example demonstrating color3 changed */
  color: var(--color-3);
}

<script src="http://codeply.com/js/embed.js"></script><div data-codeply="o9n2ST6HW5" ></div>

Note, the example also demonstrates if one theme sets the variable and others don't. The theme color you choose is invalid and the property reverts to inherit.

codeply demo

soulshined
  • 9,612
  • 5
  • 44
  • 79
  • Hi @soulshined, thank you for the answer. But how can I change the bootstrap colors based on the class of the body in your example? – Dirk Jan Feb 20 '19 at 07:34
  • So let's say I call "set-theme-color" 20 times, the bootstrap variables are redefined 20 times. Suppose I call a bootstrap color after this action, how do I know which color that is? – Dirk Jan Feb 22 '19 at 09:54
  • @djl Well assuming you call a bootstrap color after this, you’d know it was the last rule set applied based on the scope is the short answer. variables are scope driven. The `orange` color demonstrates how you would know. In my example you know it’s orange because you’ve set it either before, or with the `h3` element. But even if you set it in a different color for the red theme it would be independent of what the purple theme wants because that’s just how cascading style sheets work. – soulshined Feb 22 '19 at 12:01
6

Why you cannot use CSS variables for colors in SASS

The reason you are not able to use CSS variables for colors are functions like darken() and lighten() in SASS. You can imagine that the computer will not know what to do when it gets the instruction: darken( var(--color), 20%); You cannot modify a value you don't know about.

What if we just don't use functions that change colors?

In fact that should actually work. The bootstrap library has scss-files that use those functions. You could go through all the bootstrap code and remove the functions that modify colors. Of course that would be a bit of work. Updating bootstrap in the future would cause your site's styling to mysteriously crash and someone will be very mad at you. So this option is not a good idea.

But...But... If CSS can do it you can with SASS?

YES! This is the answer. And it's going to be quite a bit of work. If you set the colors using another variable, you can use a SASS variable like --color for those colors. E.g.:

  $color1: var(--color-1);

  /* It may seem completely redundant to  
     to define CSS variable here. But 
     it isn't. You can change the variables in 
     your browser now with JavaScript, which 
     may affect multiple elements.
  */
  :root{
    --color-1: purple;
  }
  /* An example on how to change the page
     styling with an id.        
  */
  :root#dark-mode{
    --color-1: black;
  }

  .alert-success{
    background-color: $color1;
  }
  .alert-danger{
    background-color: $color1;
  }

Luckily the bootstrap 4 developers have done a pretty good job at keeping their selectors simple, so it won't be too hard to overwrite. Remember to load your CSS after the bootstrap css. You may want to search the bootstrap scss files for the SASS variable you are overriding to make sure you cover all cases.

Goodluck!

Rob Monhemius
  • 4,822
  • 2
  • 17
  • 49
  • Thank you for the good explanation. The error could indeed have something to do with the color functions. Because I know what elements are being used on the website I will use this solution for now, although I don't like the idea of manually overwriting everything... – Dirk Jan Feb 22 '19 at 10:01
1

I like my other answer, but I would also like to offer another solution, provided that one doesn't fit the project scopes needs. Another option, and I think more direct to your needs, is you could just cascade your style sheets. Assuming a project tree resembles the following:

`css\
  `--main.css
  `--themes.css <- compiled themes.sass
  `--themes.sass
`index.html

Where index.html:

  ...
  <head>
    <link rel="stylesheet" type="text/css" media="screen" href="css/main.css">
    <link rel="stylesheet" type="text/css" media="screen" href="css/themes.css">
  </head>

  <body>
    <main class="theme-purple">
      <h1>Hello World</h1>
      <h2>SASS is fun!</h2>
    </main>

    <main class="theme-red">
      <h1>Hello World</h1>
      <h2>SASS is fun!</h2>
    </main>
  </body>
  ...

Where main.css:

.theme-red {
  --theme-color-1: red;
  --theme-color-2: black;
  --theme-color-3: white;
}
.theme-purple {
  --theme-color-1: purple;
  --theme-color-2: cornflowerblue;
  --theme-color-3: white;
}

Where themes.sass:

@import "node_modules/bootstrap/scss/bootstrap";

$theme-colors: (
  "primary": #001122, 
  "color-1": var(--theme-color-1), 
  "color-2": var(--theme-color-2), 
  "color-3": var(--theme-color-3)
);

main { 
  background-color: theme-color("color-1");
}
h1 { 
  color: theme-color("color-2");
}
h2 { 
  color: theme-color("color-3")
}

That way your CSS variables have had a chance to be compiled. I tested on a node.js server, so you should change the import statement as needed

soulshined
  • 9,612
  • 5
  • 44
  • 79
  • This is actually what I had in mind. But as @RMo says, this gives errors when functions such as darken () are used. – Dirk Jan Feb 22 '19 at 10:03
  • There is a proposal to move expressions outside from SCSS styles and allow to set it by separate variables https://github.com/twbs/bootstrap/issues/31538 , e.g. you can see a PR which shows how to enable CSS variables mode https://github.com/twbs/bootstrap/pull/31753 – Dmitry Yaremenko Sep 28 '20 at 07:13