Suggest an editImprove this articleRefine the answer for “What are props in Vue?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Props** in Vue pass data from a parent component to a child. The child declares what it expects; the parent supplies the values with `:propName="value"`. ```vue <UserCard :name="userName" :age="25" /> <script setup> defineProps({ name: String, age: Number }); </script> ``` **Key rule:** props are read-only inside the child. To send data back up, emit an event.Shown above the full answer for quick recall.Answer (EN)Image**Props** are the mechanism Vue uses to pass data from a parent component to a child component, giving each child a typed, read-only interface to what the parent provides. ## Theory ### TL;DR - Props flow one way: parent to child. The child cannot modify them directly. - Think of a component like a function: props are its arguments. - Declare props in the child; pass values from the parent with `:propName="value"`. - Child needs to update a value? Emit an event up. Don't mutate the prop. - More than 2-3 levels deep? Use `provide/inject` or Pinia instead. ### Declaring and using props ```vue <!-- Parent: App.vue --> <template> <UserCard :name="userName" :age="userAge" /> </template> <script> export default { data() { return { userName: 'Alice', userAge: 25 }; } }; </script> <!-- Child: UserCard.vue --> <template> <div>{{ name }} is {{ age }} years old</div> </template> <script> export default { props: { name: String, age: Number } }; </script> ``` The parent owns the data. The child receives it through props and renders it. ### One-way data flow Props create a one-way channel from parent to child. When `userName` changes in the parent, Vue automatically updates `name` in the child. But the child has no path to push changes back through a prop. That's by design. Parent manages state, child renders it. This makes components predictable. You look at a child component and know: all inputs come from props, nothing sneaks in from the side. ### When to use - Static config (button label, icon name) → use a prop. - Dynamic data (user profile, list of items) → use a prop. - Child needs to update a value → emit an event up to the parent. - Data needed across many components at once → use Pinia or `provide/inject`. ### Prop validation Vue lets you declare more than just a type. You can mark a prop as required, set a default, or add a custom validator: ```vue <script setup> const props = defineProps({ name: { type: String, required: true }, age: { type: Number, default: 18 }, items: { type: Array, default: () => [], // factory function, not a literal validator: (val) => val.every(item => typeof item === 'string') } }); </script> ``` Vue prints a warning in development if validation fails. Nothing breaks in production, but the warning is your signal that the parent is sending wrong data. ### How props work internally When Vue compiles `<UserCard :name="value" />`, it produces `createVNode(UserCard, { name: value })`. At runtime, Vue wraps the child instance in a `Proxy` and tracks the parent's reactive state via `effect`. When a prop changes, Vue queues a DOM update through `queueJob`, batching multiple changes into one flush. The child just reads `props.name`. The machinery is invisible. ### Common mistakes **Mutating a prop directly.** Vue warns in dev mode and the change doesn't persist. ```js // Wrong props.count++; // Fix: emit the change up emit('update:count', props.count + 1); ``` **Object or array default without a factory function.** All component instances share one reference. This is the bug I see most often when reviewing Vue codebases. ```js // Wrong: every instance shares this same array default: [] // Fix: new array per instance default: () => [] ``` **Forgetting `:` on dynamic values.** `<Comp name="val">` passes the string `"val"`. `<Comp :name="val">` passes the variable. Easy to miss in a template review. **Missing kebab-case in templates.** Declare `isAdmin` in JS, write `:is-admin="true"` in the template. Vue maps them, but only when you follow the convention. ### Real-world usage - Vuetify buttons and cards accept `color`, `elevation`, and `variant` props to configure appearance across the app. - Element Plus uses a `model-value` prop with an `update:modelValue` emit to power `v-model` on form inputs. - Nuxt projects pass a `posts` array as a prop into list components for content pages. - GitLab's Vue frontend uses props extensively for rendering dynamic user lists in merge request views. ### Follow-up questions **Q:** What is the difference between props and state? **A:** State (`data()` or `ref()`) is internal and mutable. Props are external inputs from the parent, read-only inside the child. **Q:** How do you validate a prop in Vue? **A:** Use `type`, `required`, `default`, and `validator` in the prop definition. Vue logs a warning on mismatch in development mode. **Q:** What changed for props in Vue 3? **A:** Vue 3 adds `defineProps()` for `<script setup>`, which gives TypeScript type inference automatically. Runtime behavior is the same as Vue 2. **Q:** How do you implement two-way binding with props? **A:** Declare a `modelValue` prop and emit `update:modelValue`. The parent uses `v-model`, which is shorthand for both. **Q:** How does prop reactivity work with deep objects in Vue 3? **A:** Vue wraps props in a shallow `Proxy`. A primitive prop change triggers a re-render automatically. Deep mutations inside an object prop are not tracked unless you use `watch` with `{ deep: true }` or the parent replaces the reference entirely. Mutating nested prop objects is exactly the kind of bug that's hard to trace later. ## Examples ### Basic: displaying user data in a card ```vue <!-- Parent --> <template> <UserCard :name="user.name" :age="user.age" /> </template> <script setup> import { ref } from 'vue'; import UserCard from './UserCard.vue'; const user = ref({ name: 'Alice', age: 25 }); </script> <!-- Child: UserCard.vue --> <script setup> defineProps({ name: { type: String, required: true }, age: { type: Number, default: 18 } }); </script> <template> <div class="card"> <h2>{{ name }}</h2> <p>Age: {{ age }}</p> </div> </template> ``` The child knows nothing about the parent's data structure. It renders what it receives. ### Intermediate: user list with conditional admin badge ```vue <!-- Parent: UsersList.vue --> <template> <UserCard v-for="user in users" :key="user.id" :name="user.name" :avatar="user.avatar" :is-admin="user.role === 'admin'" /> </template> <script setup> import { ref } from 'vue'; import UserCard from './UserCard.vue'; const users = ref([ { id: 1, name: 'Bob', avatar: '/bob.jpg', role: 'admin' }, { id: 2, name: 'Carol', avatar: '/carol.jpg', role: 'user' } ]); </script> <!-- Child: UserCard.vue --> <script setup> defineProps({ name: String, avatar: String, isAdmin: Boolean }); </script> <template> <div class="user-card"> <img :src="avatar" :alt="name" /> <span>{{ name }}</span> <span v-if="isAdmin" class="badge">Admin</span> </div> </template> ``` Bob gets the admin badge. Carol doesn't. The parent passes the role check result; the child just renders it. Neither component cares about the other's internal logic.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.