How to build tabs in Vue
Tab interfaces organize related content into separate panels that users can switch between, reducing page clutter and improving content discoverability. With over 12 years of Vue.js development experience since 2014 and as the creator of CoreUI, I’ve built tab components for countless enterprise dashboards. A Vue tabs component requires reactive state to track the active tab, methods to switch tabs, and conditional rendering to show the correct panel. This approach creates a flexible, reusable tabs system without external dependencies.
Build a tabs component using reactive state for the active tab and v-for for dynamic tab rendering.
<template>
<div class='tabs'>
<div class='tabs-header'>
<button
v-for='(tab, index) in tabs'
:key='index'
:class='["tab-button", { active: activeTab === index }]'
@click='activeTab = index'
>
{{ tab.title }}
</button>
</div>
<div class='tabs-content'>
<div
v-for='(tab, index) in tabs'
:key='index'
v-show='activeTab === index'
class='tab-panel'
>
<slot :name='`tab-${index}`'>
{{ tab.content }}
</slot>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const props = defineProps({
tabs: {
type: Array,
required: true
},
defaultTab: {
type: Number,
default: 0
}
})
const activeTab = ref(props.defaultTab)
</script>
<style scoped>
.tabs {
border: 1px solid #ddd;
border-radius: 4px;
}
.tabs-header {
display: flex;
border-bottom: 2px solid #ddd;
background: #f5f5f5;
}
.tab-button {
padding: 12px 24px;
background: transparent;
border: none;
cursor: pointer;
font-size: 14px;
color: #666;
transition: all 0.3s;
border-bottom: 2px solid transparent;
margin-bottom: -2px;
}
.tab-button:hover {
background: #e9e9e9;
color: #333;
}
.tab-button.active {
color: #007bff;
border-bottom-color: #007bff;
background: white;
}
.tabs-content {
padding: 20px;
}
.tab-panel {
animation: fadeIn 0.3s;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
</style>
Use it in your component:
<template>
<Tabs :tabs='tabsData' :default-tab='0'>
<template #tab-0>
<h3>Profile Content</h3>
<p>User profile information goes here</p>
</template>
<template #tab-1>
<h3>Settings Content</h3>
<p>User settings go here</p>
</template>
<template #tab-2>
<h3>Activity Content</h3>
<p>User activity log goes here</p>
</template>
</Tabs>
</template>
<script setup>
import { ref } from 'vue'
import Tabs from './Tabs.vue'
const tabsData = ref([
{ title: 'Profile' },
{ title: 'Settings' },
{ title: 'Activity' }
])
</script>
Best Practice Note
Use v-show instead of v-if for tab panels to preserve component state when switching tabs. For better performance with many tabs, use v-if and accept that component state resets. Add keyboard navigation (Arrow keys to navigate, Enter/Space to activate) for accessibility. Consider using URL hash or query parameters to make tabs bookmarkable. This is the pattern we use in CoreUI for Vue—building accessible tab components with smooth transitions and flexible content slots for complex enterprise admin interfaces.



