Vue applications are built from components — self-contained pieces of UI with their own logic and styling. Understanding how components communicate is the foundation of building maintainable Vue applications.
Creating a Component
Every Vue Single File Component (SFC) has three sections:
<script setup>
// Logic
import { ref } from 'vue'
const message = ref('Hello')
</script>
<template>
<!-- Template -->
<p>{{ message }}</p>
</template>
<style scoped>
/* Styles — scoped means they only apply to this component */
p { color: blue; }
</style>Defining Props
Props are how parent components pass data to child components:
<!-- ChildComponent.vue -->
<script setup>
defineProps({
title: String,
count: {
type: Number,
default: 0
},
isActive: {
type: Boolean,
required: true
}
})
</script>
<template>
<div :class="{ active: isActive }">
<h2>{{ title }}</h2>
<p>Count: {{ count }}</p>
</div>
</template><!-- ParentComponent.vue -->
<template>
<ChildComponent title="My Title" :count="5" :is-active="true" />
</template>Note: :count and :is-active use Vue's v-bind shorthand (:) to pass dynamic values. Without the colon, you pass a string literal.
Emitting Events
Child components communicate back to parents via events:
<!-- Button.vue -->
<script setup>
const emit = defineEmits(['click', 'hover'])
function handleClick() {
emit('click', { timestamp: Date.now() })
}
</script>
<template>
<button @click="handleClick">Click me</button>
</template><!-- Parent.vue -->
<script setup>
function onButtonClick(payload) {
console.log('Clicked at:', payload.timestamp)
}
</script>
<template>
<Button @click="onButtonClick" />
</template>Slots
Slots allow parent components to inject content into child components:
<!-- Card.vue -->
<template>
<div class="card">
<slot name="header" />
<div class="body">
<slot /> <!-- default slot -->
</div>
</div>
</template><!-- Usage -->
<Card>
<template #header>
<h2>Card Title</h2>
</template>
<p>Card content goes here</p>
</Card>Component Design Principles
Keep components small. A component that does one thing is easier to test and reuse than one that does many things.
Props down, events up. Data flows down via props; communication flows up via events. Don't mutate props directly in a child component.
Use TypeScript for props. With TypeScript, define props using interfaces for better type safety and IDE support:
interface Props {
title: string
count?: number
isActive: boolean
}
const props = defineProps<Props>()