Hook — 06 : 07 a.m., Medellín ↔ Panamá City code jam
Damp mountain air drifted through my window as Luis, pair-programming from a rooftop cowork in Panamá, complained, “Our card component needs a custom footer on one page, but not the others — and props are turning into spaghetti.” Ten minutes later we replaced a half-dozen v-ifs with a clean named slot, pushed the change, and Core Web Vitals stayed rock-solid. Luis still made his 7 a.m. surf lesson. That fix-and-ride moment sets the stage for today’s deep dive into Vue 3’s most underrated power feature: slots.


Why Slots Matter for Component Design

Slots let you inject markup into a child component while keeping logic and styling encapsulated. Use them to:

Once you master named, scoped, and dynamic slots you’ll build truly reusable components that survive redesigns and refactors — even when your team’s spread from Bogotá to the Dominican Republic.


Quick Vocabulary Refresher

TermPlain-English Meaning
Default slotUnnamed hole; receives root children
Named slotHole identified by name attribute
Scoped slotSlot that exposes data (v-slot="{…}") to the parent
Dynamic slotSlot name generated at runtime

1. Default & Named Slots — The 80 % Case

Building a Flexible Card

vueCopyEdit<!-- components/BaseCard.vue -->
<template>
  <article class="card">
    <header><slot name="header" /></header>

    <section class="body">
      <slot />
    </section>

    <footer><slot name="footer" /></footer>
  </article>
</template>

<style scoped>
.card {border:1px solid #ccc;border-radius:.5rem;padding:1rem;}
</style>

Usage

vueCopyEdit<BaseCard>
  <template #header>
    <h3>Pricing</h3>
  </template>

  <p>$15/month — unlimited pão de queijo.</p>

  <template #footer>
    <button>Subscribe</button>
  </template>
</BaseCard>

Unnamed slot fills the body; named slots override header and footer when needed.


2. Scoped Slots — Passing Data Upward

A Pagination Component That Emits Page Objects

vueCopyEdit<!-- components/Pager.vue -->
<script setup lang="ts">
import { computed } from 'vue';
const props = defineProps<{ items:number; perPage:number }>();

const pages = computed(() =>
  Array.from({ length: Math.ceil(props.items / props.perPage) }, (_, i) => ({
    number: i + 1,
    from: i * props.perPage,
    to: (i + 1) * props.perPage - 1,
  }))
);
</script>

<template>
  <nav class="pager">
    <slot :pages="pages" />
  </nav>
</template>

Consuming with v-slot

vueCopyEdit<Pager :items="42" :perPage="10" v-slot="{ pages }">
  <button
    v-for="p in pages"
    :key="p.number"
    @click="currentPage = p.number"
  >
    {{ p.number }}
  </button>
</Pager>

The parent decides markup, while the child supplies data — perfect separation of concerns.


3. Dynamic Slots — Rendering Based on Runtime Keys

Tab Component Where Each Panel Is a Slot

vueCopyEdit<!-- components/Tabs.vue -->
<script setup lang="ts">
import { ref, onMounted } from 'vue';
const active = ref('');
onMounted(() => active.value ||= Object.keys(useSlots()).[0] ?? '');
</script>

<template>
  <ul class="tab-head">
    <li
      v-for="name in Object.keys($slots)"
      :key="name"
      :class="{ active: active === name }"
      @click="active = name"
    >{{ name }}</li>
  </ul>

  <div class="tab-body">
    <slot :name="active" />
  </div>
</template>

Parent Side

vueCopyEdit<Tabs>
  <template #Brazil>
    <p>Caipirinhas & code.</p>
  </template>

  <template #Colombia>
    <p>Arepas & Vue.</p>
  </template>

  <template #Mexico>
    <p>Tacos & TypeScript.</p>
  </template>
</Tabs>

The slot keys (Brazil, Colombia, Mexico) become both tab labels and content sources — no prop arrays, no JSON config.


Remote-Work Insight Box

While migrating a fintech dashboard from São Paulo, we needed to inject bank-specific action buttons into a shared transaction row. Dynamic slot names derived from bank.id let each micro-frontend expose custom markup without touching the core table component, slashing review cycles by 30 %.


Performance & Accessibility Checkpoints


Common Pitfalls & Fast Fixes

GotchaWhat HappensRemedy
Forgot # in template syntaxSlot renders as literal <template>Use #footer not footer
Slot prop undefinedScoped slot missed destructuringUse v-slot="slotProps" then slotProps.data
Dynamic slot key missingTab body emptySet default via onMounted or fallback <slot />
Re-render stormPassing whole object as propDestructure or toRefs before slot to avoid new identity

Call-Out Cheat Sheet

API / ConceptOne-liner Purpose
<slot />Default content placeholder
#name="props"Named and scoped slot
useSlots()Runtime access inside child
Object.keys($slots)Build menus from available slots
<template v-slot:[key]>Dynamic slot binding

CLI Nuggets

CommandWhat It Does
vue-tsc --noEmitCatches missing slot prop types
npm i @vueuse/coreShips ready-made composables using scoped slots (useFetch)
vite build --reportEnsures slot heavy components don’t bloat bundles

Diagram (text)

Parent → passes children → Child’s <slot> renders them → Reactivity flows back via emits or injections — physical DOM order may differ but data flow obeys component tree.


Wrap-Up

Slots turn rigid components into lego-bricks: snap in headers, footers, tooltips, or entire render trees without rewriting internals. Reach for named slots for optional regions, scoped slots when children must feed data upward, and dynamic slots to generate UIs from runtime keys. Master these patterns once and you’ll breeze through redesigns — even while coding on a hammock in Costa Rica.

Questions or slot-powered show-and-tells? Drop them below; I’ll answer between flights and late-night arepa sessions.

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x