I was implementing tabs for one of the projects I am currently working. I decided to make the tabs implementation a reusable component which I could import into any of the Vue pages/views that required the tab. This meant that the tabs component had to accept tab names & icons from the page (Parent Component) they are used in as props and a function to determine which tab is active and show the tab window for the active tab.
We are going to start by making a sample tab component (Child Component) then the Parent Component and implement a way for the Child Component to have access to the parent function/method.
You can create a new Vue file in your components folder and name it Tabs.vue
. Add the following code to it (note I am using TailwindCSS and some custom CSS classes for styling and Iconify to provide the SVG icons, you are free to implement your own CSS and use your own icons):
<!-- Tabs.vue -->
<script setup lang="ts">
import { Icon } from "@iconify/vue";
interface TabProps {
name: string;
value: number;
icon: string;
}
// The props we expect to get from the Parent Component which are the tabs and the active/selected tab
interface Props {
tabs: Array<TabProps>;
selectedTab: number;
}
const props = withDefaults(defineProps<Props>(), {
tabs: undefined,
selectedTab: 1,
});
</script>
<template>
<div class="w-full">
<ul class="flex flex-wrap -mb-px text-sm font-medium text-center">
<li class="mr-2" v-for="tab in tabs" :key="tab.value">
<a
href="#"
class="inline-flex p-4 border-b-2 border-transparent rounded-t-lg group"
:class=" tab.value == selectedTab
? 'active activeTab'
: 'inactiveTab'
"
aria-current="page"
@click="$emit('clicked', tab.value)"
>
<Icon
class="mr-2"
:icon="tab.icon"
height="24"
color="inherit"
/><span>{{ tab.name }}</span>
</a>
</li>
</ul>
</div>
</template>
<style scoped>
/* custom CSS classes for this tab element go here */
</style>
In the above code, specifically inside the <a>
element, we are using an $emit()
method and passing an argument on clicking (@click
) the element. By doing this we are emitting a custom event named clicked
and passing the argument to be used by the parent listening for the event.
All that's left to do in the Parent is to define a reactive variable to store the active tab, pass the tabs data to the Child and declare a function to use the argument passed to it to change the active tab. Based on the active tab, we can now show different tab windows. Have the following code in your Parent Component (remember to import your tabs component from where it is located):
<script setup>
import { ref } from "vue";
import Tabs from "../../Components/Tabs.vue";
let selectedTab = ref(1);
const page_tabs = [
{
name: "My Themes/Templates",
value: 1,
icon: "solar:folder-with-files-linear",
},
{
name: "Add Theme/Template",
value: 2,
icon: "material-symbols:add-box-rounded",
},
];
const changeTab = (value) => {
selectedTab.value = value;
};
</script>
<template>
<div class="w-full h-full pb-10 flex flex-col">
<!-- Page tabs -->
<Tabs
:tabs="page_tabs"
:selectedTab="selectedTab"
@clicked="changeTab"
/>
<!-- Tab windows go here -->
<div class="w-full py-10">
<!-- You can implement the tab windows here to appear based on selectedTab and conditionally adding the CSS classes -->
</div>
</div>
</template>
The Parent listens for the clicked
event emitted from the Child by using @event-name
, in this case @clicked
, when using the child component and passes the argument from the child into the changeTab()
function. That way, we are able to update which tab is active in selectedTab
variable which we still pass to the child so that the active tab is highlighted based on the CSS. From there we are also able to tell which tab window/page to display using the selectedTab
variable.
The resulting tab component looks something like this:
I hope that this article has shed some light on how you can call a function that exists in a Parent Component from the Child. If it is still not clear, you can let me know in the comments.