Tabs

free

Tabs group related content into panels and switch between them in place.

Preview
Dark

Home shows recent activity and a summary of what changed since your last visit.

import Tabs from '@/components/ui/Tabs';

const { TabNav, TabList, TabContent } = Tabs;

export default function UsageDemo() {
  return (
    <div className="w-full max-w-md">
      <Tabs defaultValue="tab1">
        <TabList>
          <TabNav value="tab1">Home</TabNav>
          <TabNav value="tab2">Profile</TabNav>
          <TabNav value="tab3">Contact</TabNav>
        </TabList>
        <div className="p-4">
          <TabContent value="tab1">
            <p className="text-muted-foreground">
              Home shows recent activity and a summary of what changed since
              your last visit.
            </p>
          </TabContent>
          <TabContent value="tab2">
            <p className="text-muted-foreground">
              Profile holds your name, contact details, and the preferences
              other people can see.
            </p>
          </TabContent>
          <TabContent value="tab3">
            <p className="text-muted-foreground">
              Contact lists the channels the team can reach you on and your
              working hours.
            </p>
          </TabContent>
        </div>
      </Tabs>
    </div>
  );
}

Installation

Add this component with the NateUI CLI.

npx nateui@latest add Tabs

Examples

Default

Preview
Dark

The underline variant draws a moving indicator beneath the active tab.

import Tabs from '@/components/ui/Tabs';

const { TabNav, TabList, TabContent } = Tabs;

export default function DefaultDemo() {
  return (
    <div className="w-full max-w-md">
      <Tabs defaultValue="tab1">
        <TabList>
          <TabNav value="tab1">Home</TabNav>
          <TabNav value="tab2">Profile</TabNav>
          <TabNav value="tab3">Contact</TabNav>
        </TabList>
        <div className="p-4">
          <TabContent value="tab1">
            <p className="text-muted-foreground">
              The underline variant draws a moving indicator beneath the
              active tab.
            </p>
          </TabContent>
          <TabContent value="tab2">
            <p className="text-muted-foreground">
              Panels swap in place, so the surrounding layout stays put as you
              switch tabs.
            </p>
          </TabContent>
          <TabContent value="tab3">
            <p className="text-muted-foreground">
              Only the selected panel is mounted at a time.
            </p>
          </TabContent>
        </div>
      </Tabs>
    </div>
  );
}

Pill

Preview
Dark

The pill variant fills the active tab with a rounded surface instead of an underline.

import Tabs from '@/components/ui/Tabs';

const { TabNav, TabList, TabContent } = Tabs;

export default function PillDemo() {
  return (
    <div className="w-full max-w-md">
      <Tabs defaultValue="tab1" variant="pill">
        <TabList>
          <TabNav value="tab1">Home</TabNav>
          <TabNav value="tab2">Profile</TabNav>
          <TabNav value="tab3">Contact</TabNav>
        </TabList>
        <div className="p-4">
          <TabContent value="tab1">
            <p className="text-muted-foreground">
              The pill variant fills the active tab with a rounded surface
              instead of an underline.
            </p>
          </TabContent>
          <TabContent value="tab2">
            <p className="text-muted-foreground">
              Use it when tabs sit on their own rather than above a bordered
              content area.
            </p>
          </TabContent>
          <TabContent value="tab3">
            <p className="text-muted-foreground">
              The indicator animates between pills as the selection changes.
            </p>
          </TabContent>
        </div>
      </Tabs>
    </div>
  );
}

Icons

Preview
Dark

Pass an icon node through the `icon` prop to sit it before the tab label.

import { PiHouse, PiPhone, PiUser } from 'react-icons/pi'
import Tabs from '@/components/ui/Tabs';

const { TabNav, TabList, TabContent } = Tabs;

export default function IconsDemo() {
  return (
    <div className="w-full max-w-md">
      <Tabs defaultValue="tab1">
        <TabList>
          <TabNav value="tab1" icon={<PiHouse />}>
            Home
          </TabNav>
          <TabNav value="tab2" icon={<PiUser />}>
            Profile
          </TabNav>
          <TabNav value="tab3" icon={<PiPhone />}>
            Contact
          </TabNav>
        </TabList>
        <div className="p-4">
          <TabContent value="tab1">
            <p className="text-muted-foreground">
              Pass an icon node through the `icon` prop to sit it before the
              tab label.
            </p>
          </TabContent>
          <TabContent value="tab2">
            <p className="text-muted-foreground">
              Icons help people recognize sections at a glance in denser
              layouts.
            </p>
          </TabContent>
          <TabContent value="tab3">
            <p className="text-muted-foreground">
              The active icon and label share the primary color.
            </p>
          </TabContent>
        </div>
      </Tabs>
    </div>
  );
}

Disabled

Preview
Dark
import Tabs from '@/components/ui/Tabs';

const { TabNav, TabList } = Tabs;

export default function DisabledDemo() {
  return (
    <div className="grid w-full max-w-md gap-8">
      <Tabs defaultValue="tab1">
        <TabList>
          <TabNav value="tab1">Home</TabNav>
          <TabNav value="tab2" disabled>
            Profile
          </TabNav>
          <TabNav value="tab3">Contact</TabNav>
        </TabList>
      </Tabs>
      <Tabs defaultValue="tab1" variant="pill">
        <TabList>
          <TabNav value="tab1">Home</TabNav>
          <TabNav value="tab2" disabled>
            Profile
          </TabNav>
          <TabNav value="tab3">Contact</TabNav>
        </TabList>
      </Tabs>
    </div>
  );
}

Controlled

Preview
Dark

Hold the active value in state and pass it back through `value` to drive tabs from the outside.

import { useState } from 'react';
import Tabs from '@/components/ui/Tabs';

const { TabNav, TabList, TabContent } = Tabs;

export default function ControlledDemo() {
  const [currentTab, setCurrentTab] = useState('tab1');

  return (
    <div className="w-full max-w-md">
      <Tabs value={currentTab} onChange={(val) => setCurrentTab(val)}>
        <TabList>
          <TabNav value="tab1">Home</TabNav>
          <TabNav value="tab2">Profile</TabNav>
          <TabNav value="tab3">Contact</TabNav>
        </TabList>
        <div className="p-4">
          <TabContent value="tab1">
            <p className="text-muted-foreground">
              Hold the active value in state and pass it back through `value`
              to drive tabs from the outside.
            </p>
          </TabContent>
          <TabContent value="tab2">
            <p className="text-muted-foreground">
              `onChange` reports the next value whenever a different tab is
              activated.
            </p>
          </TabContent>
          <TabContent value="tab3">
            <p className="text-muted-foreground">
              This lets another control, a route, or a step flow move the
              selection.
            </p>
          </TabContent>
        </div>
      </Tabs>
    </div>
  );
}

API

Tabs is a compound component. Tabs owns the selected value and shares it through context, while Tabs.TabNav and Tabs.TabContent opt in to that value by their own value.

Tabs

PropDescriptionTypeDefault
variantVisual style of the tab strip.TabsVariant'underline'
defaultValueValue selected on first render when uncontrolled.string-
valueSelected value in controlled mode.string-
onChangeCallback fired with the next value when the selection changes.(value: string) => void-

Tabs.TabList

PropDescriptionTypeDefault
classNameExtra classes for the tab strip, which also renders the active indicator.string-
childrenThe Tabs.TabNav items.ReactNode-

Tabs.TabNav

PropDescriptionTypeDefault
valueValue this tab activates. Must match its Tabs.TabContent.string-
iconIcon node rendered before the label.string | ReactNode-
disabledPrevent selecting this tab.booleanfalse
childrenTab label content.ReactNode-

Tabs.TabContent

PropDescriptionTypeDefault
valueValue that shows this panel. Must match a Tabs.TabNav.string-
childrenPanel content, rendered only while selected.ReactNode-

Types

type TabsVariant = 'underline' | 'pill'