Skip to content

Props and defineProps

Props are how parent components pass data down to child components. In FamilyTree, PersonCard receives a Person object as a prop from FocusView — the parent supplies the data, and PersonCard displays it.

defineProps is a compiler macro available inside <script setup>. The TypeScript-generic form is the cleanest:

PersonCard.vue
<script setup lang="ts">
defineProps<{
name: string
birthYear?: number
role?: 'focus' | 'parent' | 'child' | 'spouse'
}>()
</script>
<template>
<div class="person-card" :class="role">
<span class="name">{{ name }}</span>
<span v-if="birthYear" class="year">b. {{ birthYear }}</span>
</div>
</template>

The props declared in defineProps are automatically available in the template — no need to reference props.name, just name.

In the parent template, pass props as HTML attributes:

HomeView.vue
<script setup lang="ts">
import PersonCard from '../components/PersonCard.vue'
</script>
<template>
<PersonCard name="Alice" :birth-year="1982" role="focus" />
<PersonCard name="Bob" role="parent" />
</template>

Static string props can be passed as plain strings: name="Alice". Dynamic props — values from variables — need the : binding: :birth-year="person.birthYear".

Note the naming convention: birthYear in the props definition becomes :birth-year in the template (camelCase → kebab-case). Vue handles this automatically.

When you need default values, wrap defineProps with withDefaults:

<script setup lang="ts">
withDefaults(defineProps<{
name: string
role?: 'focus' | 'parent' | 'child' | 'spouse'
}>(), {
role: 'focus',
})
</script>

If the parent doesn’t pass role, the component uses 'focus' as the default.

Props flow downward — parent to child. A child component must never mutate its props directly. This is Vue’s one-way data flow principle.

If a child needs to change something, it emits an event (covered in the next lesson) and the parent updates its own state in response.

<!-- Wrong — don't do this -->
<script setup lang="ts">
const props = defineProps<{ count: number }>()
props.count++ // ❌ mutating a prop
</script>

If you need a local copy of a prop to modify, copy it into a ref:

<script setup lang="ts">
import { ref } from 'vue'
const props = defineProps<{ initialCount: number }>()
const count = ref(props.initialCount) // ✅ local copy
</script>
  1. Update PersonCard.vue to accept name: string, birthYear?: number, and role?: string as props.
  2. Display the name and conditionally show the birth year.
  3. In App.vue, render three <PersonCard /> instances with different prop values.
  4. Add a prop isDeceased?: boolean and display ”†” after the name if true.
  • defineProps<{ ... }>() declares typed props in <script setup> — props are available directly in the template.
  • Static string props use plain attributes; dynamic props use :attr="value".
  • withDefaults provides fallback values for optional props.
  • Props are read-only in the child — copy into a ref if you need a local mutable value.
  • Data flows one way: parent → child via props; child → parent via emits (next lesson).