Vue props let parent components pass data down to child components, just like custom HTML attributes. Vue enforces one-way data flow, meaning props flow down from parent to child and stay read-only inside the child.
When a parent updates a reactive value passed as a prop, the child updates automatically. Preventing the child from mutating props directly keeps data flow predictable and easier to debug.
flowchart TD
Parent["Parent Component"] -->|Passes Props| Child["Child Component"]
Child -->|Emits Event| Parent
definePropsInside <script setup>, use the built-in defineProps() macro to declare expected props. You do not need to import defineProps because the Vue compiler transforms it at build time.
Adding type, required, default, and validator rules catches mismatched data early with helpful console warnings during development.
Here is a child UserProfile.vue component that validates incoming user properties and renders them cleanly.
<script setup>
const props = defineProps({
userId: {
type: Number,
required: true
},
username: {
type: String,
required: true
},
role: {
type: String,
default: 'Member',
validator(value) {
return ['Admin', 'Editor', 'Member'].includes(value);
}
},
tags: {
type: Array,
default: () => []
}
});
</script>
<template>
<div class="user-card">
<h3>{{ props.username }} (ID: {{ props.userId }})</h3>
<span class="role-badge">{{ props.role }}</span>
<ul class="tag-list">
<li v-for="tag in props.tags" :key="tag">{{ tag }}</li>
</ul>
</div>
</template>
In the parent App.vue, bind numbers, booleans, and arrays with : so Vue passes real JavaScript values instead of strings.
<script setup>
import UserProfile from './components/UserProfile.vue';
</script>
<template>
<div>
<UserProfile
:user-id="101"
username="alex_dev"
role="Admin"
:tags="['Vue', 'JavaScript', 'Frontend']"
/>
</div>
</template>
props.role = 'Guest' inside the child triggers a Vue warning and causes state bugs. Emit an event to ask the parent to change the value, or copy the prop into a local ref.: for Non-String Props: Writing user-id="101" passes the string "101" instead of the number 101. Always use :user-id="101" for numbers, booleans, arrays, and objects.default: []. Always use a factory function default: () => [] so component instances do not share the same array reference.defineProps and kebab-case in templates?JavaScript variables prefer camelCase like userId, while HTML attributes are case-insensitive and favor kebab-case like :user-id. Vue maps between both formats automatically.
defineProps in Vue 3?In Vue 3.5 and later, destructuring defineProps preserves reactivity automatically. In older Vue 3 versions, destructuring const { role } = defineProps(...) drops reactivity unless you use toRefs(props).
Yes. Initialize a local ref with const localRole = ref(props.role) when the prop only serves as an initial starting value.
Sign in to track your learning journey, earn industry-recognized certificates, and join our elite developer community.
Quick Access With
You've completed this section! Take a quick 5-question quiz to check your understanding.