Tabs
INFO
This page has not yet been updated to use the defineModel
macro, which was added in Vue 3.4. The techniques described here should still work, but in some cases it might be better to use defineModel
instead.
Tabs Example
There are various ways to split a tabbed UI up into components. In this example we're going to use two components, an outer container called tabs
and an inner container for each child called tab
:
vue
<template>
<basic-tabs>
<basic-tab title="First">
First tab content
</basic-tab>
<basic-tab title="Second">
<template v-for="i in 20">
Second tab content {{ i }}
<br v-if="i !== 20">
</template>
</basic-tab>
<basic-tab title="Third">
Third tab content
</basic-tab>
</basic-tabs>
</template>
<script setup>
import BasicTabs from './tabs.vue'
import BasicTab from './tab.vue'
</script>
The title
prop is used to pass the text to show on the button, with a slot for the content.
The code for tabs.vue
looks long, but a lot of it is CSS:
vue
<template>
<div class="tabs">
<div class="header">
<!-- TODO: key -->
<button
v-for="tab in tabs"
class="tab-button"
:class="{ active: tab === active }"
@click="activate(tab)"
>{{ tab.title }}</button>
</div>
<div class="body">
<slot />
</div>
</div>
</template>
<script setup>
import { computed, provide, reactive, ref } from 'vue'
const active = ref(null)
const tabs = reactive([])
const activate = tab => {
active.value = tab
}
provide('tabs-register', tab => {
tabs.push(tab)
if (!active.value) {
activate(tab)
}
return {
active: computed(() => active.value === tab),
unregister () {
const index = tabs.indexOf(tab)
tabs.splice(index, 1)
if (active.value === tab) {
activate(tabs[0])
}
}
}
})
</script>
<style scoped>
.tabs {
border: 1px solid #ccc;
display: flex;
flex-direction: column;
height: 200px;
width: 400px;
}
.header {
background-color: #e6f6ff;
border-bottom: 1px solid #ccc;
display: flex;
padding: 2px 2px 0;
}
.tab-button {
background: #fff;
border: 1px solid #ccc;
border-bottom: 0;
border-radius: 5px 5px 0 0;
cursor: pointer;
margin-right: 2px;
min-width: 70px;
padding: 5px;
}
.tab-button:hover {
background-color: #f7fcff;
}
.active {
background-color: #f7fcff;
margin-bottom: -1px;
padding-bottom: 6px;
}
.body {
background-color: #f7fcff;
display: flex;
flex: 1;
flex-direction: column;
overflow: auto;
}
</style>
tab.vue
doesn't need much code:
vue
<template>
<div v-if="active" class="tab">
<slot />
</div>
</template>
<script setup>
import { inject, onUnmounted, reactive, toRef } from 'vue'
const props = defineProps({
title: {
required: true,
type: String
}
})
const register = inject('tabs-register')
const tab = reactive({
title: toRef(props, 'title')
})
const { active, unregister } = register(tab)
onUnmounted(unregister)
</script>
Vue Patterns
See Coupled Components with provide
/inject
Libraries
Various libraries include a tabs component, including: