DEV Community

Bijit Mondal
Bijit Mondal

Posted on • Edited on

Vue.js Slots

Slots

Vue.js implements the content distribution API using the 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 and use it.

Image description

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

<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>
Enter fullscreen mode Exit fullscreen mode

Use the CustomButton Component inside another Component like below

<template>
  <CustomButton>
    Hello Btn
  </CustomButton>
</template>
<script>
import CustomButton from './CustomButton.vue';
export default {
  components: {
    CustomButton,
  }
}
</script>
Enter fullscreen mode Exit fullscreen mode

Output

Image description

Using slots, the component renders the outer (with its custom styling) while the parent component provides the inner content.

Image description

Render Scope

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

<template>
  <CustomButton>
    {{content}}
  </CustomButton>
  <span>{{content}}</span>
</template>
Enter fullscreen mode Exit fullscreen mode

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.

<!-- In CustomButton.vue -->
<button class="custom-button">
    <slot>Go</slot>
</button>
Enter fullscreen mode Exit fullscreen mode

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

<!-- In parent component -->
<CustomButton>Hello Btn</CustomButton>
<CustomButton /> <!-- Without any content for slot -->
Enter fullscreen mode Exit fullscreen mode

Image description

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 elements. It is possible to achieve this with named slots.

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

<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>
Enter fullscreen mode Exit fullscreen mode

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>

<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>
Enter fullscreen mode Exit fullscreen mode

Output

Output of Named Slots

Dynamic Slot Names

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

<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>
Enter fullscreen mode Exit fullscreen mode
<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>
Enter fullscreen mode Exit fullscreen mode

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
<template>
  <div>
    <slot :platform="gfg" age="19" :year="2023" />
  </div>
</template>

<script setup>
    const gfg = "Bijit-Mondal";
</script>
Enter fullscreen mode Exit fullscreen mode

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
<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>
Enter fullscreen mode Exit fullscreen mode

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

tag.

Image description

Check Out the Code

Top comments (0)