Tabs

PreviousNext

A component to display content in multiple tabs

Panel - 1
import { Tab, TabList, TabPanel, Tabs, TabsBody } from '../ui/tabs/Tabs';

export function TabsDemo() {
  const tabs = new Array(24).fill(undefined).map((_, n) => ({ label: `${n + 1}`, value: n + 1 }));

  const bgs = (count: number) => {
    switch (count % 3) {
      case 0:
        return 'bg-lightSand';
      case 1:
        return 'bg-saffron';
      case 2:
        return 'bg-lavender';
      default:
        return 'bg-sienna';
    }
  };
  return (
    <Tabs selected={1} className="max-w-500">
      <TabList>
        {tabs.map((tab) => {
          return <Tab key={tab.label} label={`Tab - ${tab.label}`} value={tab.value} />;
        })}
      </TabList>
      <TabsBody>
        {tabs.map((tab) => {
          return (
            <TabPanel key={tab.label} value={tab.value}>
              <div className={`p-16 ${bgs(tab.value)}`}>Panel - {tab.label}</div>
            </TabPanel>
          );
        })}
      </TabsBody>
    </Tabs>
  );
}

Installation

npx shadcn@latest add https://develop.trident-ui.pro.clubmed/r/arrows.json

Usage

import { Tab, TabList, TabPanel, Tabs, TabsBody } from '../ui/tabs/Tabs';
 
export default function TabsDemo() {
  const tabs = new Array(3).fill(undefined).map((_, n) => ({ label: `${n + 1}`, value: n + 1 }));
 
  const bgs = (count: number) => {
    switch (count % 3) {
      case 0:
        return 'bg-lightSand';
      case 1:
        return 'bg-saffron';
      case 2:
        return 'bg-lavender';
      default:
        return 'bg-sienna';
    }
  };
  return (
    <Tabs selected={1}>
      <TabList>
        {tabs.map((tab) => {
          return <Tab key={tab.label} label={`Tab - ${tab.label}`} value={tab.value} />;
        })}
      </TabList>
      <TabsBody>
        {tabs.map((tab) => {
          return (
            <TabPanel key={tab.label} value={tab.value}>
              <div className={`p-16 ${bgs(tab.value)}`}>Panel - {tab.label}</div>
            </TabPanel>
          );
        })}
      </TabsBody>
    </Tabs>
  );
}

API Reference

Main Tabs component

Main container of the component, orchestrates

PropTypeDefaultDescription
selectednumberundefinedDefault selected tab
childrenchildrenundefined
compactedbooleanfalseTightens the vertical spacing between the TabList and the TabsBody
classNamestringundefinedAdditional className for the tabs container

Tab List component

Displays the Tab components side by side. When the tab list overflows its container, left and right scroll buttons appear automatically. The buttons are keyboard-accessible (Space/Enter to scroll) and use aria-hidden / tabindex="-1" when scrolling in that direction is not possible.

PropTypeDefaultDescription
constrainedbooleanfalseWhether or not to remove the spacers on the side of the tabs list
classNamestringundefinedAdditional className for the scrollable element

Tab component

The actual tab, clicking it will switch the current active tab and display its content in the TabPanel

PropTypeDefaultDescription
labelstringundefinedTab Heading text
valuenumberMANDATORYTab Heading index (can be 0 or 1 indexed)
notificationReactNodeundefinedBadge content rendered inside the tab label (e.g. a count or "!")
onSelect(context: {value:number, label:string}) => voidundefinedCallback on tab selection
as"div" | "h1" | "h2" | "h3" | "h4" | "h5";h2HTML tag to render the tab with
classNamestringundefinedAdditional className for the root element in this component

Tabs Body component

Container for the TabPanel components

PropTypeDefaultDescription
classNamestringundefinedAdditional className for the root element in this component

Tab Panel component

Container for the content displayed when a tab is selected

PropTypeDefaultDescription
valuenumberMANDATORYIndex of the tab this panel is associated with
classNamestringundefinedAdditional className for the root element in this component
onSelect(context: {value:number}) => voidundefinedCallback on tab panel selection

Examples

Notification badge

Pass any ReactNode to the notification prop on a Tab to render a small badge next to the label. A number, a string like "!", or any inline element works. The badge colour adapts on hover and focus so it always stays legible against the active-pill background.

Overview panel
import { Tab, TabList, TabPanel, Tabs, TabsBody } from '../ui/tabs/Tabs';

export function TabsNotificationDemo() {
  return (
    <Tabs selected={1}>
      <TabList constrained>
        <Tab label="Overview" value={1} />
        <Tab label="Messages" value={2} notification={3} />
        <Tab label="Alerts" value={3} notification="!" />
        <Tab label="Settings" value={4} />
      </TabList>
      <TabsBody>
        <TabPanel value={1}>
          <div className="p-16 bg-lightSand">Overview panel</div>
        </TabPanel>
        <TabPanel value={2}>
          <div className="p-16 bg-saffron">3 unread messages</div>
        </TabPanel>
        <TabPanel value={3}>
          <div className="p-16 bg-lavender">Alerts panel</div>
        </TabPanel>
        <TabPanel value={4}>
          <div className="p-16 bg-lightSand">Settings panel</div>
        </TabPanel>
      </TabsBody>
    </Tabs>
  );
}

Au ski

A la patinoire
'use client';

import { Tab, TabList, TabPanel, Tabs, TabsBody } from '../ui/tabs/Tabs';

export function TabsWithinTabsDemo() {
  const handleSelect = (context: { value: number }) => {
    console.log('Selected tab:', context.value);
  };

  return (
    <Tabs compacted selected={1}>
      <TabList>
        <Tab label="Au ski" value={1} onSelect={handleSelect} />
        <Tab label="Au soleil" value={2} onSelect={handleSelect} />
        <Tab label="En croisière" value={3} onSelect={handleSelect} />
      </TabList>
      <TabsBody>
        <TabPanel value={1}>
          <div className="bg-lightSand p-16">
            <h4>Au ski</h4>
            <Tabs selected={2} compacted>
              <TabList constrained>
                <Tab label="Sur les pistes" value={1} onSelect={handleSelect} />
                <Tab label="A la patinoire" value={2} onSelect={handleSelect} />
              </TabList>
              <TabsBody>
                <TabPanel value={1}>
                  <div>Sur les pistes</div>
                </TabPanel>
                <TabPanel value={2}>
                  <div>A la patinoire</div>
                </TabPanel>
              </TabsBody>
            </Tabs>
          </div>
        </TabPanel>
        <TabPanel value={2}>
          <div className="bg-saffron p-16">
            <h4>Au soleil</h4>
            <Tabs selected={2} compacted>
              <TabList constrained>
                <Tab label="A la plage" value={1} onSelect={handleSelect} />
                <Tab label="A la piscine" value={2} onSelect={handleSelect} />
              </TabList>
              <TabsBody>
                <TabPanel value={1}>
                  <div>A la plage</div>
                </TabPanel>
                <TabPanel value={2}>
                  <div>A la piscine</div>
                </TabPanel>
              </TabsBody>
            </Tabs>
          </div>
        </TabPanel>
        <TabPanel value={3}>
          <div className="bg-lavender p-16">
            <h4>En croisière</h4>
            <Tabs selected={2} compacted>
              <TabList constrained>
                <Tab label="Au Karting" value={1} onSelect={handleSelect} />
                <Tab label="Au minigolf" value={2} onSelect={handleSelect} />
              </TabList>
              <TabsBody>
                <TabPanel value={1}>
                  <div>Au Karting</div>
                </TabPanel>
                <TabPanel value={2}>
                  <div>Au minigolf</div>
                </TabPanel>
              </TabsBody>
            </Tabs>
          </div>
        </TabPanel>
      </TabsBody>
    </Tabs>
  );
}
Panel - 1
import { Tab, TabList, TabPanel, Tabs, TabsBody, TabSelect } from '../ui/tabs/Tabs';

export function TabsWithSelectDemo() {
  const tabs = new Array(3).fill(undefined).map((_, n) => ({ label: `${n + 1}`, value: n + 1 }));

  const bgs = (count: number) => {
    switch (count % 3) {
      case 0:
        return 'bg-lightSand';
      case 1:
        return 'bg-saffron';
      case 2:
        return 'bg-lavender';
      default:
        return 'bg-sienna';
    }
  };
  return (
    <Tabs className="w-full" selected={1}>
      <TabSelect aria-label="Choose a tab" className="md:hidden w-full" items={tabs} />
      <TabList className="hidden md:flex">
        {tabs.map((tab) => {
          return <Tab key={tab.label} label={`Tab - ${tab.label}`} value={tab.value} />;
        })}
      </TabList>
      <TabsBody>
        {tabs.map((tab) => {
          return (
            <TabPanel key={tab.label} value={tab.value}>
              <div className={`p-16 ${bgs(tab.value)}`}>Panel - {tab.label}</div>
            </TabPanel>
          );
        })}
      </TabsBody>
    </Tabs>
  );
}

Notes