DEV Community

loading...
Cover image for Create a custom toggle component in Vue.js. πŸŽ›

Create a custom toggle component in Vue.js. πŸŽ›

vaibhavkhulbe profile image Vaibhav Khulbe ・5 min read

Making a custom component shouldn't be a mess. And what's better than using Vue's native features to create one! So let's do it. Let's make a custom or manual or whatever-you-want-to-call-it component from scratch!

Switch GIF

We have some switches to manufacture now...

What are we making❓

This:

Vue Custom Toggle Demo

I know, I know, it looks terrible, but let's just focus on the implementation :)

Kickstart the custom toggle 🦡

Step 1️⃣: The setup

Fire open your terminal to create a new Vue project:

vue create custom-toggle

If you don't have the Vue CLI installed in your machine, you can find the installation steps on their official website.

Remove all other files and create the new ones just like what you see in the following project structure:

custom-toggle
β”‚
└───public
β”‚   β”‚   index.html
└───src
β”‚   β”‚
β”‚   └───components
β”‚       β”‚   Toggle.vue
β”‚   β”‚   App.vue
β”‚   β”‚   main.js
└───package.json

As you see, our custom component will live in ./components/Toggle.vue file.

Step 2️⃣: Code App.vue

This is our entry point component. As you see, we have two things to display. First, is the toggle component itself and the text below that tells us the state of the toggle, whether it's turned on or off.

Under the <script>, use the data() method to return the default state of the component. Make it a boolean, so it can either be true or false. As I want to display the toggle to be "ON", hence I made it true by default.

Next, register the component which we'll make in the next step using the components option. Here's it's named appToggle. Make sure you import it correctly. Then register it on to the template making sure to use Vue's v-model directive to make two-way data-binding possible. Pass in the dataToggle property so that it can check the state dynamically.

As for the text below, we simply have a <p> tag which uses template syntax's string interpolation.

App.vue:

<template>
  <div>
    <app-toggle v-model="dataToggle"></app-toggle>
    <p style="text-align: center">Toggle: {{ dataToggle }}</p>
  </div>
</template>

<script>
import Toggle from "./components/Toggle";

export default {
  data() {
    return {
      dataToggle: true
    };
  },
  components: {
    appToggle: Toggle
  }
};
</script>

Step 3️⃣: Code the toggle

Open up Toggle.vue file. Here we just need the two div elements (as we're making a custom element) for the "ON" and "OFF" states. These will be wrapped in a parent toggleContainer div.

In order to style them as we want, just give each an id of on and off respectively. The entire CSS styles applied are basic, so here's the styling you need:

.toggleContainer {
  margin-top: 40%;
  display: flex;
  justify-content: center;
  align-items: center;
}

#on,
#off {
  width: 40px;
  height: 20px;
  border-radius: 20px;
  background-color: lightgray;
  padding: 2px;
  text-align: center;
  margin: 10px -5px;
  cursor: pointer;
}

#on:hover,
#on.active {
  background-color: lightgreen;
}

#off:hover,
#off.active {
  background-color: lightcoral;
}

Now comes the best part. In the script we first need to define the props object in order to pass data from App.vue to Toggle.vue. We only need the value of the parent component. This is because the prop we pass will determine which toggle is clicked, the "ON" or the "OFF". It's also useful to add dynamic CSS property changes.

As you can see from the above CSS code we have different background-color and active/hover pseudo-classes for both #on and #off. These are activated from the value prop. But for this thing to work, we need to use Vue's class/style binding feature with v-bind.

To implement this, we can either use v-bind:class or the shorthand syntax i.e. just :class. Here, just make the active object to be value (similar to dataToggle: true) in case of "ON" and the opposite active: !value (similar to dataToggle: false) in "OFF".

But we didn't implement what to do when we actually click on each of these toggle options! Ready for the last step? Here we go!

Add the @click click listener to the two children of the container. Pass on a new method switched() to it as shown:

<template>
  <div class="toggleContainer">
    <div id="on" @click="switched(true)" 
    :class="{active: value}">ON</div>
    <div id="off" @click="switched(false)" 
    :class="{active: !value}">OFF</div>
  </div>
</template>

For the body of this method, we need to emit the new custom event on mouse click. Vue provides us with the $emit syntax for this functionality. The first parameter is the type of custom event, in our case, it's an input and the second parameter is isOn.

Toggle.vue:

<template>
  <div class="toggleContainer">
    <div id="on" @click="switched(true)" 
    :class="{active: value}">ON</div>
    <div id="off" @click="switched(false)" 
    :class="{active: !value}">OFF</div>
  </div>
</template>

<script>
export default {
  props: ["value"],
  methods: {
    switched(isOn) {
      this.$emit("input", isOn);
    }
  }
};
</script>

<style scoped>
.toggleContainer {
  margin-top: 40%;
  display: flex;
  justify-content: center;
  align-items: center;
}

#on,
#off {
  width: 40px;
  height: 20px;
  border-radius: 20px;
  background-color: lightgray;
  padding: 2px;
  text-align: center;
  margin: 10px -5px;
  cursor: pointer;
}

#on:hover,
#on.active {
  background-color: lightgreen;
}

#off:hover,
#off.active {
  background-color: lightcoral;
}
</style>

And you've done it! πŸ₯³ See how the text below the toggle changes from "true" to "false" and back!

You just made a custom toggle with Vue.js in a matter of minutes, that's what I like about this framework. It's really quick to make such components.

If you're stuck somewhere in the code or just want to interact with the output, I've embedded the CodeSandbox project below:


Where to next? πŸ€”

Brush up your custom component making skills with the following resources:


Thanks for reading, I appreciate it! Have a good day. (βœΏβ—•β€Ώβ—•βœΏ)



πŸ“« Subscribe to my weekly developer newsletter πŸ“«

PS: From this year, I've decided to write here on DEV Community. Previously, I wrote on Medium. If anyone wants to take a look at my articles, here's my Medium profile.

Discussion

pic
Editor guide
Collapse
thepassle profile image
Pascal Schilp

This switch is not accessible, and not usable with keyboard navigation. You should really consider reading up on how to make custom components like these accessible before sharing wrong information.

Here are some good resources:
w3.org/TR/wai-aria-1.1/#switch
scottaohara.github.io/aria-switch-...
erikkroes.nl/blog/accessible-html-...

Collapse
jwkicklighter profile image
Jordan Kicklighter

And I would add to this: styling based on an ID is not considered good practice, nor is using an ID like "on" or "off" in a component that will be reused. According to spec, ID should be unique on the whole page.

Collapse
vaibhavkhulbe profile image
Vaibhav Khulbe Author

Hey Jordan, I agree, but this is not a full-fledged application. It's just a demo of how to start writing a custom component.

Also, please provide some external resources to back off your statements, this would help others to understand more.

Thread Thread
jwkicklighter profile image
Jordan Kicklighter

Sure, I recognize that it's just a demo and there may be some merit in just getting things out there for people to see. That said, it's a demo targeted toward a fairly beginner level audience. These subtle behaviors are often not addressed in anything new programmers will learn, and I feel that it's a disservice to allow poor programming habits to show up in an example meant for teaching people. Arguably, you should never be okay with poor practices in any code you write, if they could be easily avoidable; but this problem is even worse when you have an audience that may have never seen any of the techniques and will learn bad habits while trying to understand everything going on in the example.

Short version: it being a demo is precisely the reason you should follow best practices.

Edit: first sentence in this doc developer.mozilla.org/en-US/docs/W...

Thread Thread
thepassle profile image
Pascal Schilp

Exactly β€” this may not be a full-fledged application, but if a new dev comes here, reads this, recreates this toggle with non-unique IDs, and reuse a bunch of that component on their page/app, they will end up with multiple non-unique IDs on their page. Its a bad practice, and a poor example for new developers.

Collapse
vaibhavkhulbe profile image
Vaibhav Khulbe Author

Hi Pascal! You're right accessibility is highly important! But as you can see, this article is just focussed on "how to make a custom component". It's just about coding the component, adding other things like accessibility can surely be done. This was just to get a demo or a quick-start. :)

Also, thanks for adding the resources, they will definitely help!

Collapse
thepassle profile image
Pascal Schilp

Making it not accessible is not how you make a component. Youve just excluded a lot of people from being able to use the component, and even worse, youve taught a bunch of other people the wrong way to make a component.

If its not accessible, its not finished. β€œAdding” accessibility is not a bonus; its a requirement

Collapse
binotaliu profile image
γƒ“γƒŽγ‚Ώ

As another comment said that it is not a11y friendly. I strongly recommend to use input[type=checkbox] and use css to style it. Or, you can put an input[type=checkbox] that only visible to screen readers, then hide your divs on screen readers.

Collapse
vaibhavkhulbe profile image
Vaibhav Khulbe Author

Thanks for the info!

Collapse
abhigya53544714 profile image
Abhigyan Kumar

How did you create that GIF at the topmost?

Collapse
vaibhavkhulbe profile image
Collapse
ayushi7rawat profile image
Ayushi Rawat

Very informative and nicely explained!

Collapse
vaibhavkhulbe profile image
Vaibhav Khulbe Author

Thanks Ayushi! Your comment is highly appreciated. 😊