<component :is>Vue dynamic components let you swap between different components at the same mount point using the built-in <component :is="..."> element. Instead of writing long v-if / v-else-if chains, you bind :is to a variable holding the active component.
This pattern fits tabbed interfaces, multi-step checkout wizards, and configurable dashboard widgets. Each tab or step stays isolated in its own .vue file while sharing one container.
flowchart TD
State["currentTab = shallowRef(HomeTab)"] --> Comp["component :is='currentTab'"]
Comp --> Keep["KeepAlive Caches Inactive State"]
<KeepAlive>By default, Vue unmounts and destroys the outgoing component whenever :is changes, resetting any typed form inputs inside it. Wrapping <component :is> inside <KeepAlive> caches inactive component instances in memory so users keep their state when switching tabs back and forth.
Use shallowRef() instead of ref() when storing imported component objects inside <script setup>. This tells Vue not to wrap the component definition itself in a deep reactive proxy.
<script setup>
import { shallowRef } from 'vue';
import HomeTab from './HomeTab.vue';
import ProfileTab from './ProfileTab.vue';
import SettingsTab from './SettingsTab.vue';
const tabs = [
{ name: 'Home', comp: HomeTab },
{ name: 'Profile', comp: ProfileTab },
{ name: 'Settings', comp: SettingsTab }
];
const currentTab = shallowRef(HomeTab);
</script>
<template>
<div class="tab-system">
<nav class="tab-buttons">
<button
v-for="tab in tabs"
:key="tab.name"
:class="{ active: currentTab === tab.comp }"
@click="currentTab = tab.comp"
>
{{ tab.name }}
</button>
</nav>
<KeepAlive include="ProfileTab,SettingsTab">
<component :is="currentTab" class="tab-content" />
</KeepAlive>
</div>
</template>
ref(): Passing a .vue component to ref(HomeTab) makes Vue deeply observe the entire component object, triggering a console performance warning. Always use shallowRef().<script setup>, imported components are local variables, not global names. Pass the imported component object :is="HomeTab" rather than a string 'HomeTab'.<KeepAlive> caching can increase memory usage. Set :max="5" or use include to cache only specific component names.<KeepAlive> component switches?Instead of onMounted and onUnmounted running every switch, Vue invokes onActivated() when the cached component appears and onDeactivated() when it hides.
<component :is>?Yes. Bind props and listeners directly on <component :is="currentTab" :user-id="id" @save="onSave" /> and Vue forwards them to the active component.
<component :is> render standard HTML tags too?Yes. Passing a tag string like :is="href ? 'a' : 'button'" renders a native <a> or <button> element dynamically.
Sign in to track your learning journey, earn industry-recognized certificates, and join our elite developer community.
Quick Access With