Create a reusable button with Vue Dynamic Components
Tags: vue
Published: 2019-08-14 10:16:36

---
title: Create a reusable button with Vue Dynamic Components
published: true
tags: vue
cover_image: https://thepracticaldev.s3.amazonaws.com/i/eboiiddjtdan4e3bdon6.jpg
canonical_url: https://www.codegram.com/blog/create-a-reusable-button-with-vue-dynamic-components
---

_Originally published at [Codegram's blog](https://www.codegram.com/blog/create-a-reusable-button-with-vue-dynamic-components)_

If you are building any kind of application, it's very likely you have a `Button` component. You know, a button that can be clicked and multiple things can happen: go to a different page, open a new tab, submit a form… But, even though it can do so many different things, a button should always look like a button, right?

![](https://thepracticaldev.s3.amazonaws.com/i/3md2h5authzmfge6jbb2.png)

In the good old days, we used to style things using classes. We'd have a `.button` class that could be used anywhere in our application and that's it. Easy, right? Nowadays, some people think that global classes are not cool anymore. Since global stuff in JavaScript is evil, we assume that global stuff in CSS is evil too. I'm not going to talk about that today, for the sake of this post let's assume that this is 100% correct and that's how we want to work.

We are using Vue, because Vue is cool, and we create a `Btn` component. Something like this: 

```vue
<template>
  <button class="button">
    <slot/>
  </button>
</template>

<script>
  export default {}
</script>
    
<style scoped>
  .button {
    display: inline-block;
    margin: 0.5em 0;
    padding: 1em 2em;
    background: #fff;
    border: 2px solid tomato;
    border-radius: 3px;
    color: tomato;
    font-family: "Quicksand", sans-serif;
    font-size: 1em;
    font-weight: 700;
    letter-spacing: 0.02em;
    line-height: 1;
    text-decoration: none;
    text-transform: uppercase;
    cursor: pointer;
    transition: 0.3s;
  }
  
  .button:hover {
    background: tomato;
    color: #fff;
  }
</style>
```

So now it can be used anywhere like this: 

```vue
<btn>I'm a Button!</btn>
``` 

_(We use `Btn` instead of `Button` to avoid using the same name as the HTML tag)_ 

### Conditional rendering

But now it's just a button, and we want to use it to link to other pages. Of course, we could capture the click event and change `window.location`, but we want to do things the right way and use a proper link. So we could change our component to render an `a` if we receive an `href` prop:

```vue
<template>
  <div>
    <a v-if="href" :href="href" class="button">
      <slot/>
    </a>
    <button v-else class="button">
      <slot/>
    </button>
  </div>
</template>
    
<script>
  export default {
    props: {
      href: {
        type: String,
        default: null
      }
    }
  }
</script>

```

And that works! But, as you can see, a wild `div` has appeared. That is because a Vue component needs to have a [single root element](https://vuejs.org/v2/guide/components.html#A-Single-Root-Element). We could live with that, I've seen and worked on plenty of projects that use this pattern, and it works just fine for them. But there is a better way!


### Dynamic components to the rescue!

A relatively less popular Vue feature are dynamic components. Vue offers a `<component>` element that has a special `is` attribute that we can use to render different components or elements dynamically. So we can refactor the previous code to get rid of the annoying `div` root element:

```vue
<template>
  <component :is="type" :href="href">
    <slot></slot>
  </component>
</template>
    
<script>
  export default {
    props: {
      href: {
        type: String,
        default: null
      },
      to: {
        type: String,
        default: null
      }
    },
    computed: {
      type() {
        if (this.href) {
          return 'a'
        } else {
          return 'button'
        }
      }
    }
  }
</script>
```

Beautiful! Plus, we avoid having to repeat the `class` attribute, we move the logic to a computed property… overall is much cleaner. And don't worry about adding `href` to a `button`, because if the value is falsy the attribute [won't be rendered](https://vuejs.org/v2/guide/syntax.html#Attributes)

(If you come from React, it will probably feel more natural to you to use a [render function](https://vuejs.org/v2/guide/render-function.html), and that's a perfectly valid solution as well, but I think that dynamic components are a more _vuey_ solution)

With this, we can easily extend our component to also render a `router-link` (or `nuxt-link` if you are using Nuxt). You can check the whole code here:

Of course, dynamic components are very powerful, and you can use them to render your own components too. So many possibilities!

### Further reading

- [Dynamic Component Basics](https://vuejs.org/v2/guide/components.html#Dynamic-Components)   
- [Dynamic &amp; Async Components](https://vuejs.org/v2/guide/components-dynamic-async.html)  
- [Swappable Dynamic Components in Vue.js](https://alligator.io/vuejs/dynamic-components/)</component>