The .nav component is the base for every horizontal or vertical set of links in Bootstrap, including the links inside a navbar. Adding .nav-tabs, .nav-pills or .nav-underline changes the look, and the tab JavaScript plugin turns any nav into a set of switchable panels. After this lesson you will be able to build styled navigation, accessible tabbed interfaces, and control them from JavaScript.
<ul class="nav">
<li class="nav-item"><a class="nav-link active" aria-current="page" href="#">Active</a></li>
<li class="nav-item"><a class="nav-link" href="#">Link</a></li>
<li class="nav-item"><a class="nav-link disabled" aria-disabled="true">Disabled</a></li>
</ul>.nav is a flex container, so all flex utilities apply:
| Goal | Classes |
|---|---|
| Center the links | nav justify-content-center |
| Right-align | nav justify-content-end |
| Vertical list | nav flex-column |
| Equal-width links | nav nav-fill or nav nav-justified |
| Vertical on phones, horizontal from md | nav flex-column flex-md-row |
.nav-fill gives each item proportional width based on content; .nav-justified gives every item exactly the same width.
.nav-tabs: classic tabbed look with a bottom border; the active tab appears attached to the content..nav-pills: rounded, filled background on the active item..nav-underline: a slim underline on the active item, added in Bootstrap 5.3.<ul class="nav nav-pills">
<li class="nav-item"><a class="nav-link active" href="#">Day</a></li>
<li class="nav-item"><a class="nav-link" href="#">Week</a></li>
<li class="nav-item"><a class="nav-link" href="#">Month</a></li>
</ul>A nav item may also contain a dropdown: give the .nav-link data-bs-toggle="dropdown" and add a .dropdown-menu inside the .nav-item.dropdown.
The tab plugin needs three things: triggers with data-bs-toggle="tab" (or "pill") whose data-bs-target or href points at a pane, a .tab-content wrapper, and .tab-pane panels. Exactly one pane starts with active:
<ul class="nav nav-pills mb-3" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" data-bs-toggle="pill" data-bs-target="#overview" type="button" role="tab">Overview</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" data-bs-toggle="pill" data-bs-target="#specs" type="button" role="tab">Specs</button>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane fade show active" id="overview" role="tabpanel" tabindex="0">Overview text</div>
<div class="tab-pane fade" id="specs" role="tabpanel" tabindex="0">Specifications</div>
</div>Notes on the markup:
<button> triggers when tabs switch in-page content; use <a href="#pane"> only if you also want the URL fragment..fade animates the switch; the active pane also needs .show.aria-selected and keyboard navigation (arrow keys move between tabs) as long as role="tablist", role="tab" and role="tabpanel" are present.Vertical tabs are the same markup with .nav.flex-column in one grid column and .tab-content in another; add aria-orientation="vertical" to the tab list.
import { Tab } from "bootstrap";
// Activate a tab programmatically
const specsTab = new Tab(document.querySelector('[data-bs-target="#specs"]'));
specsTab.show();
// Or get an existing instance without creating a second one
Tab.getOrCreateInstance(document.querySelector("#general-tab")).show();
// Open the tab named in the URL hash on load
const hash = window.location.hash;
if (hash) {
const trigger = document.querySelector(`[data-bs-target="${hash}"]`);
if (trigger) Tab.getOrCreateInstance(trigger).show();
}Events fire on the trigger element in this order: hide.bs.tab (current tab), show.bs.tab (new tab), hidden.bs.tab, shown.bs.tab. Each event exposes event.target (the new tab) and event.relatedTarget (the previous one):
document.querySelectorAll('[data-bs-toggle="tab"]').forEach((el) => {
el.addEventListener("shown.bs.tab", (event) => {
history.replaceState(null, "", event.target.dataset.bsTarget);
});
});.active. Both are visible until the first click; only the initial pane should have active and show.fade without show on the active pane. The pane exists but has opacity: 0, so it looks empty.bootstrap.bundle.min.js or the Tab module.id matched by exactly one trigger.Which attribute connects a tab trigger to its pane?
.nav with .nav-item and .nav-link is the base; .nav-tabs, .nav-pills and .nav-underline change the style..nav-fill / .nav-justified control alignment and width; flex-column makes vertical navs.data-bs-toggle="tab|pill" triggers, .tab-content and .tab-pane panels, with one pane marked active show.aria-selected updates.Tab.getOrCreateInstance(el).show() and the show/shown/hide/hidden.bs.tab events control tabs from JavaScript.Next lesson: Dropdowns - toggleable menus with headers, dividers, forms, dark variants and Popper-driven placement.