DEV Community

Cover image for CSS Quickies: CSS Variables - Or how you create a ๐ŸŒžwhite/๐ŸŒ‘dark theme easily
Michael "lampe" Lazarski
Michael "lampe" Lazarski

Posted on

CSS Quickies: CSS Variables - Or how you create a ๐ŸŒžwhite/๐ŸŒ‘dark theme easily

What is CSS Quickes?

I started to ask my beloved community on Instagram: "what CSS properties are confusing for you?"

In "CSS Quickies" I will explain one CSS property in depth. These are community requested properties. If you also confused about a CSS property, then write to me on Instagram or Twitter or down below in the comments! I answer all questions.

I'm also live streaming while coding on twitch.tv if you want to spend some fun time or you can ask me any question!

Let's talk about Custom properties aka CSS Variables.

Finally! If you ever have worked with CSS and wanted to keep your design consistent? Or was it more like at some pages, your website had different padding, margin or colors?

Maybe you wanted to implement a dark theme? It was possible, but now it has become easier!

Of course, if you have used LESS or SASS, then you know variables, and now they are finally supported natively. ๐Ÿ˜

Let's have a look at it!

Defining a CSS variable

You define a CSS variable with prefixing a CSS property with --. Let's look at some examples.

:root{
  --example-color: #ccc;
  --example-align: left;
  --example-shadow: 10px 10px 5px 0px rgba(0,0,0,0.75);
}

Your first question is: "What is that ':root' pseudo-class?".
Good question! The :root pseudo-class is as you would use the html selector except that the specificity is higher of the ':root' pseudo-class. This means that if you set properties in the :root pseudo-class it will win over the html selector.

Okay, the rest is pretty simple. The custom property --example-color has the value of #ccc. When we use the custom property, for example, on the background-color property, the background of that element will be a light gray. Cool right?

You can give the custom property, aka CSS variable every value you could give any other property in CSS. It is okay to use left for example or 10px and so on.

Using CSS variables

Now that we know how to set CSS variables, we need to learn how to use them!

For this, we need to learn the var() function.
The var() can have two arguments. The first argument needs to be a custom property. If the custom property is not valid, you want to have a fallback value. To achieve this, you simply need to set the second argument of the var() function. Let's look at an example.

:root{
  --example-color: #ccc;
}

.someElement {
  background-color: var(--example-color, #d1d1d1);
}

This should be now pretty straightforward for you to understand. We are setting the --example-color to #ccc and then we are using it in .someElement to set the background color. If something goes wrong and our --example-color is not valid, we have a fallback value of #d1d1d1.

What happens if you don't set a fallback value and your custom variable is invalid? The browser then will act as if this property was not specified and do its regular job.

Tips and tricks

Multiple fallback values

What if you want to have multiple fallback values? So you would think you could do the following:

.someElement {
  background-color: var(--first-color, --second-color, white);
}

This will not work. After the first comma var() treats everything even the commas as a value. The browser would change this into background-color: --second-color, white;. This is not what we want.

To have multiple values, we can simply call var() inside a var(). Here comes an example:

.someElement {
  background-color: var(--first-color, var(--second-color, white));
}

Now this would produce our desired outcome. When both --first-color and --second-color are invalid then the browser will set the background to white.

What if my fallback value needs a comma?

What to do if for example, you want to set a font-family in in the fallback value and you need to specify more then one font? Looking back at the tip before this should be now straight forward. We simply write it with the commas. Example time:

.someElement {
    font-family: var(--main-font, "lucida grande" , tahoma, Arial);
}

Here we can see the after the first comma the var() function treats everything like one value.

Setting and getting custom properties in Javascript

In more complex apps and websites, you will javascript for state management and rendering. You also can get and set custom properties with javascript. Here is how you can do it:

    const element = document.querySelector('.someElement');
   // Get the custom propety
    element.style.getPropertyValue("--first-color");
   // Set a custom propety
   element.style.setProperty("--my-color", "#ccc");

We can get and set the custom properties like any other property. Isn't that cool?

Making a theme switcher with custom variables

Let's first have a look at what we will do here:

The HTML markup
<div class="grid theme-container">
  <div class="content">
    <div class="demo">
      <label class="switch">
        <input type="checkbox" class="theme-switcher">
        <span class="slider round"></span>
      </label>
    </div>
  </div>
</div>

Really nothing special here.
We will use CSS grid to center the content that's why we have a .grid class on our first element the .content and .demo Classes are just for styling. The two crucial classes here are .theme-container and .theme.switcher.

The Javascript code
const checkbox = document.querySelector(".theme-switcher");

checkbox.addEventListener("change", function() {
  const themeContainer = document.querySelector(".theme-container");
  if (themeContainer && this.checked) {
    themeContainer.classList.add("light");
  } else {
    themeContainer.classList.remove("light");
  }
});

First we are selecting our .theme-switcher input and the .theme-container element.
Then we are adding an event listener that listens if a change happens. This means that every time you click on the input, the callback for that event listener will run.
In the if clause we are checking if there is a .themeContainer and if the checkbox input is checked.
When this check is true, we are adding the .light class to the .themeContainer and if it is false, we are removing it.

Why are we removing and adding the .light Class? We will answer this now.

The CSS code

Since this code is lengthy, I will show it to you step by step!

.grid {
  display: grid;
  justify-items: center;
  align-content: center;
  height: 100vh;
  width: 100vw;
}

Lets first center our content. We are doing this with css grid. We will cover the grid feature in another CSS quickies!

:root {
  /* light */
  --c-light-background: linear-gradient(-225deg, #E3FDF5 0%, #FFE6FA 100%);
  --c-light-checkbox: #fce100;
  /* dark */
  --c-dark-background:linear-gradient(to bottom, rgba(255,255,255,0.15) 0%, rgba(0,0,0,0.15) 100%), radial-gradient(at top center, rgba(255,255,255,0.40) 0%, rgba(0,0,0,0.40) 120%) #989898; 
  --c-dark-checkbox: #757575;
}

This is a lot of code and numbers but actually we are not doing much here. We are preparing our custom properties to be used for our theme. --c-dark- and --c-light- is what I have chosen to prefix my custom properties. We have defined a light and a dark theme here. For our example we just need the checkbox color and the background property which is a gradient in our demo.

.theme-container {
  --c-background: var(--c-dark-background);
  --c-checkbox: var(--c-dark-checkbox);
  background: var(--c-background);
  background-blend-mode: multiply,multiply;
  transition: 0.4s;
}
.theme-container.light {
  --c-background: var(--c-light-background);
  --c-checkbox: var(--c-light-checkbox);
  background: var(--c-background);
}

Here comes an integral part of the code. We now see why we named the .theme-container How we did. It is our entrance to have now global custom variables. We don't want to use the specific custom variables. What we want is to use global custom variables. This is why we are setting --c-background. From now on, we will only use our global custom variables. Then we are setting the background.

.demo {
  font-size: 32px;
}

/* The switch - the box around the slider */
.switch {
  position: relative;
  display: inline-block;
  width: 60px;
  height: 34px;
}

/* Hide default HTML checkbox */
.switch .theme-switcher {
  opacity: 0;
  width: 0;
  height: 0;
}

This is just some boilerplate code to set our style. In the .demo selector, we are setting the font-size. This is the size of our symbols for the toggle. In the .switch selector the height and width is how long and wide the element behind our toggle symbol is.

/* The slider */
.slider {
  position: absolute;
  cursor: pointer;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: var(--c-checkbox);
  transition: 0.4s;
}

.slider:before {
  position: absolute;
  content: "๐ŸŒ‘";
  height: 0px;
  width: 0px;
  left: -10px;
  top: 16px;
  line-height: 0px;
  transition: 0.4s;
}

.theme-switcher:checked + .slider:before {
  left: 4px;
  content: "๐ŸŒž";
  transform: translateX(26px);
}

Here we can finally see our custom properties in action besides using them directly in the .theme.container and again a lot of boilerplate code. As you can see, the toggle symbols are simple Unicode symbols. This is why the toggle will look different on every OS and mobile phone vendor. You have to keep this in mind. Important to know here is that in the .slider:before Selector, we are moving our symbol around with the left and top properties. We are doing that also in the .theme-switcher:checked + .slider:before but only with the left property.

/* Rounded sliders */
.slider.round {
  border-radius: 34px;
}

This again is just for styling. To make our switch rounded on the corners.

That is it! We now have a theme switcher which is extendable. โœŒ๐Ÿ˜€

It would help me if you could do the following for me!
Go to Twitch and leave a follow for me! If just a few people would do that, then this would mean the world to me! โคโคโค๐Ÿ˜Š

๐Ÿ‘‹Say Hallo! Instagram | Twitter | LinkedIn | Medium | Twitch | YouTube

Top comments (25)

Collapse
 
chrisachard profile image
Chris Achard

YES! I first ran across native CSS variables about a month ago, and was super excited - finally variables come to CSS without a preprocessor! This post is a nice example of where they can be used. Thanks!

(for those curious, here's where you can check what browsers support css variables: caniuse.com/#feat=css-variables)

Collapse
 
nuxodin profile image
Tobias Buschor

And for IE11 i made this polyfill (linked on caniuse)
github.com/nuxodin/ie11CustomPrope...

Collapse
 
chrisachard profile image
Chris Achard

Oh, nice!

Thread Thread
 
nuxodin profile image
Tobias Buschor

Thanx.
Here the Demo in IE11 with just adding the polyfill:
jsbin.com/pofetiyaha/1/edit

Collapse
 
lampewebdev profile image
Michael "lampe" Lazarski

Nice I will check it out later ๐Ÿ‘๐Ÿ˜Š

Collapse
 
lampewebdev profile image
Michael "lampe" Lazarski

They are really awesome!

Oh, good point! I should have included the caniuse link! They are for me just so integral and normal to use that I don't even think about that there can be some issues with older browser ๐Ÿ™ˆ

Collapse
 
nickytonline profile image
Nick Taylor

Even DEV uses CSS variables for theming. ๐Ÿ”ฅ It goes through SASS, but there is a mixin for generating theme styles via CSS properties.

Chameleon

Collapse
 
ggenya132 profile image
Eugene Vedensky

That's a cute mixin.

Collapse
 
lampewebdev profile image
Michael "lampe" Lazarski

Oh cool, I didn't know that :)

I really need to look some time into the dev.to code ๐Ÿ˜€๐Ÿ––

But yeah I think SASS and LESS have some sort of plugins that let you write CSS variables :)

Collapse
 
simoncoudeville profile image
Simon Coudeville

Great article! I'm adding this to my students reading list this year. In your demo I don't think you need to set the background of .theme-container.light to var(--c-background) again. Or am I missing something?

Collapse
 
lampewebdev profile image
Michael "lampe" Lazarski • Edited

I think you are talking about this example:

.theme-container.light {
  --c-background: var(--c-light-background);
  --c-checkbox: var(--c-light-checkbox);
  background: var(--c-background);
}

So this is the .theme-container and yeah here it looks confusing but imagines you have a bigger app and want to use the right background again.

.theme.container .button {
  background: var(--c-background);
}

As you can see here you would use --c-background because you have set it up in the container with --c-background: var(--c-light-background);

This makes the code more extendable. Think now you would have a 3 or 4 or 5 theme you would only need to change the code in one place. .theme-container.<THEME NAME>.

so you can have a solarized theme or a pink theme and so on.

Collapse
 
simoncoudeville profile image
Simon Coudeville

Yeah I get that. I'm talking about the background property on .theme-container.light. I commented out to show what I mean.

.theme-container {
  --c-background: var(--c-dark-background);
  --c-checkbox: var(--c-dark-checkbox);
  background: var(--c-background);
  background-blend-mode: multiply,multiply;
  transition: 0.4s;
}
.theme-container.light {
  --c-background: var(--c-light-background);
  --c-checkbox: var(--c-light-checkbox);
  /* background: var(--c-background); */
  /* I don't think this rule is necessary since you already set it on the main .theme-container. */
}
Thread Thread
 
lampewebdev profile image
Michael "lampe" Lazarski

Ahh okay!

Now I understand.

There is no technical reason for that.

It is just to make it more clear to other people what is going on here.

Collapse
 
shamimahossai13 profile image
Shamima Hossain

Thank you so much, I was recently struggling to make a switch like that and just couldn't wrap my head around making one. I would like to implement your approach to creating one for my project heavily inspired from this if you don't mind. Also I'm glad I learnt about CSS variables along the way.

Collapse
 
lampewebdev profile image
Michael "lampe" Lazarski

Hey glad i could help!

Of course, you can use it :)

And drop a link here in the comments to your project so I and other people can see it โœŒ๐Ÿ˜€

Collapse
 
academyaquarius profile image
Aquarius.Academy

Awesome!! So happy this is native now! Could use a proofread for email grammar, but good article.

Collapse
 
lampewebdev profile image
Michael "lampe" Lazarski

Thanks.๐Ÿ‘๐Ÿ˜Š

BTW I used grammerly pro for checking ๐Ÿ˜‰

Collapse
 
academyaquarius profile image
Aquarius.Academy

"I will explain one CSS property in deep." You mean to say "depth" in place of "deep".

"Now this would produce our desired outcom." The word is "Outcome", outcom isn't a word.

"Puh this is a lot of code" I'm not sure "puh" is a word.

There was at least one more too.

Thread Thread
 
lampewebdev profile image
Michael "lampe" Lazarski

Thanks, I changed it.

"puh" is a thing in Germany :D

I was thinking that Grammarly would find stuff like that.

Thread Thread
 
academyaquarius profile image
Aquarius.Academy

I would too! Glad to help, thanks again for the article!! Awesome news!!!!

Thread Thread
 
lampewebdev profile image
Michael "lampe" Lazarski

Thank you ๐Ÿ‘ โ˜บ๏ธ

Collapse
 
tiranorod profile image
tiranorod • Edited

I still didn't get why set other custom properties inside .theme-container and .theme-container.light... wouldn't be easier to use the custom properties in the :root throughout your stylesheet or did the local custom properties of those divs had another function that I didn't get? Nice post! Keep tem coming! I believe you'll need more than one quickie to talk about grid though lol cheers!

Collapse
 
dbarwikowski profile image
Daniel Barwikowski

What is .slider.round:before style for?
For me page looks the same with and without it

Collapse
 
prateekarora profile image
Prateek Arora

great work

Collapse
 
lampewebdev profile image
Michael "lampe" Lazarski

Thank you, much appreciated.

Have a great week!