Element Plus Tabs Component
Divide data collections which are related yet belong to different types.
When to Invoke
Invoke this skill when:
- User needs to create tabbed content
- User wants to add/close tabs dynamically
- User needs card or border-card style tabs
- User wants to position tabs (top, left, right, bottom)
- User asks about tab switching with confirmation
Features
- Multiple Styles: Default, card, border-card
- Tab Position: Top, bottom, left, right
- Dynamic Tabs: Add and remove tabs
- Custom Labels: Custom content in tab labels
- Lazy Rendering: Lazy load tab content
- Before Leave Hook: Confirm before switching tabs
API Reference
Tabs Attributes
| Name |
Description |
Type |
Default |
| model-value / v-model |
binding value, name of selected tab |
string | number |
— |
| default-value |
The value of the tab that should be active when initially rendered |
string | number |
— |
| type |
type of Tab |
'' | 'card' | 'border-card' |
'' |
| closable |
whether Tab is closable |
boolean |
false |
| addable |
whether Tab is addable |
boolean |
false |
| editable |
whether Tab is addable and closable |
boolean |
false |
| tab-position |
position of tabs |
'top' | 'right' | 'bottom' | 'left' |
top |
| stretch |
whether width fits container |
boolean |
false |
| before-leave |
hook before switching tab |
(activeName, oldActiveName) => Awaitable<void | boolean> |
() => true |
| tabindex |
tabs tabindex |
string | number |
0 |
Tabs Events
| Name |
Description |
Parameters |
| tab-click |
triggers when a tab is clicked |
(pane, ev) |
| tab-change |
triggers when activeName changes |
(name) |
| tab-remove |
triggers when remove button is clicked |
(name) |
| tab-add |
triggers when add button is clicked |
() |
| edit |
triggers when add or remove is clicked |
(paneName, action) |
Tabs Slots
| Name |
Description |
Subtags |
| default |
customize default content |
Tab-pane |
| add-icon |
customize add button icon |
— |
Tabs Exposes
| Name |
Description |
Type |
| currentName |
current active pane name |
Ref<TabPaneName> |
| tabNavRef |
tab-nav component instance |
Ref<TabNavInstance | undefined> |
Tab-pane Attributes
| Name |
Description |
Type |
Default |
| label |
title of the tab |
string |
'' |
| disabled |
whether Tab is disabled |
boolean |
false |
| name |
identifier corresponding to Tabs name |
string | number |
— |
| closable |
whether Tab is closable |
boolean |
false |
| lazy |
whether Tab is lazily rendered |
boolean |
false |
Tab-pane Slots
| Name |
Description |
| default |
Tab-pane's content |
| label |
Tab-pane's label |
Usage Examples
Basic Usage
<template>
<el-tabs v-model="activeName" @tab-click="handleClick">
<el-tab-pane label="User" name="first">User</el-tab-pane>
<el-tab-pane label="Config" name="second">Config</el-tab-pane>
<el-tab-pane label="Role" name="third">Role</el-tab-pane>
<el-tab-pane label="Task" name="fourth">Task</el-tab-pane>
</el-tabs>
</template>
<script setup>
import { ref } from 'vue'
const activeName = ref('first')
const handleClick = (tab, event) => {
console.log(tab, event)
}
</script>
Card Style
<template>
<el-tabs v-model="activeName" type="card">
<el-tab-pane label="User" name="first">User</el-tab-pane>
<el-tab-pane label="Config" name="second">Config</el-tab-pane>
<el-tab-pane label="Role" name="third">Role</el-tab-pane>
</el-tabs>
</template>
<script setup>
import { ref } from 'vue'
const activeName = ref('first')
</script>
Border Card
<template>
<el-tabs type="border-card">
<el-tab-pane label="User">User</el-tab-pane>
<el-tab-pane label="Config">Config</el-tab-pane>
<el-tab-pane label="Role">Role</el-tab-pane>
</el-tabs>
</template>
Tab Position
<template>
<el-radio-group v-model="tabPosition" style="margin-bottom: 32px">
<el-radio-button value="top">Top</el-radio-button>
<el-radio-button value="right">Right</el-radio-button>
<el-radio-button value="bottom">Bottom</el-radio-button>
<el-radio-button value="left">Left</el-radio-button>
</el-radio-group>
<el-tabs :tab-position="tabPosition" style="height: 200px">
<el-tab-pane label="User">User</el-tab-pane>
<el-tab-pane label="Config">Config</el-tab-pane>
<el-tab-pane label="Role">Role</el-tab-pane>
<el-tab-pane label="Task">Task</el-tab-pane>
</el-tabs>
</template>
<script setup>
import { ref } from 'vue'
const tabPosition = ref('left')
</script>
Custom Tab Label
<template>
<el-tabs type="border-card">
<el-tab-pane>
<template #label>
<span>
<el-icon><Calendar /></el-icon>
Route
</span>
</template>
Route
</el-tab-pane>
<el-tab-pane label="Message">Message</el-tab-pane>
<el-tab-pane label="Config">Config</el-tab-pane>
</el-tabs>
</template>
<script setup>
import { Calendar } from '@element-plus/icons-vue'
</script>
Dynamic Tabs
<template>
<el-tabs
v-model="editableTabsValue"
type="card"
editable
@edit="handleTabsEdit"
>
<el-tab-pane
v-for="item in editableTabs"
:key="item.name"
:label="item.title"
:name="item.name"
>
{{ item.content }}
</el-tab-pane>
</el-tabs>
</template>
<script setup>
import { ref } from 'vue'
let tabIndex = 2
const editableTabsValue = ref('2')
const editableTabs = ref([
{ title: 'Tab 1', name: '1', content: 'Tab 1 content' },
{ title: 'Tab 2', name: '2', content: 'Tab 2 content' }
])
const handleTabsEdit = (targetName, action) => {
if (action === 'add') {
const newTabName = `${++tabIndex}`
editableTabs.value.push({
title: 'New Tab',
name: newTabName,
content: 'New Tab content'
})
editableTabsValue.value = newTabName
} else if (action === 'remove') {
const tabs = editableTabs.value
let activeName = editableTabsValue.value
if (activeName === targetName) {
tabs.forEach((tab, index) => {
if (tab.name === targetName) {
const nextTab = tabs[index + 1] || tabs[index - 1]
if (nextTab) {
activeName = nextTab.name
}
}
})
}
editableTabsValue.value = activeName
editableTabs.value = tabs.filter((tab) => tab.name !== targetName)
}
}
</script>
Before Leave Hook
<template>
<el-tabs v-model="activeName" :before-leave="beforeLeave">
<el-tab-pane label="User" name="first">User</el-tab-pane>
<el-tab-pane label="Config" name="second">Config</el-tab-pane>
</el-tabs>
</template>
<script setup>
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
const activeName = ref('first')
const beforeLeave = async (activeName, oldActiveName) => {
if (oldActiveName === 'first') {
const confirmed = await ElMessageBox.confirm(
'Are you sure you want to leave? Unsaved changes will be lost.',
'Warning',
{ type: 'warning' }
).catch(() => false)
return confirmed
}
return true
}
</script>
Lazy Loading
<template>
<el-tabs v-model="activeName">
<el-tab-pane label="Tab 1" name="1">Content 1</el-tab-pane>
<el-tab-pane label="Tab 2" name="2" lazy>
<!-- This content will only be rendered when tab is active -->
<ExpensiveComponent />
</el-tab-pane>
</el-tabs>
</template>
Custom Add Button Icon
<template>
<el-tabs type="card" addable>
<template #add-icon>
<el-icon><Plus /></el-icon>
</template>
<el-tab-pane label="Tab 1">Content 1</el-tab-pane>
<el-tab-pane label="Tab 2">Content 2</el-tab-pane>
</el-tabs>
</template>
<script setup>
import { Plus } from '@element-plus/icons-vue'
</script>
Common Issues
1. Tab Content Not Updating
Use :key on tab-pane for dynamic content:
<el-tab-pane :key="tabKey" :label="label">
{{ content }}
</el-tab-pane>
2. Height Issues with Left/Right Position
Set a fixed height:
<el-tabs tab-position="left" style="height: 400px">
<!-- tabs -->
</el-tabs>
3. Tab Not Switching
Ensure v-model value matches tab name:
<el-tabs v-model="activeName">
<el-tab-pane name="first"> <!-- activeName should be 'first' -->
</el-tabs>
Best Practices
- Use lazy loading: Use
lazy for expensive tab content
- Unique names: Ensure each tab-pane has a unique name
- Before leave hook: Use before-leave for unsaved changes warning
- Card type for dynamic: Use card type when adding/removing tabs
- Consistent styling: Use consistent tab type throughout the app