Ukpai Ugochi I am a female Nigerian with a Bachelor's degree in Marine Engineering and bootcamp certificates in software development. I'm a fullstack JavaScript developer on the MEVN stack. I love to share knowledge about my transition from marine engineering to software development in the form of writing, to encourage people who love software development and don't know where to begin. I also contribute to FOSS in my free time.

A deep dive into Vue slots

7 min read 2046

The Vue logo over a city skyline.

Many times we may want to pass data from one component to another — not in the traditional parent to child component method, but in a method that makes the component reusable.

Components are used to make web templates that you can dump in different places to re-render the same kind of code. How cool would it be if you could reuse a component, but render the component differently in different places based on imputed properties? Pretty cool.

With Vue slots, you can turn a part or all of your components into reusable templates that will render differently based on different use cases. All you need to do is embed them in slots.

In this article, I’ll help you understand the concept of Vue slots and show you how to use them.

Basic usage of slots

So, we understand the whole concept from the definition of slots —  it’s about making components reusable. Now, we’re going to dive in with some examples showcasing Vue slots and talk about how to make use of them.

We’re going to create some buttons in App.vue and allow the buttons to be reused by Buttons.vue:

/**App.vue**/
<template>
  <div id="app">
  <button-helper>
    <button type="button" class="Red">Red</button>
    <button type="button" class="Green">Green</button>
    <button type="button" class="Blue">Blue</button>
  </button-helper>
  </div>
</template>
<script>
import buttonHelper from './components/Buttons.vue'
export default {
  name: 'App',
  components: {
    'button-helper':buttonHelper
  }
    }
</script>

<style>
.Red{
  width:120px;
  height: 50px;
  margin: 50px;
  padding: 10px;
  background-color:red;
  text-align: center;
  cursor:pointer;
}
.Green{
  width:120px;
  height: 50px;
  margin: 50px;
  padding: 10px;
  background-color:Green;
  text-align: center;
  cursor:pointer;
}
.Blue{
  width:120px;
  height: 50px;
  margin: 50px;
  padding: 10px;
  background-color:blue;
  text-align: center;
  cursor:pointer;
}
</style>
//Buttons.vue

<template>
<div>
  <slot></slot>
  </div>
</template>
<script>
export default {
  name: 'Buttons',
    }
</script>

 

A list of three buttons: one red, one green and one blue.

In the above example, we have created some buttons in our <App></App> template. We’ve also called them into our Buttons.vue. When we remove <slot></slot> from App.vue, we’ll see that only <h1>List of Buttons</h1> is shown.

This is because you cannot pass properties of a component from one component (App.vue) to another component (Buttons.vue) simply by embedding the property in the child component tag.

We made a custom demo for .
No really. Click here to check it out.

What if we want to control how the buttons are rendered in Buttons.vue? We’ll probably want a green button to be rendered first, and a red button last.

Below is an example that illustrates how we can reuse components by rendering them differently:

//App.vue

<template>
  <div id="app">
  <button-helper>
    <h1 slot="title">List of Buttons</h1>
    <button slot="red" type="button" class="Red">Red</button>
    <button slot="green" type="button" class="Green">Green</button>
    <button slot="blue" type="button" class="Blue">Blue</button>
  </button-helper>
  </div>
</template>
<script>
import buttonHelper from './components/Buttons.vue'
export default {
  name: 'App',
  components: {
    'button-helper':buttonHelper
  }
    }
</script>

<style>
.Red{
  width:120px;
  height: 50px;
  margin: 50px;
  padding: 10px;
  background-color:red;
  text-align: center;
  cursor:pointer;
}
.Green{
  width:120px;
  height: 50px;
  margin: 50px;
  padding: 10px;
  background-color:Green;
  text-align: center;
  cursor:pointer;
}
.Blue{
  width:120px;
  height: 50px;
  margin: 50px;
  padding: 10px;
  background-color:blue;
  text-align: center;
  cursor:pointer;
}
</style>
//Buttons.vue
<template>
<div>
  <h1>This is the list of buttons from App.vue</h1>
<div>
  <slot name="title"></slot>
  <slot name="green"></slot>
</div>
<div>
  <slot name="title"></slot>
  <slot name="blue"></slot>
</div>
<div>
  <slot name="title"></slot>
  <slot name="red"></slot>
  </div>
<div>
  <slot name="title"></slot>
  <slot name="blue"></slot>
  <slot name="red"></slot>
  <slot name="green"></slot>
</div>
</div>
</template>
<script>
export default {
  name: 'Buttons',
    }
</script>

Our list of buttons now named as slots.

Above, we’ve named our slots. This is very essential when we have more than one property to pass as slots — we’ll need to assign names to slots. For instance, we may not want all of the slots to appear together when we call them.

This way, we can call the slots we want from the child component by calling their names.

We name slots by adding slot="name" to the property in the parent component we want to pass to the child component. We call the named slot in our child component by adding <slot name="name"></slot> to the position where we want our slot to be called. This method is called naming slots with a name attribute.

We can also name our templates using the v-slot directive on a <template>. This way, the name of the slot will be passed as v-slot‘s argument:

//Buttons.vue
<template>
  <div>
    <h1>List of Buttons</h1>
    <slot name="green"></slot>
    <slot name="red"></slot>
    <slot name="blue"></slot>
  </div>
</template>
<script>
export default {
  name: 'Buttons',
    }
</script>
//App.vue
<template>
  <div id="app">
    <Buttons>
      <template v-slot:green>
        <button class="Green">I am Green</button>
      </template>
    </Buttons>
      <Buttons>
      <template v-slot:blue>
        <button class="Blue">I am Blue</button>
      </template>
    </Buttons>
      <Buttons>
      <template v-slot:red> 
        <button class="Red">I am Red</button>
      </template>
    </Buttons>
    </div>
</template>
<script>
import Buttons from './components/Buttons'
export default {
  name: 'App',
  components: {
    Buttons,
  }
    }
</script>
<style>
.Red{
  width:120px;
  height: 50px;
  margin: 50px;
  padding: 10px;
  background-color:red;
  text-align: center;
  cursor:pointer;
}
.Green{
  width:120px;
  height: 50px;
  margin: 50px;
  padding: 10px;
  background-color:Green;
  text-align: center;
  cursor:pointer;
}
.Blue{
  width:120px;
  height: 50px;
  margin: 50px;
  padding: 10px;
  background-color:blue;
  text-align: center;
  cursor:pointer;
}
</style>

Data binding

Now, let’s dig into some more advanced methods of passing slots by binding expressions. This way, we can render slots based on the use case — especially from the input of the user, like when we manipulate the DOM.

We are going to create a simple program that binds data from the child component (Buttons.vue) to the parent component (App.vue) according to the input of the user:

// Button.vue

<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <slot name="propsTest" :count="count" :addOne="addOne" />
  </div>
</template>
<script>
export default {
  name: "Buttons",
  props: {
    msg: String
  },
  data() {
    return {
      count: 0
    };
  },
  methods: {
    addOne() {
      this.count++;
    }
  }
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
  margin: 40px 0 0;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  display: inline-block;
  margin: 0 10px;
}
a {
  color: #42b983;
}
</style>
<template>
  <div id="app">
    <Buttons msg="Simple Slot Binding With Counter Program">
      <template v-slot:propsTest="{ count, addOne }">
        <p>{{ count }}</p>
        <button @click="addOne" class="Red">Add One</button>
      </template>
    </Buttons>
  </div>
</template>
<script>
import Buttons from "./components/Buttons";
export default {
  name: "app",
  components: {
    Buttons
  }
};
</script>
<style>
#app {
  font-family: "Avenir", Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
.Red{
  width:120px;
  height: 50px;
  margin: 50px;
  padding: 10px;
  background-color:red;
  text-align: center;
  cursor:pointer;
}
</style>

A red button that says 'Send'.

In the above example, we’ve passed the counter property from Button.vue to App.vue and added a button by using vue-slots.

Fallback content

So, we may want to assign fallback content to our property from Button.vue.That way, if no property is assigned in App.vue or other components accessing data from Button.vue, the component can fall back to the fall back content:

//Buttons.vue
<template>
<button type="submit" class="Red">
  <slot>Submit</slot>
</button>
</template>
<script>
export default {
  name: 'Buttons',
    }
</script>
<style>
.Red{
  width:120px;
  height: 50px;
  margin: 50px;
  padding: 10px;
  background-color:red;
  text-align: center;
  cursor:pointer;
}
</style>
//App.vue
<template>
  <div id="app">
    <Buttons>
      send
    </Buttons>
  </div>
</template>

<script>
import Buttons from './components/Buttons'
export default {
  name: 'App',
  components: {
    Buttons,
  }
    }
</script>

A red button that says 'send.'

If we remove the send button in the 5th line of App.vue, we would see that the text in the button changes from Submit to Send.

Scoped slots

To pass a property in Vue, you would define the data in the parent component and assign a value to the data. Then, you’d pass the value of the property to the child component so the data becomes a property in the child component.

But with scoped slots, you’d pass properties from your child components into a scoped slot, then access them from the parent component. This is the reverse of the traditional property passing in Vue. This concept is to allow the parent component to have access to some data on the child component.

To understand this concept properly, we would create a simple expression and pass properties from our child component into our parent component using slot-scope.

//App.vue
<template>
  <div>
    <Buttons>
      <template slot-scope="firstSlotScope">
        <p>{{firstSlotScope.text}}</p>
        <!-- Renders <p>I'll get rendered inside the first slot.</p> -->
      </template>
      <template slot="not-first" slot-scope="secondSlotScope">
        <p>{{secondSlotScope.text}}</p>
        <!-- Renders <p>I'll get rendered inside the second slot.</p> -->
      </template>
    </Buttons>
  </div>
</template>
<script>
import Buttons from './components/Buttons';
export default {
  name: 'App',
  components: {
    Buttons,
  }
}
</script>
//Buttons.vue
<template>
  <div>
    <h1>Scoped Slots Example</h1>
    <slot :text="firstSlotText"></slot>
    <slot name="not-first" :text="secondSlotText"></slot>
  </div>
</template>
<script>
export default {
  name: 'Buttons',
  data() {
    return {
      firstSlotText: "I'll get rendered inside the first slot, I'll be first to be rendered.",
      secondSlotText: "I'll get rendered inside the second slot, I'll be second to be rendered."
    }
  }
}
</script>

Scoped slots example.

Instead of binding data and assigning values to them with Vue slots, you can access data on the child component from the parent component as props.

Conclusion

We have seen the different ways to reuse properties of a component by using the slot property. This article also explains how we can name slots dynamically or by using the slot attributes. We have also seen how we can pass specific data from the child component to the parent component with slots and how to use scoped slots.

You can practice more with slots with this Github repo. All the instructions to set up the repository are attached in the ReadMe.md file. The complete code for this project can be found here.

You can read more about Vue slots in Vue’s official site. Feel free to contact me if you have suggestions or questions about this article.

Experience your Vue apps exactly how a user does

Debugging Vue.js applications can be difficult, especially when there are dozens, if not hundreds of mutations during a user session. If you’re interested in monitoring and tracking Vue mutations for all of your users in production, try LogRocket. https://logrocket.com/signup/

LogRocket is like a DVR for web apps, recording literally everything that happens in your Vue apps including network requests, JavaScript errors, performance problems, and much more. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred.

The LogRocket Vuex plugin logs Vuex mutations to the LogRocket console, giving you context around what led to an error, and what state the application was in when an issue occurred.

Modernize how you debug your Vue apps - .

Ukpai Ugochi I am a female Nigerian with a Bachelor's degree in Marine Engineering and bootcamp certificates in software development. I'm a fullstack JavaScript developer on the MEVN stack. I love to share knowledge about my transition from marine engineering to software development in the form of writing, to encourage people who love software development and don't know where to begin. I also contribute to FOSS in my free time.

One Reply to “A deep dive into Vue slots”

Leave a Reply