Next.js Bootstrap Tabs
Next.js Bootstrap tabs for the App Router: client components a server component page renders directly, with keyboard support and vertical or colored styles.
This component is only available in AdminKit PRO. Learn more.
When a page has more to show than fits comfortably, tabs let the user switch between panes instead of scrolling. AdminKit gives you Bootstrap's tabs as three components: TabContainer, TabLink and TabPane. They switch panes with React state instead of Bootstrap's JavaScript. Tabs are part of the PRO version of AdminKit for Next.js.
In the App Router, pages are server components: they render on the server and have no state. The tab components are client components, files that start with "use client", so they handle the state themselves. That means a server component page can render them directly, without a wrapper of your own.
You write the nav and the panes in Bootstrap's markup. A TabLink and a TabPane with the same eventKey belong together. TabContainer keeps the active key and adds no element to the page. All three are in src/components/bootstrap/tabs.tsx.
Example
Every prop in this example is a string, so it works straight in a server component page. The demo's tabs page does the same:
import { TabContainer, TabLink, TabPane } from "@/components/bootstrap/tabs";
import { pageMetadata } from "@/lib/site";
export const metadata = pageMetadata("/projects");
export default function ProjectsPage() {
return (
<div className="tab">
<TabContainer defaultActiveKey="overview">
<ul className="nav nav-tabs" role="tablist">
<li className="nav-item" role="presentation">
<TabLink eventKey="overview" className="nav-link">Overview</TabLink>
</li>
<li className="nav-item" role="presentation">
<TabLink eventKey="activity" className="nav-link">Activity</TabLink>
</li>
<li className="nav-item" role="presentation">
<TabLink eventKey="files" className="nav-link">Files</TabLink>
</li>
</ul>
<div className="tab-content">
<TabPane eventKey="overview">
<p>The status, owner and due date of the project.</p>
</TabPane>
<TabPane eventKey="activity">
<p>Comments and changes from the last 30 days.</p>
</TabPane>
<TabPane eventKey="files">
<p>Documents and images shared with the team.</p>
</TabPane>
</div>
</TabContainer>
</div>
);
}The server renders every pane and shows the defaultActiveKey one. The tabs respond as soon as the page hydrates, that is, once the browser has taken over the HTML Next.js built.
.tab is AdminKit's tab style: borderless tabs above a card-like .tab-content. Without it you get Bootstrap's plain .nav-tabs. For pills, put nav nav-pills on the list.
Props
TabContainer:
| Prop | Type | Default | Description |
|---|---|---|---|
defaultActiveKey | string | The tab that is active first | |
activeKey | string | Controlled: the active tab; use it with onSelect | |
onSelect | (key: string) => void | Called with the eventKey of the tab that was selected |
TabLink:
| Prop | Type | Default | Description |
|---|---|---|---|
eventKey | string | The key of the pane it shows | |
as | ElementType | "a" | The element: an a with href="#", or "button" (gets type="button") |
disabled | boolean | Adds .disabled; a click does nothing and the arrow keys skip it |
TabPane:
| Prop | Type | Default | Description |
|---|---|---|---|
eventKey | string | The key of its tab | |
fade | boolean | true | Fades the pane in (.fade); false switches at once |
Other props, such as className, go to the TabLink and TabPane elements. The id, aria-controls and aria-labelledby attributes that connect a tab and its pane come from useId. That way the server HTML and the browser agree on them.
A server component can only pass serializable props: values, such as strings, that Next.js can send to the browser as data. Functions and components are not, so onSelect, onClick and an as component need a client component. A tag name such as "button" is a string, so as="button" works from a server component.
Controlled tabs
Sometimes a button outside the nav should open a tab. Then a client component owns the active tab, through activeKey and onSelect:
"use client";
import { useState } from "react";
import { TabContainer, TabLink, TabPane } from "@/components/bootstrap/tabs";
export function ProjectTabs() {
const [tab, setTab] = useState("overview");
return (
<>
<button type="button" className="btn btn-light mb-3" onClick={() => setTab("files")}>
Show files
</button>
<TabContainer activeKey={tab} onSelect={setTab}>
{/* the nav and the panes */}
</TabContainer>
</>
);
}Colored and vertical tabs
Color and direction are classes on the wrapper, so a server component can set them. A theme color (tab-primary, tab-success, tab-danger, ...) colors the active tab and the content. tab-vertical moves the tabs to the left. The demo panes use .tab-title for their heading.
<div className="tab tab-vertical tab-success">
<TabContainer defaultActiveKey="plan">
<ul className="nav nav-tabs" role="tablist">
<li className="nav-item" role="presentation">
<TabLink eventKey="plan" className="nav-link">Plan</TabLink>
</li>
<li className="nav-item" role="presentation">
<TabLink eventKey="usage" className="nav-link">Usage</TabLink>
</li>
</ul>
<div className="tab-content">
<TabPane eventKey="plan" fade={false}>
<h2 className="h4 tab-title">Plan</h2>
<p>Your current plan and its renewal date.</p>
</TabPane>
<TabPane eventKey="usage" fade={false}>
<h2 className="h4 tab-title">Usage</h2>
<p>Seats and storage used this month.</p>
</TabPane>
</div>
</TabContainer>
</div>Icon tabs work from a server component too. Put a Lucide icon in the TabLink and give the link an aria-label. The demo does it with <TabLink eventKey="settings" className="nav-link" aria-label="Settings"><Settings className="align-middle" /></TabLink>. The .tab style makes the icon 20px.
Accessibility
TabLinksetsrole="tab",aria-selectedandaria-controls.TabPanesetsrole="tabpanel",aria-labelledbyandtabIndex={0}. You addrole="tablist"on the list androle="presentation"on its items.- The active tab is the only tab in the Tab order. Arrow keys, Home and End move between the tabs of the same
role="tablist"and show the pane, as in Bootstrap 5.3. They do nothing when the list has norole="tablist". - Give icon-only tabs an
aria-label. disabledadds the.disabledclass only. Addaria-disabled="true"for screen readers.
Related
- React Bootstrap tabs: the same component in AdminKit for React (Vite)
- Bootstrap 5 navs and tabs: the nav and tab classes of the HTML version
- Accordion: another way to show one section at a time
- Tabs demo
Last updated on
TooltipsPro
A Next.js Bootstrap tooltip for the App Router: wrap an element in a client component for a tooltip on hover and focus, positioned by Floating UI.
Navbar
The Next.js Bootstrap navbar in AdminKit: notifications, messages, color mode and user menus on every dashboard page, and how to change or add them.