Components

Bootstrap 5 Tabs: Examples, Pills, Vertical Tabs and JS

By the bootstrap.build team · 5 min read · Published

Bootstrap tabs let you show one panel of content at a time, with a row of tabs to switch between them, and no page reload. They are built from two parts: Bootstrap’s nav component for the tabs, and its tab JavaScript plugin to show and hide the panels. This guide gives you working tabs to copy, then covers pills and underline styles, vertical tabs, fade effects, opening a tab from JavaScript or a link, and the problems that stop tabs from switching. Everything is for Bootstrap 5.3. If the sections should stack and open one by one instead, especially on phones, use an accordion.

Working tabs to copy

<ul class="nav nav-tabs" id="productTabs" role="tablist">
  <li class="nav-item" role="presentation">
    <button class="nav-link active" id="overview-tab" data-bs-toggle="tab"
      data-bs-target="#overview" type="button" role="tab"
      aria-controls="overview" aria-selected="true">Overview</button>
  </li>
  <li class="nav-item" role="presentation">
    <button class="nav-link" id="specs-tab" data-bs-toggle="tab"
      data-bs-target="#specs" type="button" role="tab"
      aria-controls="specs" aria-selected="false">Specs</button>
  </li>
  <li class="nav-item" role="presentation">
    <button class="nav-link" id="reviews-tab" data-bs-toggle="tab"
      data-bs-target="#reviews" type="button" role="tab"
      aria-controls="reviews" aria-selected="false">Reviews</button>
  </li>
</ul>

<div class="tab-content border border-top-0 p-3" id="productTabsContent">
  <div class="tab-pane fade show active" id="overview" role="tabpanel"
    aria-labelledby="overview-tab" tabindex="0">Overview content.</div>
  <div class="tab-pane fade" id="specs" role="tabpanel"
    aria-labelledby="specs-tab" tabindex="0">Specs content.</div>
  <div class="tab-pane fade" id="reviews" role="tabpanel"
    aria-labelledby="reviews-tab" tabindex="0">Reviews content.</div>
</div>

Load Bootstrap’s JavaScript bundle before </body>, or the tabs won’t switch:

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.9/dist/js/bootstrap.bundle.min.js"></script>

How the pieces connect

Part Job
.nav.nav-tabs with role="tablist" The row of tabs
.nav-link with data-bs-toggle="tab" A tab; clicking it shows its panel
data-bs-target="#id" Which panel the tab opens
.tab-content The wrapper for all panels
.tab-pane with a matching id One panel
.active The tab and panel shown first
.fade and .show Optional fade animation

The first tab has .active and aria-selected="true"; its panel has .active and, with fade, .show. Bootstrap moves these classes and updates aria-selected as the user switches tabs. The role and aria attributes let screen readers announce the tabs, and the plugin adds keyboard support: the arrow keys move between tabs, and Home and End jump to the first and last.

Tab styles: tabs, pills, and underline

Change one class on the nav to change the look:

<ul class="nav nav-tabs">...</ul>      <!-- Classic folder tabs -->
<ul class="nav nav-pills">...</ul>     <!-- Filled buttons -->
<ul class="nav nav-underline">...</ul> <!-- Underline on the active tab (5.3) -->

To stretch tabs across the full width, add nav-fill (each tab sized to its text) or nav-justified (all tabs the same width):

<ul class="nav nav-pills nav-fill">...</ul>

Vertical tabs

Put pills in a column next to the content with flex utilities:

<div class="d-flex align-items-start gap-3">
  <div class="nav flex-column nav-pills" id="settingsTabs" role="tablist" aria-orientation="vertical">
    <button class="nav-link active" id="profile-tab" data-bs-toggle="pill"
      data-bs-target="#profile" type="button" role="tab"
      aria-controls="profile" aria-selected="true">Profile</button>
    <button class="nav-link" id="security-tab" data-bs-toggle="pill"
      data-bs-target="#security" type="button" role="tab"
      aria-controls="security" aria-selected="false">Security</button>
  </div>
  <div class="tab-content flex-grow-1">
    <div class="tab-pane fade show active" id="profile" role="tabpanel"
      aria-labelledby="profile-tab" tabindex="0">Profile settings.</div>
    <div class="tab-pane fade" id="security" role="tabpanel"
      aria-labelledby="security-tab" tabindex="0">Security settings.</div>
  </div>
</div>

With pills, use data-bs-toggle="pill" instead of "tab". To stack the tabs above the content on phones, change the wrapper to d-flex flex-column flex-md-row. The flexbox guide explains these classes.

Open a tab with JavaScript

Show a tab from your own code with the Tab plugin:

const tab = document.querySelector('#specs-tab')
bootstrap.Tab.getOrCreateInstance(tab).show()

To run code when the user switches tabs, listen for the shown.bs.tab event on the tab:

document.querySelectorAll('[data-bs-toggle="tab"]').forEach(tab => {
  tab.addEventListener('shown.bs.tab', event => {
    console.log('Now showing', event.target.dataset.bsTarget)
  })
})

show.bs.tab fires before the switch, and hide.bs.tab and hidden.bs.tab fire on the tab being left.

Bootstrap doesn’t read the URL hash, so page.html#reviews won’t open the Reviews tab on its own. Add a few lines that do it:

const hash = window.location.hash
if (hash) {
  const tab = document.querySelector(`[data-bs-target="${hash}"]`)
  if (tab) bootstrap.Tab.getOrCreateInstance(tab).show()
}

To keep the URL in step as users switch tabs, update it in the shown.bs.tab handler with history.replaceState(null, '', event.target.dataset.bsTarget).

Style the tabs

Tabs use CSS variables, so you can restyle them without Sass:

.nav-tabs {
  --bs-nav-link-color: #475569;
  --bs-nav-link-hover-color: #2f55ff;
  --bs-nav-tabs-link-active-color: #2f55ff;
  --bs-nav-tabs-link-active-border-color: #dee2e6 #dee2e6 #fff;
}

.nav-pills {
  --bs-nav-pills-link-active-bg: #2f55ff;
}

In Sass, $nav-link-color, $nav-tabs-link-active-color, $nav-pills-link-active-bg, and $nav-underline-border-width set the same values for the whole site. Pills follow $primary by default, so changing the primary color restyles them too.

Common problems

Clicking a tab does nothing. Bootstrap’s JavaScript isn’t loaded, or the markup uses Bootstrap 4’s data-toggle instead of data-bs-toggle. Load bootstrap.bundle.min.js and check the attributes.

The wrong panel opens, or none. The tab’s data-bs-target doesn’t match the panel’s id, including the #. IDs must also be unique on the page, so two tab sets can’t both use #overview.

All panels show at once. The panels are missing the .tab-pane class, or they aren’t inside a .tab-content wrapper.

The first panel is blank with fade. The active panel needs both .active and .show.

A chart or map in a hidden tab draws at the wrong size. It was drawn while its panel was hidden. Redraw it in a shown.bs.tab handler.

FAQ

How do I create tabs in Bootstrap 5?

Make a .nav.nav-tabs list of buttons with data-bs-toggle="tab" and data-bs-target="#panel-id", then a .tab-content wrapper holding .tab-pane elements with matching ids. Mark the first tab and panel .active, and load Bootstrap’s JavaScript bundle so the tabs switch.

Why are my Bootstrap tabs not working?

Usually Bootstrap’s JavaScript isn’t loaded, the markup uses Bootstrap 4’s data-toggle instead of data-bs-toggle, or a tab’s data-bs-target doesn’t match its panel’s id. Also check that panels have .tab-pane inside a .tab-content.

What is the difference between nav-tabs and nav-pills?

They are two looks for the same component. nav-tabs draws folder-style tabs with a border under the row, and nav-pills draws the active item as a filled button. Bootstrap 5.3 adds a third, nav-underline, which underlines the active tab.

How do I make vertical tabs in Bootstrap?

Add flex-column to a nav nav-pills list, put it beside the .tab-content in a d-flex wrapper, and use data-bs-toggle="pill" on each link. Add aria-orientation="vertical" to the nav for screen readers.

Bootstrap doesn’t read the URL hash itself. On page load, find the tab whose data-bs-target matches window.location.hash and call bootstrap.Tab.getOrCreateInstance(tab).show().

Try it in the builder

Make these changes visually with a live preview, then export clean Bootstrap Sass or CSS.

Open the Builder