
Quick answer: Bootstrap 5 tabs are built from a ul.nav.nav-tabs whose links carry data-bs-toggle="tab" and a data-bs-target pointing at a matching div.tab-pane. Add active to the starting link and active show to its pane. Switch to nav-pills for a button-style look, or nav-fill and nav-justified to spread the items. The most common bug is an id mismatch between the toggle and its pane.
Part of our Bootstrap series. If the framework is not on your page yet, run through getting started with Bootstrap 5 first.
Working tabs in one block
The tab list and the panes must be siblings or share a common parent; each toggle names one pane. This example uses three tabs.
<ul class="nav nav-tabs" id="tabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="t1-tab" data-bs-toggle="tab"
data-bs-target="#t1" type="button" role="tab"
aria-controls="t1" aria-selected="true">Overview</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="t2-tab" data-bs-toggle="tab"
data-bs-target="#t2" type="button" role="tab"
aria-controls="t2" aria-selected="false">Pricing</button>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane fade show active" id="t1" role="tabpanel"
aria-labelledby="t1-tab">Overview content.</div>
<div class="tab-pane fade" id="t2" role="tabpanel"
aria-labelledby="t2-tab">Pricing content.</div>
</div>Use button elements rather than anchors: tabs switch content on the same page, so there is no URL to navigate to, and a button is announced correctly and can be activated with the keyboard. The fade class on a pane gives the cross-fade; remove it if you want an instant switch. The show class is what actually displays a pane, so a pane without show stays hidden even if it has active.
Styles and alignment
| Class on the nav | Result |
|---|---|
| nav-tabs | Underlined tab strip |
| nav-pills | Rounded button-style tabs |
| nav-underline | Modern underline that follows the active item |
| nav-fill | Items stretch to fill the width proportionally |
| nav-justified | All items share an equal width |
| flex-column | Vertical tabs down the side |
| nav-tabs + border-bottom-0 | Tabs that sit flush on top of a card |
| JavaScript option | Default | Effect |
|---|---|---|
| fade | true | Cross-fade between panes |
| tab | — | Element to activate on init |
| trigger | click | Set to hover to switch on mouse-over |
Tabs driven by JavaScript
Sometimes the tab should open because of an event elsewhere — a deep link, a form result, or a hash in the URL. Drive it with the Tab plugin instead of a click.
// Open the tab whose href matches the hash, e.g. #t2
const trigger = document.querySelector('[data-bs-target="' + location.hash + '"]');
if (trigger) bootstrap.Tab.getOrCreateInstance(trigger).show();
// React to a tab being shown
document.getElementById('t2').addEventListener('shown.bs.tab', (e) => {
console.log('now showing', e.target.id);
});The shown.bs.tab event fires after the transition finishes; there is also show.bs.tab before it starts. Both hand you event.target (the new pane) and event.relatedTarget (the previous one) when there was one. That pair is how you lazy-load a heavy pane the first time it is opened: listen for shown.bs.tab, check whether the target pane is still empty, and fetch its content then. Doing the work on demand keeps the first paint fast, and because the tab plugin only ever shows one pane at a time you never load a panel the reader will not look at.
Common mistakes
- id mismatch.
data-bs-target="#t1"needs a pane withid="t1", and the pane’saria-labelledbyshould point back at the tab’s id. - Forgetting
show. A pane with onlyactivestays invisible; the visible one needsactive show. - Using anchors for in-page tabs. They add history entries and can scroll the page; buttons are cleaner.
- Nesting the content far from the nav. Keep the tab list and
tab-contentas siblings so the plugin can manage them together. - Tabs inside a form. A bare button inside a form submits it — always add
type="button".
Tabs often hold the same content people reach through a dropdown menu or an accordion on small screens. Pair them with the layouts in the Bootstrap 5 grid guide to keep panes aligned, and with the navbar guide when the tabs live inside navigation.
Building an interface with many states and want it reviewed? Ampersand Academy teaches front-end development one-to-one, using your own project as the curriculum.
Frequently asked questions
Why are my Bootstrap tabs not switching?
The data-bs-target value must match the id of the tab pane, and the pane must carry the show class when active. Also confirm the Bootstrap bundle JavaScript is loaded on the page.
What is the difference between nav-tabs and nav-pills in Bootstrap 5?
nav-tabs draws an underlined tab strip, while nav-pills renders rounded button-style items. Both use the same tab plugin and the same data-bs-toggle attributes.
How do I make Bootstrap tabs equal width?
Add nav-fill to stretch items proportionally, or nav-justified to give every item the same width. Both work with nav-tabs and nav-pills.
How do I activate a Bootstrap tab on page load?
Add active to the link and active show to its pane in your markup, or call bootstrap.Tab.getOrCreateInstance on the trigger and then the show method.
Can Bootstrap tabs switch on hover instead of click?
Yes. Initialize the Tab plugin with the trigger option set to hover. For keyboard and touch users, click remains the safer default.
