Chapter 62: Vue $props Object
The $props object (also written as this.$props in Options API or simply props in modern Composition API)
This object is the official, single source of truth for all data that the parent component deliberately passed down to this child component.
Understanding $props deeply is what separates people who write fragile, hard-to-maintain Vue code from people who write clean, scalable, type-safe, production-ready Vue code.
1. What exactly is $props? (Very clear mental model)
When a parent uses your component like this:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
<UserProfile :user="currentUser" :is-admin="true" :loading="isLoading" @update="handleUpdate" class="profile-card" data-testid="user-123" /> |
Vue does the following automatic splitting inside the child component:
- Anything that matches a declared prop (name, type, required, default, validator) → goes into $props
- Everything else → goes into $attrs (class, style, data-, aria-, unknown attributes, event listeners)
So in the child:
- $props contains only:
- user
- isAdmin
- loading
- $attrs contains:
- class
- data-testid
- @update listener
Important mental model:
$props = the contract between parent and child → Parent promises: “I will give you these exact named values” → Child promises: “I will use them only for reading (not writing)”
2. Modern Vue 3 – How You Actually Work with Props (2026 Standard)
In <script setup> (the way almost everyone writes Vue in 2026), you never write this.$props or even $props.
Instead you use:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<script setup lang="ts"> // 1. Declare props with types (best practice) const props = defineProps<{ user: { id: number name: string email: string avatar?: string } isAdmin?: boolean loading?: boolean }>() // 2. Use them directly – no $props needed console.log(props.user.name) // 'Rahul' console.log(props.isAdmin) // true or undefined </script> |
Or even shorter (very common):
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
<script setup lang="ts"> const { user, isAdmin, loading } = defineProps<{ user: { id: number; name: string; email: string; avatar?: string } isAdmin?: boolean loading?: boolean }>() </script> |
→ In template: {{ user.name }} (no props. prefix needed)
3. Real, Complete Example – UserProfile Component
Child: UserProfile.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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
<template> <div class="user-profile" :class="{ 'admin-mode': isAdmin }"> <img v-if="user.avatar" :src="user.avatar" alt="Avatar" class="avatar" /> <h2>{{ user.name }}</h2> <p>{{ user.email }}</p> <p v-if="loading" class="loading">Loading additional info...</p> <div v-if="isAdmin" class="admin-badge"> Admin Privileges Enabled </div> </div> </template> <script setup lang="ts"> // Declare props with types (strongly recommended in 2026) defineProps<{ user: { id: number name: string email: string avatar?: string } isAdmin?: boolean // optional loading?: boolean // optional }>() </script> <style scoped> .user-profile { padding: 2rem; background: white; border-radius: 12px; box-shadow: 0 4px 15px rgba(0,0,0,0.08); max-width: 400px; margin: 2rem auto; text-align: center; } .avatar { width: 120px; height: 120px; border-radius: 50%; object-fit: cover; margin-bottom: 1rem; } .admin-badge { background: #fbbf24; color: #92400e; padding: 0.4rem 1rem; border-radius: 9999px; font-weight: bold; margin-top: 1rem; display: inline-block; } .loading { color: #6b7280; font-style: italic; } .admin-mode { border: 2px solid #3b82f6; } </style> |
Parent – passing 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 |
<template> <UserProfile :user="currentUser" :is-admin="true" :loading="isFetching" /> </template> <script setup lang="ts"> import { ref } from 'vue' const currentUser = ref({ id: 101, name: 'Rahul Sharma', email: 'rahul@webliance.in', avatar: 'https://example.com/avatar.jpg' }) const isFetching = ref(false) </script> |
4. Important Rules & Behaviors (2026 Must-Know)
| Rule / Gotcha | Behavior / Best Practice |
|---|---|
| Props are read-only | Never do props.user.name = ‘new’ → Vue warns / throws in dev mode |
| Props are reactive | If parent changes :user=”newUser” → child auto-updates |
| Optional props | Use ? in type → isAdmin?: boolean → can be undefined |
| Default values | defineProps<{ count?: number }>() const count = props.count ?? 0 |
| Prop validation (rare in 2026) | TypeScript + defineProps usually enough — old validator still works in Options API |
| Non-prop attributes | Go to $attrs (class, style, data-, aria-, @events, unknown props) |
| v-model support | Emit ‘update:modelValue’ → enables <MyComp v-model=”value” /> |
| TypeScript + defineProps | Best DX — autocompletion, type checking, required props |
5. Quick Summary Table – Props vs $attrs (2026 Cheat Sheet)
| Question | $props (or just props) | $attrs |
|---|---|---|
| What does it contain? | Only declared props | Everything else (class, style, events, unknown attrs) |
| Auto-applied to root? | No — you use them manually | Yes (unless inheritAttrs: false) |
| Can child modify? | No — read-only | Yes (but usually forward them) |
| Contains event listeners? | No | Yes |
| Type-safe? | Yes — with defineProps<…> | Partial (Record<string, any>) |
| Used for? | Component configuration | Forwarding to native elements / child components |
Pro Tips from Real Projects (Hyderabad 2026)
- Always use TypeScript + defineProps — catches 90% of prop-related bugs at compile time
- Prefer kebab-case in templates (user-name) → auto-converted to camelCase (userName) in JS
- Never mutate props — if child needs to change data → emit → parent updates
- For very large prop lists → consider slots, provide/inject, or composables instead
- Use default values and optional props wisely — makes component more reusable
Your mini practice task:
- Create UserCard.vue with props: user (object), isPremium?, size: ‘small’ | ‘large’
- Add conditional class based on isPremium and size
- Use it from parent with different users & sizes
- Try passing an undeclared prop (e.g. data-test) → check DevTools → it goes to $attrs
Any part confusing? Want full examples for:
- v-model on custom component using props + emit?
- Props validation with custom validator (old style)?
- $props vs $attrs vs $emit full comparison?
- Real UI library component with typed props + forwarded attrs?
Just tell me — we’ll build the next clean, type-safe component together 🚀
Happy propping from Hyderabad! 💙
