Data Flow Between Parent and Child Components in Vue 3

Passing Data Downward with Props

Start by defining reactive state inside the parent component. Below, a ref named messageSource holds a string.

<!-- ParentComponent.vue -->
<script setup>
import { ref } from 'vue';
import ChildCard from './ChildCard.vue';

const messageSource = ref('Data originating from parent');
</script>

<template>
  <ChildCard />
</template>

To expose this data to the child, bind a custom attribute on the child tag using v-bind or its shorthand :. The left side declares the prop name the child will receive; the right side references the parent's reactive value.

<!-- ParentComponent.vue -->
<template>
  <ChildCard :transferredMessage="messageSource" />
</template>

Inside the child, define props via defineProps. The received value becomes available for interpolation inside the template or for use within <script setup> through the returned props object.

<!-- ChildCard.vue -->
<script setup>
const props = defineProps({
  transferredMessage: String
});
</script>

<template>
  <p>Child received: {{ props.transferredMessage }}</p>
</template>

Props can also include validation, default values, or multiple types. For instance:

defineProps({
  transferredMessage: {
    type: String,
    default: 'Fallback text'
  }
});

Emitting Events Upward

When a child needs to send enformation back, it declares custom events with defineEmits. The emitted event carries a payload that the parent can capture.

<!-- ChildSender.vue -->
<script setup>
const emit = defineEmits(['update-selection']);

function notifyParent() {
  const payload = { id: 1, label: 'selected item' };
  emit('update-selection', payload);
}
</script>

<template>
  <button @click="notifyParent">Notify Parent</button>
</template>

The parent listens for the event using v-on or @, matching the event name exactly. The handler receives the emitted arguments.

<!-- ParentReceiver.vue -->
<script setup>
import { ref } from 'vue';
import ChildSender from './ChildSender.vue';

const currentSelection = ref(null);

function handleUpdate(data) {
  currentSelection.value = data;
  console.log('Payload from child:', data);
}
</script>

<template>
  <ChildSender @update-selection="handleUpdate" />
  <p>Held selection: {{ currentSelection }}</p>
</template>

Putting It Together

Props flow down; events flow up. In a shared view, both mechanisms often coexist:

<!-- Dashboard.vue -->
<script setup>
import { ref } from 'vue';
import StatusWidget from './StatusWidget.vue';

const systemStatus = ref('operational');
function processAlert(alertMsg) {
  console.warn('Alert received:', alertMsg);
}
</script>

<template>
  <StatusWidget :status="systemStatus" @trigger-alert="processAlert" />
</template>
<!-- StatusWidget.vue -->
<script setup>
const props = defineProps({
  status: String
});

const emit = defineEmits(['trigger-alert']);

function raiseAlert() {
  emit('trigger-alert', `Status changed to: ${props.status}`);
}
</script>

<template>
  <div>
    <span>Current status: {{ status }}</span>
    <button @click="raiseAlert">Simulate Alert</button>
  </div>
</template>

Both props and emits are compile-time macros that don't require explicit imports in <script setup>, though importing them is harmless and often used for explicitnes.

Tags: Vue 3 Component Communication Props Emits frontend

Posted on Mon, 05 Oct 2026 16:19:50 +0000 by jigsawsoul