Creating a Reusable Tab Component in Vue
📣 Sponsor
One of the most frequently used UX elements on the web, or on personal devices are tabs. In this guide, let’s look at how you can make a reusable tabs component using the Vue Composition API. This set of tabs can be imported, used and styled easily in any project you like, and means you never have to think twice when you want to implement your own set of tabs.
You can find the source code for Vue Tabs on GitHub via this link!
New to Vue?
If you’re new to Vue, I’d suggest checking out my guide on getting started and making your first Vue app before reading this guide.
Creating a Reusable Vue Tabs Component
Tabs essentially consist of two parts - the tab itself, and a container which houses all the tabs. Therefore, to get started, I’m going to make two files in our Vue file structure - Tab.vue
and Tabs.vue
. Our basic file structure for this component is going to look like this:
|- src
|-- App.vue
|-- main.js
|-- components
|--- Tabs.vue
|--- Tab.vue
|- index.html
|- README.md
|- package.json
So let’s start by creating our Tab.vue
file. We are using the composition API
to make these tabs, so our code is a little simpler than if we used the Options API. You can learn the difference between the Composition and Options API here.
Tab.vue
<script setup>
import { ref, onMounted } from 'vue';
const props = defineProps([ 'active' ]);
</script>
<div class="tab" :class="(active == 'true') ? 'active' : ''">
<slot></slot>
</div>
<style>
.tab {
display: none;
}
.tab.active {
display: block;
}
</style>
The code for a single tab is relatively simple. Our tabs are going to have one property - active
. This property will define whether a tab should show or not. Inside our tab, we put a slot
. This is so we can define custom content for our Tab
whenever we get round to defining it. Finally, we have some CSS to show or hide tabs, based on if they are active or not.
Now that we have a Tab
, let’s try making a container for multiple tabs, which I’ve put in the Tabs.vue
file.
Tabs.vue
Let’s start by defining our script. The interesting problem we need to solve here is tabs consist of the tabs themselves, and then the tab which you click on to access that particular tab. Therefore, we need to pull our child tabs up, and create headers for each. Let’s start by defining our script to do that.
<script setup>
import { ref, onMounted } from 'vue';
const props = defineProps([ 'customClass' ]);
// Defining our reactive `data()` properties
let tabContainer = ref(null);
let tabHeaders = ref(null);
let tabs = ref(null);
let activeTabIndex = ref(0);
onMounted(() => {
tabs.value = [ ...tabContainer.value.querySelectorAll('.tab') ];
for(let x of tabs.value) {
if(x.classList.contains('active')) {
activeTabIndex = tabs.value.indexOf(x);
}
}
});
</script>
In essence, we have to gather our tabs from the tab container through a reference. We’ll attach that ref
to our template tag later. For now, let’s just define the variable. Then we’ll need someway to get all the different “tab headers”, so let’s define that variable now. We’ll also need somewhere to store our tabs, which will be in tabs
.
Finally, we need a way to track which tab is active, which will be our activeTabIndex
. In the composition API, we use ref
. If you’re familiar with the Options API, most of these variables would’ve gone in the data()
function instead.
When we mount our component, we run `onMounted(), and query all tabs. This lets us do two things:
- We can now get access to all of our tabs, in one simple variable.
- We can figure out which tab is currently active, and set the variable correctly.
Changing tabs
We’ll also need one additional function, for when the user changes tabs. This function just hides all the currently active elements, and then adds active
classes to the headers and tabs which are active.
const changeTab = (index) => {
// Set activeTabIndex item to the index of the element clicked
activeTabIndex = index;
// Remove any active classes
for(let x of [...tabs.value, ...tabHeaders.value]) {
x.classList.remove('active')
}
// Add active classes where appropriate, to the active elements!
tabs.value[activeTabIndex].classList.add('active')
tabHeaders.value[activeTabIndex].classList.add('active')
}
Putting it in our template
Now that we have our script setup, let’s make our template and style. Since we’ve gathered all of our tabs into the tabs
variable, we’ll loop over it using a v-for
. We’ll also append on the click event to each of those tab headers.
Note: this is also where we add our references. So our variable, tabContainer
, is now tied to #tabs-container
, since we added the ref tabContainer
to it. The same goes for tabHeaders
.
<div id="tabs-container" :class="customClass" ref="tabContainer">
<div id="tab-headers">
<ul>
<!-- this shows all of the titles -->
<li v-for="(tab, index) in tabs" :key="index" :class="activeTabIndex == index ? 'active' : ''" @click="changeTab(index)" ref="tabHeaders"></li>
</ul>
</div>
<!-- this is where the tabs go, in this slot -->
<div id="active-tab">
<slot></slot>
</div>
</div>
<style>
#tab-headers ul {
margin: 0;
padding: 0;
display: flex;
border-bottom: 2px solid #ddd;
}
#tab-headers ul li {
list-style: none;
padding: 1rem 1.25rem;
position: relative;
cursor: pointer;
}
#tab-headers ul li.active {
color: #008438;
font-weight: bold;
}
#tab-headers ul li.active:after {
content: '';
position: absolute;
bottom: -2px;
left: 0;
height: 2px;
width: 100%;
background: #008438;
}
#active-tab, #tab-headers {
width: 100%;
}
#active-tab {
padding: 0.75rem;
}
</style>
Pulling it all together into a single view
Now that we have our two components, we can implement our tabs anywhere we like by importing both and using them. We need to give every Tab
a header attribute, which will act as the title for the tab that you click on. Adding tabs to your site then looks like this:
<script setup>
import Tabs from './components/Tabs.vue'
import Tab from './components/Tab.vue'
</script>
<Tabs>
<Tab active="true" title="First Tab">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce gravida purus vitae vulputate commodo.
</Tab>
<Tab title="Second Tab">
Cras scelerisque, dolor vitae suscipit efficitur, risus orci sagittis velit, ac molestie nulla tortor id augue.
</Tab>
<Tab title="Third Tab">
Morbi posuere, mauris eu vehicula tempor, nibh orci consectetur tortor, id eleifend dolor sapien ut augue.
</Tab>
<Tab title="Fourth Tab">
Aenean varius dui eget ante finibus, sit amet finibus nisi facilisis. Nunc pellentesque, risus et pretium hendrerit.
</Tab>
</Tabs>
And just like that, we have tabs we can use anywhere. You can view the demo below:
Conclusion and Source Code
Implementing Vue tabs is pretty straightforward, and by importing these two components into any project you’ll have instantly functional tabs. You can find the source code for Vue Tabs on GitHub via this link.
I hope you’ve enjoyed this guide. If you want more, you can find my other Vue tutorials and guides here.
More Tips and Tricks for Vue
- Creating your first Vue App
- How to Watch for Nested Changes in Vue
- Getting Started with Storing Vue Data in Vuex
- The Difference Between the Composition API and Options API in Vue
- Creating a Websocket Server in Vue.js
- How to use v-for in Vue
- Using .env Environment Variables in Vue
- A Guide on How to use Emit in Vue
- How v-if and v-else work in Vue
- The difference between created and mounted in Vue