AdminKitDocs
Navigation

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.

Pro

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:

src/app/(dashboard)/projects/page.tsx
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:

PropTypeDefaultDescription
defaultActiveKeystringThe tab that is active first
activeKeystringControlled: the active tab; use it with onSelect
onSelect(key: string) => voidCalled with the eventKey of the tab that was selected

TabLink:

PropTypeDefaultDescription
eventKeystringThe key of the pane it shows
asElementType"a"The element: an a with href="#", or "button" (gets type="button")
disabledbooleanAdds .disabled; a click does nothing and the arrow keys skip it

TabPane:

PropTypeDefaultDescription
eventKeystringThe key of its tab
fadebooleantrueFades 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:

src/components/project-tabs.tsx
"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

  • TabLink sets role="tab", aria-selected and aria-controls. TabPane sets role="tabpanel", aria-labelledby and tabIndex={0}. You add role="tablist" on the list and role="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 no role="tablist".
  • Give icon-only tabs an aria-label.
  • disabled adds the .disabled class only. Add aria-disabled="true" for screen readers.

Last updated on

On this page