Implementing Dynamic Content with Vue Slot Props
Vue's slots are a powerful mechanism for passing content from a parent component to a child component. Slot props take this further by allowing the child component to pass data back to the parent component within the slot's scope. This challenge focuses on implementing and utilizing slot props to create flexible and reusable components.
Problem Description
You are tasked with building a reusable Card component in Vue that can display a title, a footer, and custom content in its body. The Card component should accept the title and footer content directly as props. However, the main content area of the card should be powered by a slot that accepts props. This means the parent component will provide the content for the card's body, and the Card component itself will be able to pass data (like a theme or a loading state) to that slot content.
Key Requirements:
CardComponent Structure:- It should have a predefined structure (e.g., a
divwith classes for title, body, and footer). - Accepts
titleandfooteras plain string props.
- It should have a predefined structure (e.g., a
- Named Slot for Body:
- Use a named slot (e.g.,
#defaultor#body) for the main content area.
- Use a named slot (e.g.,
- Slot Props from
Card:- The
Cardcomponent should expose at least one prop to its slot. For this challenge, let's make it athemeprop (e.g., 'light' or 'dark').
- The
- Using Slot Props in Parent:
- The parent component should be able to receive the
themeprop from theCardcomponent within the slot's scope. - The parent component should conditionally render its slot content based on the
themeprop.
- The parent component should be able to receive the
- Basic Styling:
- Apply minimal CSS to visually distinguish the title, body, and footer, and to demonstrate the
themeprop (e.g., changing background color).
- Apply minimal CSS to visually distinguish the title, body, and footer, and to demonstrate the
Expected Behavior:
When a parent component uses the Card component, it should be able to:
- Pass a simple string for the title.
- Pass a simple string for the footer.
- Provide custom content for the card's body, which can then react to the
themeprop passed down by theCardcomponent.
Edge Cases to Consider:
- What happens if the
titleorfooterprops are not provided? (They should gracefully render empty or with default content). - Ensure the
themeprop is correctly passed and received.
Examples
Example 1: Basic Usage
Parent Component (App.vue)
<template>
<div>
<Card title="Welcome!" footer="Learn more">
<template #default="{ theme }">
<p :class="theme">This is the main content of the card.</p>
<p>Current theme: {{ theme }}</p>
</template>
</Card>
</div>
</template>
<script setup lang="ts">
import Card from './Card.vue';
</script>
<style scoped>
/* Basic styling for demonstration */
.card {
border: 1px solid #ccc;
padding: 15px;
margin-bottom: 20px;
border-radius: 5px;
}
.card-title {
font-weight: bold;
margin-bottom: 10px;
}
.card-footer {
font-size: 0.9em;
color: #666;
margin-top: 10px;
}
/* Theme specific styles */
.light {
background-color: #f9f9f9;
color: #333;
}
.dark {
background-color: #333;
color: #f9f9f9;
}
</style>
Card Component (Card.vue)
<template>
<div class="card">
<div class="card-title">{{ title }}</div>
<div class="card-body">
<slot :theme="currentTheme">
<!-- Fallback content if no slot is provided -->
<p>Default content.</p>
</slot>
</div>
<div class="card-footer">{{ footer }}</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
interface Props {
title?: string;
footer?: string;
}
defineProps<Props>();
// In a real app, this might come from context or another prop
const currentTheme = computed(() => 'light'); // For this example, always 'light'
</script>
<style scoped>
/* Styling within Card component as well */
.card {
border: 1px solid #ccc;
padding: 15px;
margin-bottom: 20px;
border-radius: 5px;
}
.card-title {
font-weight: bold;
margin-bottom: 10px;
}
.card-footer {
font-size: 0.9em;
color: #666;
margin-top: 10px;
}
</style>
Output (Conceptual):
The Card component will render with "Welcome!" as the title, "Learn more" as the footer, and the provided paragraph content. The paragraph will have the class light applied to it, changing its background and text color according to the light theme styles defined in App.vue. The "Current theme: light" text will also be displayed.
Example 2: Dark Theme
Parent Component (App.vue - modification)
<template>
<div>
<Card title="Advanced Settings" footer="Save changes">
<template #default="{ theme }">
<div :class="theme" style="padding: 10px; border-radius: 3px;">
<p>Here are some advanced options.</p>
<p>The applied theme is: {{ theme }}</p>
</div>
</template>
</Card>
</div>
</template>
<script setup lang="ts">
import Card from './Card.vue';
</script>
<style scoped>
/* ... (same styles as Example 1) ... */
</style>
Card Component (Card.vue - modification to expose 'dark' theme)
<template>
<div class="card">
<div class="card-title">{{ title }}</div>
<div class="card-body">
<slot :theme="currentTheme">
<p>Default content.</p>
</slot>
</div>
<div class="card-footer">{{ footer }}</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
interface Props {
title?: string;
footer?: string;
themeProp?: 'light' | 'dark'; // Added prop to control theme from parent if needed
}
const props = defineProps<Props>();
// In a real app, this might come from context or another prop
// For this example, we'll allow the parent to suggest a theme, but Card decides
const currentTheme = computed(() => props.themeProp === 'dark' ? 'dark' : 'light');
</script>
<style scoped>
/* ... (same styles as Example 1) ... */
</style>
Output (Conceptual):
Modify App.vue to pass themeProp="dark" to the Card component:
<template>
<div>
<Card title="Advanced Settings" footer="Save changes" themeProp="dark">
<template #default="{ theme }">
<div :class="theme" style="padding: 10px; border-radius: 3px;">
<p>Here are some advanced options.</p>
<p>The applied theme is: {{ theme }}</p>
</div>
</template>
</Card>
</div>
</template>
The Card component will now render with a dark theme applied to its slot content. The div inside the slot in App.vue will have the dark class, resulting in a dark background and light text for the "Advanced Settings" card. The output will show "The applied theme is: dark".
Constraints
- Vue Version: Vue 3 with the Composition API.
- Language: TypeScript.
- The
Cardcomponent must be a standalone, reusable component. - The
themeprop exposed by the slot should be a string literal type:'light' | 'dark'.
Notes
- Think about how to define the slot's interface in TypeScript for better type safety.
- Consider how a component might dynamically decide which theme to pass down. For this challenge, you can hardcode it in
Card.vueor derive it from a prop passed toCard. - The goal is to demonstrate the concept of slot props, so keep the styling and logic relatively simple.
- Focus on the
<template>structure and the<script setup>for type safety and composition.