Building a Theme Switcher with Vue

If you are building a site for a client, you may want to give your client an idea of how different themes look on a particular site, site page, or site element. In this case, it can help to have a demo that allows your client to dynamically switch themes in order to see how they look at a glance and arrive at a decision.

Here, we will look at a way to build a theme switcher with VueJS.

What we will create

The theme switcher, which uses themes from Canva’s website color schemes, allows you to switch themes using radio input elements, which are styled to look like tabs. When you press a tab, Vue works behind the scenes to change the CSS class of the output element. Try clicking the tabs here.

Current Theme:

Sample text is right here. The theme switcher is powered by VueJS.

Now, let’s walk through the process for building this theme switcher from the ground up.

Part 1: Building the basic markup

Step 1: Adding the Vue JS library

Being a library, Vue JS can be baked into your site’s codebase in a number of ways, which I discuss elsewhere. For this particular demo, we are pulling in the VueJS library from a content delivery network (CDN) using a script tag:

<script src="https://npmcdn.com/vue/dist/vue.js"></script>

So integrating VueJS for this project does not require a build step.

If you are building this on a static html page, you should add this to an HTML template with all the usual starting material, such as this SitePoint template. If you are using a code sandbox like CodePen or JS Fiddle, no need.

Step 2: Adding the root element

Assuming the Vue library script tag is already added to our html page, let’s add the main DOM element to our html and give it an ID of root.




If you are trying this on a static html page, it is best to add this inside a <body> element.

Step 3: Adding the theme switcher and output containers

Now, let’s add an element that will contain the list of tabbed theme options.




And now, let’s add the container with an id of output that will contain the elements with the resulting styles.




Step 4: Create a template element that will generate the theme options

Ok, our containers are set up. Time to create our four theme options that the user will click on. Now if this were not a VueJS tutorial, we could just add some theme options using html radio buttons…




… but where is the fun in that? This gets repetitive, especially when we add additional attributes to each input element.

A less repetitive way to make these tabs is to tie our HTML file to a Javascript file with a Vue object. Here’s the plan:

  1. In a Javascript file, we’ll create a Vue object.
  2. In the Vue object, we’ll create a list of themes.
  3. In this HTML page we’ve been building, we’ll loop through the list of themes to create our four radio inputs.
  4. At the end of this project, we will style these inputs to look like tabs.

Before we start our JS file, let’s lay the groundwork for that by adding a template element. The nice thing about a template is that you can add Vue logic to it but it won’t show up in the DOM.




Part 2: Adding the Vue Object

Step 5: Creating the Vue Object

The purpose of the Vue object will be threefold:

  • It stores the list of themes.
  • It stores which theme is selected.
  • It is used to set the default theme at runtime.

Let’s now start our Javascript file and create the Vue object. Our first goal will be to make it hold our themes list.

Remember that by using that handy script tag earlier, we are already pulling in the Vue library, which allows us to create this Vue object and have it work the way it should.

To make the Vue object act on our main root element, we will give it an el property, which we set equal to #root. The el property tells the Vue object which HTML element it should act on.


 let vm = new Vue({
    el:'#root'
  });

Now let’s add a data object, which will hold our list of themes.


 let vm = new Vue({
    el:'#root',
    // add the data object.
    data: {}
  });

The data object is meant to be an expandable list of properties. We can now add a themes property to our data object, which we can define as a list of four theme names.


 let vm = new Vue({
    el:'#root',
    // add the data object.
    data: {
       themes:['dark','modern','light','gemstone']
    }
  });

Okay, so now we have a Vue object with four themes that is wired up to our HTML structure.

Step 6: Iterate through our theme list to create options

Let’s now go back to our HTML file, where we will now use what we call a v-for loop to iterate through those four themes in the above theme list and create our four clickable options. To do this we’ll use v-for with the themes property…




… and use theme as the term for the current theme that gets used for that particular iteration of the loop.

Below you will see the HTML markup and the Javascript side-by-side. Compare the HTML to the Javascript and see if you can make the connection between how the themes from the Vue object are now pulled into the HTML file.





 let vm = new Vue({
    el:'#root',
    // add the data object.
    data: {
       themes:['dark','modern','light','gemstone']
    }
  });


There are four elements in the Vue object’s themes list:


['dark','modern','light','gemstone']

Here is how this list will be used in the HTML:

  • The v-for loop creates the radio inputs needed for switching themes.
  • With each iteration of the v-for loop, the theme value becomes the next item in the Vue object’s themes list.
  • In each iteration, Vue will use whatever the current theme value is to make our radio input element.
  • But for that to happen, we need to add our <input> element inside the v-for loop, which we do below.

In code below, you will see that input element. Notice that we have a colon : before value below, which allows us to reference the theme variable.




Step 7: Wire the input elements to Vue object

So we can generate our list of input elements now, but we ned to make it so that when you select one, it sets the current theme. So let’s create a variable called selected in our Vue object…


 let vm = new Vue({
    el:'#root',
    data: {
      themes:['dark','modern','light','gemstone'],
      // add the 'selected' property here.
      selected:''
    }
  });

…and then add a property called v-model on each input element, which will allow each one to set the theme by way of the selected property.




Demo 1

Here’s what we have so far. Our radio buttons show up, and if you click one, it will appear to be selected. They are not much to look at yet, but we’ll get there.

So far, we cannot tell which radio button is which without labels, so let’s add some.

Step 8: Add an ID and a label to each input option

We will add visible labels for each button using the <label> element, which can be wired to an input and which allows for the custom styling options that are needed to make our radio buttons look like tabs. Here is what you need to know at this point:

  • There is one label for each input.
  • When you click on the label, its corresponding radio button ends up being selected.

To wire each label to its input element, we will give each radio input element an id attribute, which will also be the theme name.

Why do we need the id? Because each label uses its for attribute, which is also the name of the input’s theme, to find the id of its input counterpart.

Here is how we add the labels and id’s. We are using colons before for and id so we can use that Vue goodness to reference the theme property.




Notice also that the label has a v-html property that is set equal to the theme element as well.

<label :for="theme" v-html="theme"></label>

The v-html attribute injects the theme name as visible text into each label element.

Demo 2

Here is how the project should look now.

Still pretty modest, so let’s start adding some style and functionality.

Part 3: Adding the theme switch effect

Step 9: Show the result of selecting a theme

Now that we have some working options, let’s show the result of selecting a theme. We know that selecting an option will set the select variable on the Vue object to that option’s theme name, so now let’s use that select variable to define the class of the output element that will display our content.

But first, we need some content to display! Let’s start by adding an h1 element with some placeholder text. Here is how the output element looks itself with that addition.

    
    
  

To make the output element take on the style of the theme select, let’s set the class of the element equal to whatever theme is in the Vue object’s selected variable using the colon syntax.

    
    
  

To make the name of the theme explicitly show up for the user, let’s also make the <h1> header print the name of the theme using that same “selected” variable.

    
    
  

Here is how it looks with the rest of the markup.




Syntax Moment

Another way to print a Vue variable is with curly brace syntax, as in the snippet below.

    
    
  
 

For this project, I am using v-html="selected" instead of curly braces. This is because curly braces do not work properly when I compile my site with Jekyll, the static site generator I use. This is because Jekyll uses curly braces for other purposes.

That said — if you are not using a system that conflicts with curly braces, you can use use them in place of v-html syntax as in the example above.

Step 10: Add styles for the four themes

Before we test the theme switcher we need themes to switch through, right? So to test basic functionality, let’s go ahead and add styles for the four themes. Here is what we add to our CSS file. You’ll notice that the last selector in each theme is a button element, which we will add shortly to our HTML.

/* dark */
#root .dark {
  background-color: #062f4f;
  color: #fff;
  border: 1px solid #b82601;
  font-family: Arial;
}
#root .dark h1 {
  background-color: #813772;
}

#root .dark button {
  background-color: #b82601;
  color: #fff;
}

/* modern */
#root .modern {
  background-color: #efefef;
  font-family: Verdana;
  color: #606060;
}
#root .modern h1 {
  background-color: #caebf2;
}

#root .modern button {
  background-color: #ff3b3f;
  color: white;
}
/* light */
#root .light {
  background-color: rgba(246,246,246,1);
}
#root .light h1 {
  background-color: #67aeca;
  color: 
}
#root .light p {
  color: #5f0f4e;
}
#root button {
  background-color: #e52a6f;
  color: white;
}
/* gemstone */
#root .gemstone {
  background-color: #efefef;
}
#root .gemstone h1 {
  background-color: #0f6571;
  color: #fff;
}
#root .gemstone p {
  color: #414141;
}
#root .gemstone button {
  background-color: #ff6a5c;
  color: #fff;
}

Step 11: Add some general styles

And since we’re starting to focus on styling, let’s quickly add some general styles to the overall page:


body {
  font-family: Arial;
}
#output {
  min-height: 130px;
  width: 100%;
}
#root h1 {
  font-size: 16px;
  margin: 0px;
  padding: 10px; 
  font-weight: 700;
}

Demo 3

And here is how the theme switcher should look so far. You can see that it basically works, albeit without content beyond the header, and without styles for the tabs.

Current Theme:

Step 12: Add some additional elements that would be styled by the theme.

Now let’s add a <p> tag and a <button> element in the output markup.




Step 13: Set first option, dark, as our default theme.

And back in our Vue object, let’s add a way to set our first option as the default theme. To do this, let’s use the variable name of the Vue object, vm, and set its selected property as the first element in the Vue object’s themes array.


  let vm = new Vue({
    el:'#root',
    data: {
      themes:['dark','modern','light','gemstone'],
      selected:''
    }
   });
   // set the default as the first element.
  vm.selected = vm.themes[0];

Step 14: Add a listener and method for the button clicks

Let’s make something happen when you click that button - so let’s add a methods property to the Vue object. In methods, we’ll define a method called hello that handles the button click event with a simple “hello world!” alert.


  //define a new vue that grabs onto the root element.
  let vm = new Vue({
    el:'#root',
    data: {
      themes:['dark','modern','light','gemstone'],
      selected:''
    },
    methods: {
      hello:function() {
        alert('hello, world!')
      }
    }
   });
   // set the default as the first element.
   vm.selected = vm.themes[0];

And let’s add a click listener on the button using a new v-on attribute.




Step 15: Add remaining styling

We already added general styling and the styling for the four themes. So to round out our styles, let’s add the following:

  • the theme switcher styles
  • a few more general styles for the output

The general styles include those for the <button> and <p> elements.

body {
  font-family: Arial;
}

#output {
  min-height: 130px;
  width: 100%;
}
#root h1 {
  font-size: 16px;
  margin: 0px;
  padding: 10px; 
  font-weight: 700;
}
#root p {
  margin: 0px;
  padding: 10px;
  font-size: 15px;
}
#root button {
  margin: 0px auto;
  display: block;
  border: 0px;
  padding: 8px;
  color:  #000;
  border-radius: 20px;
  cursor: pointer;
}

Now let’s add the style for the switcher. Notice that I’m hiding the input elements and only showing the label elements so I can make the options look like tabs. Since the label acts as a proxy for the input element, clicking the label will automatically mark that theme as selected.


/* theme-switcher */
#theme-switcher {
  margin-bottom: 10px;
  border-bottom: 1px solid darkgrey;
}
/* hide the radio button element */
#theme-switcher input[type="radio"] {
  display: none;
}
/* style the label */
#theme-switcher label {
  padding: 10px;
  background-color: lightgrey;
  text-align: center;
  cursor: pointer;
  display: inline-block;
  margin-top: 0px;
  min-width: 50px;
  border-right: 1px solid darkgrey;
}
#theme-switcher label:last-child {
  border-right: 0px;
}
#theme-switcher input[type="radio"]:checked+label {
  background-color: dodgerblue;
  color: white;
}
 

Here is the HTML, JS, and full CSS all together.





  //define a new vue that grabs onto the root element.
  let vm = new Vue({
    el:'#root',
    data: {
      themes:['dark','modern','light','gemstone'],
      selected:''
    },
    methods: {
      hello:function() {
        alert('hello, world!')
      }
    }
   });
   // set the default as the first element.
   vm.selected = vm.themes[0];

body {
  font-family: Arial;
}

/* hide the radio button element */
#theme-switcher input[type="radio"] {
  display: none;
}
/* style the label */
#theme-switcher label {
  padding: 10px;
  background-color: lightgrey;
  text-align: center;
  cursor: pointer;
  display: inline-block;
  margin-top: 0px;
  min-width: 50px;
  border-right: 1px solid darkgrey;
}
#theme-switcher label:last-child {
  border-right: 0px;
}
#theme-switcher input[type="radio"]:checked+label {
  background-color: dodgerblue;
  color: white;
}
  
/* styles that relate to the theme switcher */
#output {
  min-height: 130px;
  width: 100%;
}
#root h1 {
  font-size: 16px;
  margin: 0px;
  padding: 10px; 
  font-weight: 700;
}
#root p {
  margin: 0px;
  padding: 10px;
  font-size: 15px;
}
#root button {
  margin: 0px auto;
  display: block;
  border: 0px;
  padding: 8px;
  color:  #000;
  border-radius: 20px;
  cursor: pointer;
}
/* themes */
#root .dark {
  background-color: #062f4f;
  color: #fff;
  border: 1px solid #b82601;
  font-family: Arial;
}
#root .dark h1 {
  background-color: #813772;
}

#root .dark button {
  background-color: #b82601;
  color: #fff;
}
/* modern */
#root .modern {
  background-color: #efefef;
  font-family: Verdana;
  color: #606060;
}
#root .modern h1 {
  background-color: #caebf2;
}

#root .modern button {
  background-color: #ff3b3f;
  color: white;
}
/* light */
#root .light {
  background-color: rgba(246,246,246,1);
}
#root .light h1 {
  background-color: #67aeca;
  color: 
}
#root .light p {
  color: #5f0f4e;
}
#root button {
  background-color: #e52a6f;
  color: white;
}
/* gemstone */
#root .gemstone {
  background-color: #efefef;
}
#root .gemstone h1 {
  background-color: #0f6571;
  color: #fff;
}
#root .gemstone p {
  color: #414141;
}
#root .gemstone button {
  background-color: #ff6a5c;
  color: #fff;
}

Final Product

Here is the final product once again.

Current Theme:

Sample text is right here. The theme switcher is powered by VueJS.

Note: While I am making the full versions of the theme switcher show up at both the beginning and end with some additional code behind the scenes, the code presented in this tutorial limits the occurrence of the theme switcher to just once on a given page.

You can also view the demo on this fiddle.

I hope this was a helpful introduction to the use of Vue JS. As you can see, you can put together a cool style switcher with a minimal amount of Javascript. You also end up defining a lot of your logic in your html, which connects it closely to your Vue object without the need for undue repetition.