Skip to content

Tabs

The VTabs component is a tabs component that allows users to create a tabbed interface for their applications. It offers a range of customization options, including the ability to specify custom classes for active and inactive tabs, show scroll arrows, center the active tab, and render the tabs vertically. Additionally, it can be used as a header in a VCard component, and its style can be customized through the use of CSS variables.

Usage

Basic Usage

You can use the VTabs component in your template like so:

INFO

The VTabs component is registered globally when you install with @morpheme/ui. So you don't need to import it manually.

v-model

To specify the selected tab, you can use the v-model directive to bind the VTabs component to a data property:

vue
<script setup lang="ts">
import {ref} from 'vue';

const selectedTab = ref(0);
</script>

<template>
  <VTabs v-model="selectedTab" :items="items" />
</template>
<script setup lang="ts">
import {ref} from 'vue';

const selectedTab = ref(0);
</script>

<template>
  <VTabs v-model="selectedTab" :items="items" />
</template>

Custom Active Class

Define custom classes to apply when tab is active (active-class), inactive (inactive-class). Or add common custom class for tabs using default-class.

Show Arrow

Use showArrow to enable arrows to scroll the tab list. Only works if the list is scrollable.

Center Active

Use centerActive to allow the active tab to be position as at the center if possible. Works well when the list is scrollable.

Vertical

Use vertical to render the tabs vertically.

Tabs with Card

Use VTabs as header in a VCard component.

Custom Style

Override CSS variables for VTabs inline through style prop;

Removeable

Makes tab item removable. To automatically handle side effects, items props need to be bound as v-model, otherwise side effect can be applied manually by listening to either remove event or update:items.

Props

NameTypeDefault
modelValueNumber or String0
itemsArray<TabItem[]>[]
itemTextString'text'
color'primary' | 'secondary' | 'info' | 'warning' | 'success' | 'error''primary'
showArrowsBooleanfalse
centerActiveBooleanfalse
removeableBooleanfalse
verticalBooleanfalse
defaultWrapperClassString''
wrapperClassString''
hideSliderBooleanfalse
activeClassString''
inactiveClassString''
defaultClassString''
contentClassString''
sliderClassString''
removeIconString'heroicons:trash-solid'
removeIconSizeString'md'

Events

update:modelValue

Event emitted when active tab changes. It provides new value as argument.

vue
<script setup lang="ts">
import {ref} from 'vue';

const items = ref([
  {
    text: 'Item 1',
  },
  {
    text: 'Item 2',
  },
]);

const selected = ref(0);

const onChange = (value: number) => console.log(value);
</script>

<template>
  <VTabs v-model="selected" :items="items" @update:modelValue="onChange" />
</template>
<script setup lang="ts">
import {ref} from 'vue';

const items = ref([
  {
    text: 'Item 1',
  },
  {
    text: 'Item 2',
  },
]);

const selected = ref(0);

const onChange = (value: number) => console.log(value);
</script>

<template>
  <VTabs v-model="selected" :items="items" @update:modelValue="onChange" />
</template>

update:items

Event emitted when list items change. It provides new items as argument.

vue
<script setup lang="ts">
import {ref} from 'vue';

const items = ref([
  {
    text: 'Item 1',
  },
  {
    text: 'Item 2',
  },
]);

const selected = ref(0);

const onChange = (items: any[]) => console.log(items);
</script>

<template>
  <VTabs v-model="selected" :items="items" @update:items="onChange" />
</template>
<script setup lang="ts">
import {ref} from 'vue';

const items = ref([
  {
    text: 'Item 1',
  },
  {
    text: 'Item 2',
  },
]);

const selected = ref(0);

const onChange = (items: any[]) => console.log(items);
</script>

<template>
  <VTabs v-model="selected" :items="items" @update:items="onChange" />
</template>

change

Event emitted when active tab changes. It provides new value as argument.

vue
<script setup lang="ts">
import {ref} from 'vue';

const items = ref([
  {
    text: 'Item 1',
  },
  {
    text: 'Item 2',
  },
]);

const selected = ref(0);

const onChange = (value: number) => console.log(value);
</script>

<template>
  <VTabs v-model="selected" :items="items" @change="onChange" />
</template>
<script setup lang="ts">
import {ref} from 'vue';

const items = ref([
  {
    text: 'Item 1',
  },
  {
    text: 'Item 2',
  },
]);

const selected = ref(0);

const onChange = (value: number) => console.log(value);
</script>

<template>
  <VTabs v-model="selected" :items="items" @change="onChange" />
</template>

remove

Event emitted when user click the remove tab buttton.

vue
<script setup lang="ts">
import {ref} from 'vue';
import {useToast} from '@/composables';

const items = ref([
  {
    text: 'Item 1',
  },
  {
    text: 'Item 2',
  },
]);

const selected = ref(0);

const onRemove = (index: number) => {
  useToast()
    .confirm('Remove this tab?')
    .then(() => {
      items.value.splice(index, 1);
    });
};
</script>

<template>
  <VTabs v-model="selected" :items="items" removeable @remove="onRemove" />
</template>
<script setup lang="ts">
import {ref} from 'vue';
import {useToast} from '@/composables';

const items = ref([
  {
    text: 'Item 1',
  },
  {
    text: 'Item 2',
  },
]);

const selected = ref(0);

const onRemove = (index: number) => {
  useToast()
    .confirm('Remove this tab?')
    .then(() => {
      items.value.splice(index, 1);
    });
};
</script>

<template>
  <VTabs v-model="selected" :items="items" removeable @remove="onRemove" />
</template>

Slots

default

Define custom tab items rendering

Slot Props

PropValueDescription
onClickfunctionIndex of current tab
registerReffunctionCurrent tab original value

item

Define custom content for the tab.

Slot Props

PropValueDescription
indexnumberIndex of current tab
itemanyCurrent tab original value
valueanyCurrent tab label
activebooleanIndication whether current tab is active

previous

Define custom content for the previous arrow. Works when showArrows is set to true.

Slot Props

PropValueDescription
onClickfunctionCallback to handle click action

next

Define custom content for the next arrow. Works when showArrows is set to true.

Slot Props

PropValueDescription
onClickfunctionCallback to handle click action

prepend

Add an item to the start of the tab list

append

Add an item to the end of the tab list

CSS Variables

css
:root {
  --v-tabs-padding-x: theme('padding.4');
  --v-tabs-padding-y: theme('padding.2');
  --v-tabs-bg-color: theme('colors.transparent');
  --v-tabs-border-color: theme('colors.transparent');
  --v-tabs-border-radius: theme('borderRadius.DEFAULT');

  /* item */
  --v-tabs-item-padding-x: theme('padding.4');
  --v-tabs-item-padding-y: theme('padding.2');
  --v-tabs-item-font-size: theme('fontSize.base');
  --v-tabs-item-font-weight: theme('fontWeight.normal');
  --v-tabs-item-bg-color: theme('colors.transparent');
  --v-tabs-item-text-color: theme('colors.gray.800');
  --v-tabs-item-border-color: theme('colors.transparent');
  --v-tabs-item-border-radius: theme('borderRadius.DEFAULT');

  /* item active */
  --v-tabs-item-active-padding-x: var(--v-tabs-item-padding-x);
  --v-tabs-item-active-padding-y: var(--v-tabs-item-padding-y);
  --v-tabs-item-active-font-size: var(--v-tabs-item-font-size);
  --v-tabs-item-active-font-weight: theme('fontWeight.semibold');
  --v-tabs-item-active-bg-color: var(--v-tabs-item-bg-color);
  --v-tabs-item-active-text-color: var(--v-tabs-item-text-color);
  --v-tabs-item-active-border-color: var(--v-tabs-item-border-color);
  --v-tabs-item-active-border-radius: var(--v-tabs-item-border-radius);

  /* item hover */
  --v-tabs-item-hover-padding-x: var(--v-tabs-item-padding-x);
  --v-tabs-item-hover-padding-y: var(--v-tabs-item-padding-y);
  --v-tabs-item-hover-font-size: var(--v-tabs-item-font-size);
  --v-tabs-item-hover-font-weight: var(--v-tabs-item-font-weight);
  --v-tabs-item-hover-bg-color: var(--v-tabs-item-bg-color);
  --v-tabs-item-hover-text-color: var(--v-tabs-item-text-color);
  --v-tabs-item-hover-border-color: var(--v-tabs-item-border-color);
  --v-tabs-item-hover-border-radius: var(--v-tabs-item-border-radius);

  /* slider */
  --v-tabs-slider-height: 3px;
  --v-tabs-slider-width: 3rem;
  --v-tabs-slider-max-width: theme('maxWidth.md');
  --v-tabs-slider-bg-color: theme('colors.primary.500');
  --v-tabs-slider-border-color: theme('colors.primary.500');
  --v-tabs-slider-border-radius: theme('borderRadius.DEFAULT');
}
:root {
  --v-tabs-padding-x: theme('padding.4');
  --v-tabs-padding-y: theme('padding.2');
  --v-tabs-bg-color: theme('colors.transparent');
  --v-tabs-border-color: theme('colors.transparent');
  --v-tabs-border-radius: theme('borderRadius.DEFAULT');

  /* item */
  --v-tabs-item-padding-x: theme('padding.4');
  --v-tabs-item-padding-y: theme('padding.2');
  --v-tabs-item-font-size: theme('fontSize.base');
  --v-tabs-item-font-weight: theme('fontWeight.normal');
  --v-tabs-item-bg-color: theme('colors.transparent');
  --v-tabs-item-text-color: theme('colors.gray.800');
  --v-tabs-item-border-color: theme('colors.transparent');
  --v-tabs-item-border-radius: theme('borderRadius.DEFAULT');

  /* item active */
  --v-tabs-item-active-padding-x: var(--v-tabs-item-padding-x);
  --v-tabs-item-active-padding-y: var(--v-tabs-item-padding-y);
  --v-tabs-item-active-font-size: var(--v-tabs-item-font-size);
  --v-tabs-item-active-font-weight: theme('fontWeight.semibold');
  --v-tabs-item-active-bg-color: var(--v-tabs-item-bg-color);
  --v-tabs-item-active-text-color: var(--v-tabs-item-text-color);
  --v-tabs-item-active-border-color: var(--v-tabs-item-border-color);
  --v-tabs-item-active-border-radius: var(--v-tabs-item-border-radius);

  /* item hover */
  --v-tabs-item-hover-padding-x: var(--v-tabs-item-padding-x);
  --v-tabs-item-hover-padding-y: var(--v-tabs-item-padding-y);
  --v-tabs-item-hover-font-size: var(--v-tabs-item-font-size);
  --v-tabs-item-hover-font-weight: var(--v-tabs-item-font-weight);
  --v-tabs-item-hover-bg-color: var(--v-tabs-item-bg-color);
  --v-tabs-item-hover-text-color: var(--v-tabs-item-text-color);
  --v-tabs-item-hover-border-color: var(--v-tabs-item-border-color);
  --v-tabs-item-hover-border-radius: var(--v-tabs-item-border-radius);

  /* slider */
  --v-tabs-slider-height: 3px;
  --v-tabs-slider-width: 3rem;
  --v-tabs-slider-max-width: theme('maxWidth.md');
  --v-tabs-slider-bg-color: theme('colors.primary.500');
  --v-tabs-slider-border-color: theme('colors.primary.500');
  --v-tabs-slider-border-radius: theme('borderRadius.DEFAULT');
}

Standalone Installation

You can also install the Tabs component individually via @morpheme/tabs package:

bash
npm i @morpheme/tabs
npm i @morpheme/tabs
vue
<script setup lang="ts">
import VTabs from '@morpheme/tabs';
</script>

<template>
  <VTabs :items="items" />
</template>
<script setup lang="ts">
import VTabs from '@morpheme/tabs';
</script>

<template>
  <VTabs :items="items" />
</template>

Storybook

View Storybook documentation here.