CSS Theme Switcher with VueJS
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:
- In a Javascript file, we’ll create a Vue object.
- In the Vue object, we’ll create a list of themes.
- In this HTML page we’ve been building, we’ll loop through the list of themes to create our four radio inputs.
- 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, thetheme
value becomes the next item in the Vue object’sthemes
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 thev-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 eachinput
. - 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.