Vue.js Slots
Tags: webdev, vue, javascript, tutorial
Published: 2023-12-14 09:47:37

## Slots
Vue.js implements the content distribution API using the **<slot>** element. Slots enables distributing content across other components. They're particularly helpful when creating reusable widgets. This approach ensures that distributed content is positioned precisely within the child component, as intended.

### Slots content and outlet
We can create custom button component **<CustomButton/>** and use it. 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o2h5doku4ls7fxppj3gq.png)

Create a **CustomButton.vue** component, the template should look like this

```javascript
<template>
  <button class="custom-button">
    <slot></slot>
  </button>
</template>
<script>
export default {
  name: 'CustomButton',
};
</script>
<style scoped>
.custom-button {
  padding: 0.5rem 2rem;
  border-radius: 77px;
  background: #2f8d46;
  box-shadow: 2px 4px 4px 0px rgba(#2f8d46, 0.15);
  border: 0;
  color: #fff;
  font-family: Josefin Sans;
  font-weight: 500;
}
</style>
```

Use the **CustomButton** Component inside another Component like below

```javascript
<template>
  <CustomButton>
    Hello Btn
  </CustomButton>
</template>
<script>
import CustomButton from './CustomButton.vue';
export default {
  components: {
    CustomButton,
  }
}
</script>
```

### Output


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qbb5rmd5o3uhjsr9qeer.png)

Using slots, the **<CustomButton>** component renders the outer <button> (with its custom styling) while the parent component provides the inner content.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kfveaajxiq15ul4jbyn1.png)


## Render Scope
The slot content has access to the data scope of the parent component because it is defined within the parent.

```javascript
<template>
  <CustomButton>
    {{content}}
  </CustomButton>
  <span>{{content}}</span>
</template>
```

Both of the `{{content}}` will render the same content. 
Slot content doesn't have access to the child component's data 

## Fallback Content
Sometimes, it's helpful to specify a default message or content for a slot, which will be displayed if the parent component doesn't provide any specific content. 

```html
<!-- In CustomButton.vue -->
<button class="custom-button">
    <slot>Go</slot>
</button>
```

After changing the `CustomButton` if we use the component in parent component without any content, it will render the default content `Go`

```html
<!-- In parent component -->
<CustomButton>Hello Btn</CustomButton>
<CustomButton /> <!-- Without any content for slot -->
```


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wcvh5y2rqdoajgmupjs3.png)


## Named Slots

Having one `<slot>` element to inject content can satisfy some use cases. However, in other use cases where there is a need to utilize multiple <slot> elements. It is possible to achieve this with named slots.

Named slots are <slot> elements with a name attribute to allow for name spaced content injection, we can improve our `CustomButton` to have a icon slot.

```html
<template>
  <button class="custom-button">
    <slot name="context">Go</slot>
    <template v-if="$slots.icon" class="slot-icon">
      <slot name="icon"></slot>
    </template>
  </button>
</template>
<script>
export default {
  name: 'CustomButton',
};
</script>
<style scoped>
.custom-button {
  padding: 0.5rem 2rem;
  border-radius: 77px;
  background: #2f8d46;
  box-shadow: 2px 4px 4px 0px rgba(#2f8d46, 0.15);
  border: 0;
  color: #fff;
  font-family: Josefin Sans;
  font-weight: 500;
}
.slot-icon {
  display: flex;
}
</style>
```

In the above example the `<slot>` elements has a special attribute, name. A `<slot>` outlet without name implicitly has the name "`default`".

To pass a named slot, Use a `<template>` with `v-slot` followed by the colon and slot name or use the shorthand of v-slot '#', so` <template v-slot:context>` => `<template #context>`

```html
<div class="button">
    <CustomButton>
      <template #context> Hello Btn</template>
      <template v-slot:icon>
        <svg
          xmlns="http://www.w3.org/2000/svg"
          viewBox="0 0 20 20"
          fill="currentColor"
          class="button__icon"
        >
          <path
            d="M13.75 7h-3v5.296l1.943-2.048a.75.75 0 011.114 1.004l-3.25 3.5a.75.75 0 01-1.114 0l-3.25-3.5a.75.75 0 111.114-1.004l1.943 2.048V7h1.5V1.75a.75.75 0 00-1.5 0V7h-3A2.25 2.25 0 004 9.25v7.5A2.25 2.25 0 006.25 19h7.5A2.25 2.25 0 0016 16.75v-7.5A2.25 2.25 0 0013.75 7z"
          />
        </svg>
      </template>
    </CustomButton>
    <CustomButton />
  </div>
```

## Output

![Output of Named Slots](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ui4o1ftb7r9bz4mryhym.png)

## Dynamic Slot Names

v-slot also allows dynamic slot names, we can change in the previous button context a dynamic slot

```html
<template>
  <button class="custom-button">
    <slot :name="context">Go</slot>
    <template v-if="$slots.icon" class="slot-icon">
      <slot name="icon"></slot>
    </template>
  </button>
</template>
<script>
export default {
  name: 'CustomButton',
  props: {
    context: {
      type: String,
      default: 'context', // Default slot name
    },
  },
};
</script>
<style scoped>
.custom-button {
  padding: 0.5rem 2rem;
  border-radius: 77px;
  background: #2f8d46;
  box-shadow: 2px 4px 4px 0px rgba(#2f8d46, 0.15);
  border: 0;
  color: #fff;
  font-family: Josefin Sans;
  font-weight: 500;
}
.slot-icon {
  display: flex;
}
</style>
```

```html
<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
  </div>
  <div class="button">
    <CustomButton>
      <template #[dynamicSlotName]> Hello Btn</template>
      <template v-slot:icon>
        <svg
          xmlns="http://www.w3.org/2000/svg"
          viewBox="0 0 20 20"
          fill="currentColor"
          class="button__icon"
        >
          <path
            d="M13.75 7h-3v5.296l1.943-2.048a.75.75 0 011.114 1.004l-3.25 3.5a.75.75 0 01-1.114 0l-3.25-3.5a.75.75 0 111.114-1.004l1.943 2.048V7h1.5V1.75a.75.75 0 00-1.5 0V7h-3A2.25 2.25 0 004 9.25v7.5A2.25 2.25 0 006.25 19h7.5A2.25 2.25 0 0016 16.75v-7.5A2.25 2.25 0 0013.75 7z"
          />
        </svg>
      </template>
    </CustomButton>
    <CustomButton />
  </div>
</template>

<script>
import CustomButton from './CustomButton.vue';
export default {
  components: {
    CustomButton,
  },
  data() {
    return {
      dynamicSlotName: 'context',
    };
  },
  name: 'HelloWorld',
  props: {
    msg: String,
  },
};
</script>
<style>
.button {
  display: flex;
  align-items: center;
}
.button * {
  margin-bottom: 10px;
}
.button__icon {
  width: 2em;
  height: 2em;
  margin-right: 0.5em;
}
</style>
```

## Scoped Slots
Scoped Slot provides local data from the component so that the parent can choose how to render it. Vue scoped slots allow to pass data from a child component to its parent component, and then access that data from the parent component's template.

### Single Scoped Slot
    
- Send Data to Parent. Create a `ChildComponent.vue
`
```html
<template>
  <div>
    <slot :platform="gfg" age="19" :year="2023" />
  </div>
</template>

<script setup>
    const gfg = "Bijit-Mondal";
</script>
```
To pass props to a single default slot, use the v-slot directive directly on the child component tag. The props will be available as the value of the v-slot directive, which can access using expressions inside the slot. Example below

- Receive Data from Scoped Slot. Inside  `ParentComponent.vue`

```html
<template>
  <div>
   <ChildComponent v-slot="slotProps">
        <p> {{ slotProps.platform }} at {{ slotProps.age }} on {{ slotProps.year }}</p>
   </ChildComponent>
  </div>
</template>

<script setup>
    import ChildComponent from './ChildComponent.vue';
</script>
```
In the example above, slotProps is a name that you can choose yourself to represent the data object that you receive from the scoped slot. You can get the text string from the slot by using the platform property.

A scoped slot can also send static data, which is data that does not belong to the data property of the Vue instance. When sending static data, you do not need to use v-bind. Instead, you can use interpolation to render the text in an <p> tag.



![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bc714nl02w9b3dgxw9ve.png)

[Check Out the Code](https://stackblitz.com/edit/vue-pspmh5?file=src%2FApp.vue)