Chapter 22: Vue Props
Vue Props, one of the most fundamental and frequently used concepts in Vue.js.
Think of props as the official way a parent component sends data down to a child component.
It’s a one-way data flow:
- Parent → gives data to child via props
- Child → can read and use that data, but cannot mutate it directly (in strict mode / good practice)
This rule keeps your app predictable and much easier to debug — especially when the app grows to dozens or hundreds of components.
Core Rules of Props in Vue 3 (2026 style)
- Props are read-only in the child (Vue warns / throws in development if you try to mutate them)
- Props are passed from parent → child using attributes
- Child declares which props it expects using defineProps
- Props can be typed (especially with TypeScript — strongly recommended in 2026)
- Props can have default values, required flags, validators
Modern Way: <script setup> + defineProps (what you should use)
There are two syntaxes — but we will focus on the clean, modern one.
Example 1 – Very Basic Props (String & Number)
Child: GreetingCard.vue
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
<!-- src/components/GreetingCard.vue --> <template> <div class="card"> <h2>Hello, {{ name }}!</h2> <p>You are {{ age }} years old.</p> <p v-if="isPremium" class="premium-badge">Premium Member 🌟</p> </div> </template> <script setup lang="ts"> // Declare props with types (best practice) const props = defineProps<{ name: string age: number isPremium?: boolean // optional prop (can be undefined) }>() </script> <style scoped> .card { padding: 1.5rem; background: #f8fafc; border-radius: 12px; box-shadow: 0 4px 15px rgba(0,0,0,0.08); max-width: 400px; margin: 1rem auto; text-align: center; } .premium-badge { background: #fbbf24; color: #92400e; padding: 0.4rem 1rem; border-radius: 9999px; font-weight: bold; display: inline-block; margin-top: 1rem; } </style> |
Parent: using the component
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
<!-- src/views/HomeView.vue or App.vue --> <template> <div> <h1>Vue Props Demo</h1> <!-- Basic usage --> <GreetingCard name="Rahul from Hyderabad" :age="28" :is-premium="true" /> <!-- Another instance with different data --> <GreetingCard name="Priya" :age="24" /> <!-- No premium prop → isPremium is undefined → no badge --> <GreetingCard name="Guest User" :age="30" /> </div> </template> <script setup lang="ts"> import GreetingCard from '@/components/GreetingCard.vue' </script> |
What happened here?
- :age=”28″ → the : means bind dynamically (v-bind shorthand)
- Without : → it would be passed as string: age=”28″ → child gets “28” (string)
- is-premium → kebab-case in HTML → automatically camelCased to isPremium in JS
Example 2 – Props with Defaults & Required
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
<script setup lang="ts"> const props = defineProps<{ title: string subtitle?: string // optional level?: 'info' | 'success' | 'warning' | 'error' // union type count?: number }>() // Default values (very common pattern) const actualLevel = computed(() => props.level ?? 'info') const actualCount = computed(() => props.count ?? 0) </script> <template> <div :class="['alert', `alert-${actualLevel}`]"> <h3>{{ title }}</h3> <p v-if="subtitle">{{ subtitle }}</p> <p>Count: {{ actualCount }}</p> </div> </template> <style scoped> .alert { padding: 1rem; border-radius: 8px; margin: 1rem 0; } .alert-info { background: #e0f2fe; border: 1px solid #3b82f6; } .alert-success { background: #dcfce7; border: 1px solid #22c55e; } .alert-warning { background: #fef3c7; border: 1px solid #f59e0b; } .alert-error { background: #fee2e2; border: 1px solid #ef4444; } </style> |
Usage:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<AlertBox title="Welcome Back!" subtitle="You have 3 new messages" /> <AlertBox title="Payment Failed" level="error" /> |
Example 3 – Passing Objects & Arrays as Props
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<!-- Child: UserProfile.vue --> <script setup lang="ts"> defineProps<{ user: { id: number name: string email: string avatar?: string roles: string[] } }>() </script> <template> <div class="profile"> <img v-if="user.avatar" :src="user.avatar" alt="Avatar" class="avatar" /> <h3>{{ user.name }}</h3> <p>{{ user.email }}</p> <div class="roles"> <span v-for="role in user.roles" :key="role" class="role-tag"> {{ role }} </span> </div> </div> </template> |
Parent:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<UserProfile :user="{ id: 101, name: 'Rahul Sharma', email: 'rahul@webliance.in', avatar: 'https://example.com/avatar.jpg', roles: ['admin', 'developer'] }" /> |
Important rule (2026 best practice):
- When passing objects/arrays → prefer reactive objects from Pinia / composables
- Avoid mutating props inside child — if child needs to change data → emit event → parent updates
Props Summary Table (2026 Quick Reference)
| Feature | Syntax (defineProps) | Example / Note |
|---|---|---|
| Basic prop | name: string | name=”Rahul” or :age=”28″ |
| Optional prop | isPremium?: boolean | Can be omitted |
| Default value | count?: number + ?? 0 in computed | Very common pattern |
| Required prop | title: string (no ?) | Vue warns in dev if missing |
| Union / Literal types | level?: ‘info’ | ‘success’ | ‘error’ | Great with TypeScript |
| Complex object | user: { id: number; name: string; … } | Type safety + autocompletion |
| Validation (advanced) | defineProps + custom validator function | Rare — mostly use TypeScript |
Pro Tips from Real 2026 Projects
- Always use TypeScript with props → catches bugs early
- Keep props flat & simple when possible (avoid very deep nested objects)
- Never mutate props directly → emit ‘update:xxx’ for v-model support
- Use kebab-case in templates (user-name) → auto-converted to camelCase (userName) in JS
- For very large prop lists → consider slots or provide/inject / composables instead
Practice challenge:
- Create ProductCard.vue with props: name, price, image, isNew?, rating: number
- Add conditional “New!” badge, star rating display, formatted price (₹)
- Use it 3 times in a parent component with different data
Any part still confusing? Want me to show:
- v-model support on custom component (update:prop pattern)?
- Prop validation with custom functions?
- Passing functions as props?
- Props vs slots vs provide/inject?
Just tell me — we’ll build the next example together 🚀
Happy propping from Hyderabad! 💙
