Tabs

Create a widget of tabbable panes of _local content_. The tabs component is built upon navs and cards internally, and provides full keyboard navigation control of the tabs.

For navigation based tabs (i.e. tabs that would change the URL), use the <BNav> component instead.

Basic usage

I'm the first tab

I'm the second tab

I'm a disabled tab!

HTML
template
<BTabs content-class="mt-3">
  <BTab title="First" active><p>I'm the first tab</p></BTab>
  <BTab title="Second"><p>I'm the second tab</p></BTab>
  <BTab title="Disabled" disabled><p>I'm a disabled tab!</p></BTab>
</BTabs>

INFO

You should supply each child <BTab> component a unique key value if dynamically adding or removing <BTab> components (i.e. v-if or for loops). The key attribute is a special Vue attribute, see the Vue doce for details.

Cards integration

Tabs support integrating with Bootstrap cards. Just add the card property to <BTabs> and place it inside a <BCard> component. Note that you should add theno-body prop on the <BCard> component in order to properly decorate the card header and remove the extra padding introduced by card-body.

Tab contents 1

Tab contents 2

HTML
template
<BCard no-body>
  <BTabs card>
    <BTab title="Tab 1" active>
      <BCardText>Tab contents 1</BCardText>
    </BTab>
    <BTab title="Tab 2">
      <BCardText>Tab contents 2</BCardText>
    </BTab>
  </BTabs>
</BCard>

When <BTabs> is in card mode, each <BTab> sub-component will automatically have the card-body class applied (this class provides the padding around the tab content). To disable the card-body class, set the no-body prop on the <BTab> sub component.

Image 21
Image 25
Image 26

This tab does not have the no-body prop set

Quis magna Lorem anim amet ipsum do mollit sit cillum voluptate ex nulla tempor. Laborum consequat non elit enim exercitation cillum aliqua consequat id aliqua. Esse ex consectetur mollit voluptate est in duis laboris ad sit ipsum anim Lorem. Incididunt veniam velit elit elit veniam Lorem aliqua quis ullamco deserunt sit enim elit aliqua esse irure.

HTML
template
<BCard no-body>
  <BTabs card>
    <BTab no-body title="Picture 1">
      <BCardImg bottom src="https://picsum.photos/600/200/?image=21" alt="Image 21" />
      <BCardFooter>Picture 1 footer</BCardFooter>
    </BTab>

    <BTab no-body title="Picture 2">
      <BCardImg bottom src="https://picsum.photos/600/200/?image=25" alt="Image 25" />
      <BCardFooter>Picture 2 footer</BCardFooter>
    </BTab>

    <BTab no-body title="Picture 3">
      <BCardImg bottom src="https://picsum.photos/600/200/?image=26" alt="Image 26" />
      <BCardFooter>Picture 3 footer</BCardFooter>
    </BTab>

    <BTab title="Text">
      <BCardTitle>This tab does not have the <code>no-body</code> prop set</BCardTitle>
      <BCardText>
        Quis magna Lorem anim amet ipsum do mollit sit cillum voluptate ex nulla tempor. Laborum
        consequat non elit enim exercitation cillum aliqua consequat id aliqua. Esse ex
        consectetur mollit voluptate est in duis laboris ad sit ipsum anim Lorem. Incididunt
        veniam velit elit elit veniam Lorem aliqua quis ullamco deserunt sit enim elit aliqua esse
        irure.
      </BCardText>
    </BTab>
  </BTabs>
</BCard>

TIP

Setting the no-body prop on <BTab> will have no affect when <BTabs> is not in card mode (as the card-body class is only set when in card mode).

Refer to the Cards documentation for more details on card components.

Pills variant

Tabs use the tabs styling by default. Just add pills property to <BTabs> for the pill style variant.

Tab contents 1

Tab contents 2

HTML
template
<BCard no-body>
  <BTabs pills card>
    <BTab title="Tab 1" active><BCardText>Tab contents 1</BCardText></BTab>
    <BTab title="Tab 2"><BCardText>Tab contents 2</BCardText></BTab>
  </BTabs>
</BCard>

Fill and justify

Force your <BTabs> controls to extend the full available width.

Fill

To proportionately fill all available space with your tab controls, set the fill prop. Notice that all horizontal space is occupied, but not every control has the same width.

I'm the first tab

I'm the second tab

I'm the tab with the very, very long title

I'm a disabled tab!

HTML
template
<BTabs content-class="mt-3" fill>
  <BTab title="First" active><p>I'm the first tab</p></BTab>
  <BTab title="Second"><p>I'm the second tab</p></BTab>
  <BTab title="Very, very long title"><p>I'm the tab with the very, very long title</p></BTab>
  <BTab title="Disabled" disabled><p>I'm a disabled tab!</p></BTab>
</BTabs>

Justified

For equal-width controls, use the justified prop instead. All horizontal space will be occupied by the controls, but unlike using fill above, every control will be the same width.

I'm the first tab

I'm the second tab

I'm the tab with the very, very long title

I'm a disabled tab!

HTML
template
<BTabs content-class="mt-3" justified>
  <BTab title="First" active><p>I'm the first tab</p></BTab>
  <BTab title="Second"><p>I'm the second tab</p></BTab>
  <BTab title="Very, very long title"><p>I'm the tab with the very, very long title</p></BTab>
  <BTab title="Disabled" disabled><p>I'm a disabled tab!</p></BTab>
</BTabs>

Alignment

To align your tab controls, use the align prop. Available values are start, center, end, between, around, and evenly.

I'm the first tab

I'm the second tab

I'm a disabled tab!

HTML
template
<BTabs content-class="mt-3" align="center">
  <BTab title="First" active><p>I'm the first tab</p></BTab>
  <BTab title="Second"><p>I'm the second tab</p></BTab>
  <BTab title="Disabled" disabled><p>I'm a disabled tab!</p></BTab>
</BTabs>

Bottom placement of tab controls

Visually move the tab controls to the bottom by setting the prop end.

Tab contents 1

Tab contents 2

HTML
template
<BCard no-body>
  <BTabs pills card end>
    <BTab title="Tab 1" active><BCardText>Tab contents 1</BCardText></BTab>
    <BTab title="Tab 2"><BCardText>Tab contents 2</BCardText></BTab>
  </BTabs>
</BCard>

WARNING

  • Bottom placement visually works best with the pills variant. When using the default tabs variant, you may want to provided your own custom styling classes, as Bootstrap v4 CSS assumes the tabs will always be placed on the top of the tabs content.
  • To provide a better user experience with bottom placed controls, ensure that the content of each tab pane is the same height and fits completely within the visible viewport, otherwise the user will need to scroll up to read the start of the tabbed content.

Vertical tabs

Have the tab controls placed on the lefthand side by setting the vertical prop to true. Vertical tabs work with or without card mode enabled.

Tab contents 1

Tab contents 2

Tab contents 3

HTML
template
<BCard no-body>
  <BTabs pills card vertical>
    <BTab title="Tab 1" active><BCardText>Tab contents 1</BCardText></BTab>
    <BTab title="Tab 2"><BCardText>Tab contents 2</BCardText></BTab>
    <BTab title="Tab 3"><BCardText>Tab contents 3</BCardText></BTab>
  </BTabs>
</BCard>

Visually move the tab controls to the right hand side by setting the end prop:

Tab contents 1

Tab contents 2

Tab contents 3

HTML
template
<BCard no-body>
  <BTabs pills card vertical end>
    <BTab title="Tab 1" active><BCardText>Tab contents 1</BCardText></BTab>
    <BTab title="Tab 2"><BCardText>Tab contents 2</BCardText></BTab>
    <BTab title="Tab 3"><BCardText>Tab contents 3</BCardText></BTab>
  </BTabs>
</BCard>

The width of the vertical tab controls will expand to fit the width of the tab title. To control the width, set a width utility class via the prop nav-wrapper-class. You can use values such as w-25 (25% width), w-50 (50% width), etc., or column classes such as col-2, col-3, etc.

Tab contents 1

Tab contents 2

Tab contents 3

HTML
template
<BCard no-body>
  <BTabs pills card vertical nav-wrapper-class="w-50">
    <BTab title="Tab 1" active><BCardText>Tab contents 1</BCardText></BTab>
    <BTab title="Tab 2"><BCardText>Tab contents 2</BCardText></BTab>
    <BTab title="Tab 3"><BCardText>Tab contents 3</BCardText></BTab>
  </BTabs>
</BCard>

Vertical placement visually works best with the pills variant. When using the default tabs variant, you may want to provided your own custom styling classes, as Bootstrap v4 CSS assumes the tab controls will always be placed on the top of the tabs content.

INFO

Overflowing text may occur if your width is narrower than the tab title. You may need additional custom styling.

Active classes

To apply classes to the currently active control or tab use the active-nav-item-class and active-tab-class props.

I'm the first tab

I'm the second tab

I'm a disabled tab!

HTML
template
<BTabs
  active-nav-item-class="font-weight-bold text-uppercase text-danger"
  active-tab-class="font-weight-bold text-success"
  content-class="mt-3"
>
  <BTab title="First" active><p>I'm the first tab</p></BTab>
  <BTab title="Second"><p>I'm the second tab</p></BTab>
  <BTab title="Disabled" disabled><p>I'm a disabled tab!</p></BTab>
</BTabs>

Fade animation

Fade is enabled by default when changing tabs. It can disabled with no-fade property.

Add tabs without content

If you want to add extra tabs that do not have any content, you can put them in tabs-start or tabs-end slot(s):

HTML
template
<BTabs>
  <!-- Add your b-tab components here -->
  <template #tabs-end>
    <BNavItem href="#" role="presentation" @click="() => {}">Another tab</BNavItem>
    <li role="presentation" class="nav-item align-self-center">Plain text</li>
  </template>
</BTabs>

Use the tabs-start slot to place extra tab buttons before the content tab buttons, and use the tabs-end slot to place extra tab buttons after the content tab buttons.

INFO

Extra (contentless) tab buttons should be a <BNavItem> or have a root element of <li> and class nav-item for proper rendering and semantic markup.

Add custom content to tab title

If you want to add custom content to tab title, like HTML code, icons, or another non-interactive Vue component, this possible by using title slot of <BTab>.

Tab contents 1

Tab contents 2

HTML
template
<BTabs>
  <BTab active>
    <template #title>
      <BSpinner type="grow" small /> I'm <i>custom</i> <strong>title</strong>
    </template>
    <p class="p-3">Tab contents 1</p>
  </BTab>

  <BTab>
    <template #title> <BSpinner type="border" small /> Tab 2 </template>
    <p class="p-3">Tab contents 2</p>
  </BTab>
</BTabs>

WARNING

Do not place interactive elements/components inside the title slot. The tab button is a link which does not support child interactive elements per the HTML5 spec.

Apply custom classes to the generated nav-tabs or pills

The tab selectors are based on Bootstrap v5's nav markup ( i.e. ul.nav > li.nav-item > a.nav-link). In some situations, you may want to add classes to the <li> (nav-item) and/or the <a> (nav-link) on a per tab basis. To do so, simply supply the classname to the title-item-class prop (for the <li> element) or title-link-class prop (for the <a> element). Value's can be passed as a string or array of strings.

INFO

The active class is automatically applied to the active tabs <a> element. You may need to accommodate your custom classes for this.

Tab contents 1
Tab contents 2
Tab contents 3
HTML
template
<template>
  <BCard no-body>
    <BTabs v-model="tabIndex" card>
      <BTab title="Tab 1" :title-link-class="linkClass[0]">Tab contents 1</BTab>
      <BTab title="Tab 2" :title-link-class="linkClass[1]">Tab contents 2</BTab>
      <BTab title="Tab 3" :title-link-class="linkClass[2]">Tab contents 3</BTab>
    </BTabs>
  </BCard>
</template>

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

const tabIndex = ref(0)

const linkClass = computed(() =>
  Array.from(Array(3).keys()).map((idx) =>
    tabIndex.value === idx ? ['bg-primary', 'text-light'] : ['bg-light', 'text-info']
  )
)
</script>

Lazy loading tab content

Sometimes it's preferred to load components & data only when activating a tab, instead of loading all tabs (and associated data) when rendering the <BTabs> set.

Individual <BTab> components can be lazy loaded via the lazy prop, which when set doesn't mount the content of the <BTab> until it is activated (shown), and will be un-mounted when the tab is deactivated (hidden):

DANGER

There is currently a bug in lazy that causes an infinite recursion

HTML
template
<BTabs content-class="mt-3">
  <!-- This tabs content will always be mounted -->
  <BTab title="Regular tab"><BAlert :model-value="true">I'm always mounted</BAlert></BTab>

  <!-- This tabs content will not be mounted until the tab is shown -->
  <!-- and will be un-mounted when hidden -->
  <BTab title="Lazy tab" lazy><BAlert :model-value="true">I'm lazy mounted!</BAlert></BTab>
</BTabs>

One can also make all tab's lazy by setting the lazy prop on the parent <BTabs> component:

HTML
template
<BTabs content-class="mt-3" lazy>
  <BTab title="Tab 1"><BAlert :model-value="true">I'm lazy mounted!</BAlert></BTab>
  <BTab title="Tab 2"><BAlert :model-value="true">I'm lazy mounted too!</BAlert></BTab>
</BTabs>

Keyboard navigation

Keyboard navigation is enabled by default for ARIA compliance with tablists when a tab button has focus.

Not yet implemented: no-key-nave prop and keyboard navigations as described below is not fully implemented
KeypressAction
Left or UpActivate the previous non-disabled tab
Right or DownActivate the next non-disabled tab
Shift+Left or Shift+UpActivate the first non-disabled tab
HomeActivate the first non-disabled tab
Shift+Right or Shift+DownActivate the last non-disabled tab
EndActivate the last non-disabled tab
TabMove focus to the active tab content
Shift+TabMove focus to the previous control on the page

Disable keyboard navigation by setting the prop no-key-nav. Behavior will now default to regular browser navigation with TAB key.

KeypressAction
TabMove to the next tab button or control on the page
Shift+TabMove to the previous tab button or control on the page
Enter or SpaceActivate current focused button's tab

Programmatically activating and deactivating tabs

Use the <BTabs> v-model to control which tab is active by setting the v-model to the index (zero-based) of the tab to be shown (see example below).

Alternatively, you can use the active prop on each <BTab> with the .sync modifier to activate the tab, or to detect if a particular tab is active.

Not yet implemented: .activate() and .deactivate()

Each <BTab>instance also provides two public methods to activate or deactivate the tab. The methods are.activate()and.deactivate(), respectively. If activation or deactivation fails (i.e. a tab is disabled or no tab is available to move activation to), then the currently active tab will remain active and the method will return false. You will need a reference to the <BTab> in order to use these methods.

Preventing a <BTab> from being activated

To prevent a tab from activating, simply set the disabled prop on the <BTab> component.

Alternatively, you can listen for the activate-tab event, which provides an option to prevent the tab from activating. The activate-tab event is emitted with three arguments:

  • newTabIndex: The index of the tab that is going to be activated
  • prevTabIndex: The index of the currently active tab
  • bvEvent: The BvEvent object. Call bvEvent.preventDefault() to prevent newTabIndex from being activated

For accessibility reasons, when using the activate-tab event to prevent a tab from activating, you should provide some means of notification to the user as to why the tab is not able to be activated. It is recommended to use the disabled attribute on the <BTab> component instead of using the activate-tab event (as disabled is more intuitive for screen reader users).

Advanced examples

External controls using v-model

I'm the first fading tab
I'm the second tab
I'm the card in tab
Sibzamini!
I'm the last tab
Current Tab: 0
HTML
vue
<template>
  <div>
    <!-- Tabs with card integration -->
    <BCard no-body>
      <BTabs v-model="tabIndex" small card>
        <BTab title="General">I'm the first fading tab</BTab>
        <BTab title="Edit profile">
          I'm the second tab
          <BCard>I'm the card in tab</BCard>
        </BTab>
        <BTab title="Premium Plan" disabled>Sibzamini!</BTab>
        <BTab title="Info">I'm the last tab</BTab>
      </BTabs>
    </BCard>

    <!-- Control buttons-->
    <div class="text-center">
      <BButtonGroup class="mt-2">
        <BButton @click="tabIndex--">Previous</BButton>
        <BButton @click="tabIndex++">Next</BButton>
      </BButtonGroup>

      <div class="text-muted">Current Tab: {{ tabIndex }}</div>
    </div>
  </div>
</template>

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

const tabIndex = ref(0)
</script>

Dynamic tabs + tabs-end slot

There are no open tabs
Open a new tab using the + button above.
HTML
vue
<template>
  <div>
    <BCard no-body>
      <BTabs card>
        <!-- Render Tabs, supply a unique `key` to each tab -->
        <BTab v-for="i in tabs" :key="'dyn-tab-' + i" :title="'Tab ' + i">
          Tab contents {{ i }}
          <BButton size="sm" variant="danger" class="float-right" @click="closeTab(i)">
            Close tab
          </BButton>
        </BTab>

        <!-- New Tab Button (Using tabs-end slot) -->
        <template #tabs-end>
          <BNavItem role="presentation" @click.prevent="newTab"><b>+</b></BNavItem>
        </template>

        <!-- Render this if no tabs -->
        <template #empty>
          <div class="text-center text-muted">
            There are no open tabs<br />
            Open a new tab using the <b>+</b> button above.
          </div>
        </template>
      </BTabs>
    </BCard>
  </div>
</template>

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

const tabCounter = ref(0)
const tabs = ref<number[]>([])

const newTab = () => {
  tabs.value.push(tabCounter.value++)
}

const closeTab = (i: number) => {
  tabs.value = tabs.value.filter((tab) => tab !== i)
}
</script>

Component Reference

PropTypeDefaultDescription
activebooleanfalse
button-idstringundefined
disabledbooleanfalse
idstringundefined
lazybooleanundefined
lazy-oncebooleanundefined
no-bodybooleanfalse
tagstring'div'
titlestringundefined
title-item-classClassValueundefined
title-link-attrsAttrsValueundefined
title-link-classClassValueundefined
NameScopeDescription
default
title
PropTypeDefaultDescription
active-idstringundefined
active-nav-item-classClassValueundefined
active-tab-classClassValueundefined
alignAlignmentJustifyContentundefined
cardbooleanfalse
content-classClassValueundefined
endbooleanfalse
fillbooleanfalse
idstringundefined
inactive-nav-item-classClassValueundefined
inactive-tab-classClassValueundefined
justifiedbooleanfalse
lazybooleanfalse
model-valuenumber-1
nav-classClassValueundefined
nav-item-classClassValueundefined
nav-wrapper-classClassValueundefined
no-fadebooleanfalse
no-key-navbooleanfalse
no-nav-stylebooleanfalse
pillsbooleanfalse
smallbooleanfalse
tab-classClassValueundefined
tagstring'div'
underlinebooleanfalse
verticalbooleanfalse
EventArgsDescription
activate-tab
v1: number
v2: number
v3: BvEvent
click
update:model-value
update:model-value: number
NameScopeDescription
empty
empty
tabs-end
tabs-start