JAVASCRIPT

Vue 3 Advanced Scoped Slots for Dynamic UI Rendering

Master Vue 3 scoped slots to build highly reusable components that let parent components define how specific parts of the child's content are rendered, enabling flexible UI structures.

// src/components/GenericTable.vue
<template>
  <div class="generic-table-container">
    <table>
      <thead>
        <tr>
          <th v-for="header in headers" :key="header.key">
            <slot :name="`header-${header.key}`" :header="header">
              {{ header.label || header.key }}
            </slot>
          </th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="(item, index) in data" :key="item.id || index">
          <td v-for="header in headers" :key="header.key">
            <slot :name="`item-${header.key}`" :item="item" :index="index" :value="item[header.key]">
              {{ item[header.key] }}
            </slot>
          </td>
        </tr>
      </tbody>
      <tfoot v-if="$slots.footer">
        <tr>
          <td :colspan="headers.length">
            <slot name="footer"></slot>
          </td>
        </tr>
      </tfoot>
    </table>
    <div v-if="!data || data.length === 0" class="no-data-message">
      <slot name="no-data">
        No data available.
      </slot>
    </div>
  </div>
</template>

<script setup>
import { defineProps } from 'vue';

defineProps({
  headers: {
    type: Array,
    required: true,
    validator: (headers) => headers.every(h => h.key)
  },
  data: {
    type: Array,
    default: () => []
  }
});
</script>

<style scoped>
.generic-table-container {
  width: 100%;
  overflow-x: auto;
}
table {
  width: 100%;
  border-collapse: collapse;
  margin: 1em 0;
}
th, td {
  border: 1px solid #ddd;
  padding: 8px;
  text-align: left;
}
th {
  background-color: #f2f2f2;
}
.no-data-message {
  padding: 10px;
  text-align: center;
  color: #666;
  background-color: #f9f9f9;
  border: 1px dashed #ccc;
  margin-top: 1em;
}
</style>

// MyParentComponent.vue usage:
/*
<template>
  <GenericTable :headers="tableHeaders" :data="tableData">
    <template #header-name="{ header }">
      <strong>{{ header.label }} (Sorted)</strong>
    </template>

    <template #item-status="{ item }">
      <span :style="{ color: item.status === 'active' ? 'green' : 'red' }">
        {{ item.status.toUpperCase() }}
      </span>
    </template>

    <template #item-actions="{ item }">
      <button @click="editItem(item.id)">Edit</button>
      <button @click="deleteItem(item.id)">Delete</button>
    </template>

    <template #footer>
      <div style="text-align: right; font-weight: bold;">
        Total items: {{ tableData.length }}
      </div>
    </template>

    <template #no-data>
      <p style="color: blue;">No records found. Please add new data.</p>
    </template>
  </GenericTable>
</template>

<script setup>
import { ref } from 'vue';
import GenericTable from './components/GenericTable.vue';

const tableHeaders = ref([
  { key: 'id', label: 'ID' },
  { key: 'name', label: 'Product Name' },
  { key: 'status', label: 'Status' },
  { key: 'price', label: 'Price' },
  { key: 'actions', label: 'Actions' }
]);

const tableData = ref([
  { id: 1, name: 'Laptop', status: 'active', price: 1200 },
  { id: 2, name: 'Mouse', status: 'inactive', price: 25 },
  { id: 3, name: 'Keyboard', status: 'active', price: 75 },
]);

const editItem = (id) => console.log('Edit item:', id);
const deleteItem = (id) => console.log('Delete item:', id);
</script>
*/
How it works: This snippet demonstrates building a highly flexible `GenericTable` component using advanced named and scoped slots. The parent component can completely customize the rendering of individual table headers (`header-<key>`), specific item cells (`item-<key>`), the table footer, and even the "no data" message. The `item`, `index`, and `value` props passed via scoped slots allow the parent to access data for each row and column, making the table extremely versatile without needing to rewrite its core structure.

Need help integrating this into your project?

Our team of expert developers can help you build your custom application from scratch.

Hire DigitalCodeLabs