Components encapsulate template, data, methods, and lifecycle into reusable units.
Register in components on the root app or any parent component. Children are available in that component's template and all its descendants.
createApp({
components: {
'user-card': {
template: `<div class="card">{{ name }} — {{ role }}</div>`,
data: { name: '', role: '' }
}
},
template: `<user-card :name="currentUser" :role="'editor'" />`
}).mount('#app');
Pass reactive data from parent to child via :propName. Parent changes flow down automatically.
<!-- Parent passes props with : prefix -->
<user-card :name="currentUser" :role="'editor'" />
// user-card component
{
data: { name: '', role: '' },
template: `<h3>{{ name }}</h3><span>{{ role }}</span>`
}
Child notifies parent without direct coupling.
// Child emits to parent
{
methods: {
close() { this.$emit('close'); },
submit(data) { this.$emit('submit', data); }
}
}
<!-- Parent listens -->
<modal @close="onClose" @submit="onSubmit" />
Alternative to emit — fires a native CustomEvent that bubbles up the DOM. Any ancestor element can catch it with @event.
// Child dispatches a bubbling CustomEvent from $el
methods: {
select(item) {
this.$dispatch('item-selected', { id: item.id });
}
}
<!-- Any ancestor can listen -->
<div @item-selected="onSelected">
<product-list />
</div>
Default slot, named slots, and scoped slots (parent reads component-exposed data via v-slot).
<!-- Default slot <!-- Default slot -->
<my-panel><p>Content from parent</p></my-panel>
<!-- my-panel template <!-- my-panel template -->
<div class="panel"><slot></slot></div>
<!-- Named slots <!-- Named slots -->
<my-card>
<span slot="header">Title</span>
<p>Body content</p>
</my-card>
<!-- my-card template <!-- my-card template -->
<div>
<header><slot name="header" /></header>
<main><slot /></main>
</div>
<!-- Scoped slot — component exposes data up to parent <!-- Scoped slot — component exposes data up to parent -->
<item-list :items="products" v-slot="{ item, index }">
{{ index }}. {{ item.name }}
</item-list>
Destroys and mounts a new component when the value changes.
<!-- Mounts the component whose name matches activeView <!-- Mounts the component whose name matches activeView -->
<component :is="activeView" />
data: { activeView: 'tab-home' },
components: {
'tab-home': { template: `<p>Home</p>` },
'tab-settings': { template: `<p>Settings</p>` }
}
<!-- On a native element: stores the HTMLElement <!-- On a native element: stores the HTMLElement -->
<input cv-ref="myInput" />
<!-- On a component: stores the child's reactive state <!-- On a component: stores the child's reactive state -->
<counter cv-ref="counter" />
<button @click="$refs.counter.reset()">Reset</button>
<!-- cv-model on a component -->
<mi-input cv-model="search" />
// Expands to: :modelValue="search" @update:modelValue="search = $event"
// Child emits:
methods: {
onInput(e) { this.$emit('update:modelValue', e.target.value); }
}
<!-- Multiple cv-model bindings -->
<editor cv-model:title="docTitle" cv-model:body="docBody" />
| Property | Description |
|---|---|
$el | Root DOM element |
$attrs | Non-prop, non-event attributes. Set inheritAttrs: false to opt out of auto-inheritance. |
$parent | Parent component's reactive state. Prefer props + emit when possible. |
$slots | Object with true for each provided slot name. Use for conditional slot rendering. |