Vue 3 and Nuxt · Vue 3 Fundamentals

Single-File Components: Props, Slots and Rendering

Build typed single-file components with defineProps and defaults, compose them with default and named slots, and render lists and conditions.

Create a free account to save this page, take notes and track your progress. Sign in

What you'll build

A ProductCard single-file component with typed props and defaults, a computed label, conditional rendering for stock, and slots for the card's content and actions, used from a page that renders a product list. It runs as a server-rendered Nuxt page, so the output is the HTML a visitor receives.

The idea

  • A single-file component (.vue) has <script setup>, <template> and optional <style>.
  • defineProps<T>() declares typed props; destructured props keep reactivity in Vue 3.5+ and can have defaults (const { badge = "New" } = defineProps<...>()).
  • Slots let parents pass template content: the default slot and named slots (<template #actions>).
  • v-for needs a stable :key; v-if/v-else render conditionally; :class binds classes from state.
  • Props flow down, events flow up; never mutate a prop.

The code

A typed component with slots, rendered on the server
// file: nuxt.config.ts
export default defineNuxtConfig({ compatibilityDate: "2026-10-01", devtools: { enabled: false } });

// file: app/app.vue
<template>
  <main><NuxtPage /></main>
</template>

// file: app/components/ProductCard.vue
<script setup lang="ts">
const { name, price, stock, badge = "" } = defineProps<{
  name: string;
  price: number;
  stock: number;
  badge?: string;
}>();

const priceLabel = computed(() => `$${price.toFixed(2)}`);
</script>

<template>
  <article :class="{ 'sold-out': stock === 0 }">
    <h2>{{ name }} <small v-if="badge">({{ badge }})</small></h2>
    <p>{{ priceLabel }}</p>
    <p v-if="stock === 0">Sold out</p>
    <p v-else-if="stock < 5">Only {{ stock }} left</p>
    <slot />
    <footer><slot name="actions" /></footer>
  </article>
</template>

// file: app/pages/index.vue
<script setup lang="ts">
const products = [
  { id: 1, name: "Mug", price: 8.5, stock: 12, badge: "Bestseller", description: "Stoneware, 350 ml" },
  { id: 2, name: "Teapot", price: 24, stock: 3, description: "Cast iron, 1 l" },
  { id: 3, name: "Kettle", price: 31.9, stock: 0, description: "Whistling, 2 l" },
];
</script>

<template>
  <h1>Kitchen shop</h1>
  <ProductCard v-for="p in products" :key="p.id" :name="p.name" :price="p.price" :stock="p.stock" :badge="p.badge">
    <p>{{ p.description }}</p>
    <template #actions>
      <button :disabled="p.stock === 0">Add to cart</button>
    </template>
  </ProductCard>
</template>

// request: GET /

Output

GET / -> 200
Kitchen shop
Mug (Bestseller)
$8.50
Stoneware, 350 ml
Add to cart
Teapot
$24.00
Only 3 left
Cast iron, 1 l
Add to cart
Kettle
$31.90
Sold out
Whistling, 2 l
Add to cart

Run this on your computer — see “Get Started” for setup.

Nuxt auto-imports computed and components from app/components; in a plain Vue project you import them.

Common mistakes

  • Mutating props (props.stock--); emit an event or use defineModel instead.
  • Using the array index as :key for lists that can reorder or change.
  • v-if and v-for on the same element; filter with a computed list instead.
  • Passing many props through several layers ("prop drilling") where a slot or provide/inject would be simpler.

Key points

  • defineProps with TypeScript types and destructured defaults.
  • Default and named slots for flexible composition.
  • Stable keys for lists; props down, events up.

Further reading: Components basics · Props · Slots

Practice questions

Why must you not mutate props?

Answer

Props are owned by the parent; mutating them breaks one-way data flow and is overwritten on the next parent render. Emit an event or use defineModel so the parent updates the value.

What are named slots for?

Answer

They let a parent pass several pieces of template content into specific places in a child component, such as a header and an actions area.

Why does v-for need a key?

Answer

Keys let Vue track each item's identity so it can reuse and reorder elements correctly when the list changes; use stable IDs, not indexes.

Open in the app to mark lessons complete, take notes and track your progress.

Was this page helpful?