Content Distribution in Vue JS
Tags: vuejavascript
Published: 2020-09-15 13:31:20

---
title: Content Distribution in Vue JS
published: true
date: 2020-07-23 13:17:59 UTC
tags: vuejavascript
canonical_url: https://www.thisdot.co/blog/content-distribution-in-vue-js
---

Vue implements a content distribution API inspired by the Web Components spec draft, using the `<slot>` element to serve as distribution outlets for content.

It promotes building composable and reusable components. Many folks have been writing about, or discussing, best practices, how to build components, how they communicate with each other, and how they should be structured in an app.

In this article, we'll dive head-first into Vue.js Slots, and Scoped Slots concepts, and go through code samples.

## Slots

A Slot is a placeholder that allows content projection from a parent component to a child component.

The Vue engine, at runtime, processes the parent template, pulls the dynamic content, and injects it inside the Slot placeholder in the child component. In general, the child component hosts the `<slot>` element, and the parent component specifies the content to go inside it.

**Figure 1** shows the interaction between a parent and child component.

| ![Parent and child component](http://images.ctfassets.net/zojzzdop0fzx/4pwaF9iSFeoxNQqOjugyWB/213fdbf0179b0494ac0de2c02959a4ec/sC-fCDpdVDX-mo-oPMC-dqA.png) |
| --- |
| **Figure 1:** Parent and child component |

Basically, Slots are used for building Layout components (also known as Master Pages), base components (Button, Hyperlink and the like) or any other type of composable components to provide flexibility for component consumers to inject dynamic content at runtime.

Let’s create a `Button` component with a `<slot>` element.

```
<template>
  <button>
    <slot>Click Me</slot>
  </button>
</template>
```

  

The `Button` component is basic and contains a single `<button>` element. The button wraps a `<slot>` component with a default fallback text. This means, if the parent component didn’t provide any content for the Slot, it will be replaced at runtime with the default fallback text **Click Me**.

Let's embed this `Button` component inside a parent component.

```
<template>
    <Button></Button>
</template>
```

  

The child component `Button` is embedded into the parent component like any other normal component in Vue.js. Notice that in the code snippet above, the parent component is not injecting any content. When you run the app, you will see an HTML `<button>` element with the default fallback text. **Figure 2** shows the app running.

| ![Slot with default content](http://images.ctfassets.net/zojzzdop0fzx/5tUzaHiE1relYrI8NwIBCa/c3de0f29550fa14a7dbcf3e6f695dcea/spmKe7Eduyowf4WWM-FZadw.png) |
| --- |
| **Figure 2:** Slot with default content |

Let's inject some other text instead of the default fallback text, and run the app again. You have multiple choices when it comes to injecting content.

**Without `<template>` element**

```
<Button>
  Pretty Nice Button!
</Button>
```

  

**With `<template>` element**

```
<Button>
  <template v-slot:default>
    Pretty Nice Button!
  </template>
</Button>
```

  

Personally, I prefer the option using `<template>` element. The template content will be rendered in place of the `<slot>` element.

The `<slot>` element has a special attribute called `name`. This allows you to embed multiple Slots in your component, each designated with a different Slot name.

A `<slot>` element without a `name` attribute is considered the default Slot. To provide content for a `<slot>` element, you would use the `v-slot` directive, and then specify the name of the Slot.

In this code snippet, we could have removed the `v-slot:default` as this is the only `<slot>` element in the component. But, as a best practice, and in case you add more Slots in the future, I’d recommend you stick to this naming convention by using the `v-slot:default`.

You can play with this example on [codesandbox.io](https://codesandbox.io/s/single-slot-652zt).

Let's assume the `Button` component has another slot:

```
<template>
  <button>
    <slot>Click Me</slot>
    <br>
    <slot name="footer"/>
  </button>
</template>
```

  

Inside the parent component:

```
<template>
    <Button>
      <template v-slot:default>Pretty Nice Button!</template>
      <template v-slot:footer>Footer!</template>
    </Button>
</template>
```

  

There are two `<template>` elements, each for a specific slot.To add content to the default `<slot>` element, you add the `v-slot:default` directive by specifying the name of the slot. For the footer Slot, you provide the `v-slot:footer` directive to target the footer `<slot>` element.

Now, it makes more sense to use the `v-slot` directive on both Slots to differentiate them, and make your code readable.

You can play with this example on [codesandbox.io](https://codesandbox.io/s/multiple-slots-znyjs).

## Slots and Props

Thus far, we have seen how to use `<slot>` elements, and inject content to them. In some other cases, you might need to pass data to Slots.

Slots are generally compiled under the parent’s component context. This means whatever content you project inside a `<slot>` element is aware of the parent’s context only. For instance, if you have an interpolation or binding projected inside the `<slot>` element, these will be compiled with respect to the parent component, and not the child component. A code snippet is worth a thousand words.

```
<template>
    <Button>
      <template v-slot:default>
        {{ text }}
      </template>
    </Button>
</template>

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

export default {
  components: {
    Button
  },
  data() {
    return {
      text: 'Pretty Nice Button!',
    };
  },
};
</script>
```

  

The `{{ text }}` interpolation is referencing a local data variable defined on the component named `text`. The Vue.js engine compiles the projected content inside the parent component scope, and embeds the results into the child component.

You can play with this example on [codesandbox.io](https://codesandbox.io/s/slot-parent-context-uij18).

A `<slot>` element can also render default fallback text passed from the parent component to the child component. How? Nothing special really. It's similar to how you pass data to Vue components via [props](https://vuejs.org/v2/guide/components-props.html).

Let's introduce a new `text` property on the `Button` component:

```
<template>
  <button>
    <slot>{{ text }}</slot>
  </button>
</template>

<script>
export default {
  props: {
    text: {
      type: String,
      default: "Click Me"
    }
  }
};
</script>
```

  

In the parent component:

```
<template>
  <div id="app">
    <h2>Vue Slots Demo</h2>
    <Button v-bind:text="text"></Button>
  </div>
</template>

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

export default {
  components: {
    Button
  },
  data() {
    return {
      text: "Pretty Nice Button!!!!"
    };
  }
};
</script>
```

  

You make use of Vue.js binding convention to bind the `text` prop on `Button` component to a local variable also named `text`. That’s all!

You can play with this example on [codesandbox.io](https://codesandbox.io/s/pass-params-to-slots-wq3c9).

You can even go wild and accept a prop of type `Function` on the child component! The child component can share its context with the parent component. The parent component can then manipulate the child component context and return whatever content back to the child component.

The child component accepts a prop of type `Function`:

```
<template>
  <button>
    <slot>{{ textFnc('Hi from child!') }}</slot>
  </button>
</template>

<script>
export default {
  props: {
    textFnc: {
      type: Function
    }
  }
};
</script>
```

  

The default fallback content inside the child component calls the prop `Function` passing some random text.

The parent component defines the prop `Function` as follows:

```
<template>
  <div id="app">
    <h2>Vue Slots Demo</h2>
    <Button v-bind:textFnc="(prefix) => prefix + ' - ' + message"></Button>
  </div>
</template>

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

export default {
  components: {
    Button
  },
  data() {
    return {
      message: "Pretty Nice Button!!!!"
    };
  }
};
</script>
```

  

The function `textFunc()` executes under the parent component context. In this case, the function returns the text received from the child component, and appends it to a message that’s local to the parent component. The end result is shown in **Figure 3**.

| ![Function Prop](http://images.ctfassets.net/zojzzdop0fzx/1hLSryPVBk0harmor6bX7Y/6f5e7675aa27072afef75e593e4f8d07/spU8owLusLB7pAw6y-YIllg.png) |
| --- |
| **Figure 3:** Function prop |

You can play with this example on [codesandbox.io](https://codesandbox.io/s/pass-function-params-to-slots-6jybj).

The takeaway, in using functions as props, is the ability to share the child component’s context with the parent component.

## Scoped Slots

Passing functions as props to a child component is one way of sharing context between child and parent components. However, this method is limited to text only. The function defined on the parent component can only return text.

What if you want the parent component to pass HTML content to the child component’s slot(s) and also make use of the child component context? This is where Scoped Slots play their part.

Pretty complicated right? Well, not as complicated as you might think. Let’s check it out together!

Let's build a `List` component that owns an array of objects with a `title` and `description` properties.

```
<template>
  <ul>
    <li v-for="item in items" :key="item.id">
      <slot :item="item">
        <p>
          {{ item['title'] }}
          <br>
          {{ item['description'] }}
        </p>
      </slot>
    </li>
  </ul>
</template>

<script>
export default {
  props: {
    items: {
      type: Array,
      default: () => []
    }
  }
};
</script>
```

  

The component uses a `<ul>` element to loop over the items in the array. An `<li>` element is used to represent a single item. The code places a `<slot>` element inside each and every `<li>` element. The slot is bound to the single item object. In addition, the `<slot>` element defines a default fallback content.

Let’s switch to the parent component:

```
<template>
  <div id="app">
    <h2>Vue Slots Demo</h2>
    <List :items="items" />
  </div>
</template>

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

export default {
  components: {
    List
  },
  data() {
    return {
      items: [
        {
          id: 1,
          title: "Item #1",
          description: "Item #1 description"
        },
        {
          id: 2,
          title: "Item #2",
          description: "Item #2 description"
        },
        {
          id: 3,
          title: "Item #3",
          description: "Item #3 description"
        },
        {
          id: 4,
          title: "Item #4",
          description: "Item #4 description"
        },
        {
          id: 5,
          title: "Item #5",
          description: "Item #5 description"
        }
      ]
    };
  }
};
</script>
```

  

The component binds the `items` array property to the `List` component property `items`. It uses the default fallback content coming from the child component.

**Figure 4** shows the app running:

| ![List component with default fallback content](http://images.ctfassets.net/zojzzdop0fzx/2zRBSNAgyZ4WdSzvNBl5xC/efc44ac1596e369b979a5ad4eb6459fe/sk42HyQr06sdqgW8N0aD1nw.png) |
| --- |
| **Figure 4:** List component with default fallback content |

Let’s see how we can make use of the Slot binding.

As a refresher, the child component defines the following:

```
<slot :item="item">
  <p>
    {{ item['title'] }}
    <br>
    {{ item['description'] }}
  </p>
</slot>
```

  

The `<slot>` element binds the `item` object on the Slot. It also provides a default fallback content. What if the parent component wants to replace this default content with a new content? That's easy!

Let's change the parent component as follows:

```
<template>
  <List :items="items">
    <template v-slot:default="prop">
      <strong>{{ prop.item.title }}</strong>
    </template>
  </List>
</template>
```

  

The code snippet provides a `<template>` with a `v-slot:default` directive. The value given to the `v-slot:default` directive is actually the directive value representing the object passed in by the `<slot>` element defined inside the child component.

> As a side note, I am writing another article, to be published very soon, exploring custom directives in Vue.js. Stay tuned!

Going back to the code snippet, the parent component now has access to the child component represented by the `v-slot:default` directive value.

The `<template>` element replaces the default fallback content by rendering just the `title` field and making it bold.

**Figure 5** shows the app running:

| ![List component with custom content](http://images.ctfassets.net/zojzzdop0fzx/2oh6pHv4hjfUMXkkQWx79A/404331594f768efa225368752e7568f4/s7_s_z17KVYuicG92pSDivA.png) |
| --- |
| **Figure 5:** List component with custom content |

Scoped Slots is a powerful tool in Vue.js to allow you to build composable and reusable components.

## Slots in Vue 3

The content on Slots and Scoped Slots in this article applies to both Vue 2 and Vue 3. I am not aware of any change up until this moment. Things might change with the final release of Vue 3, but we’ll have to wait and see together!

## Conclusion

Vue Slots feature gives developers the flexibility needed to build reusable and composable components in their applications.