Tabs
freeTabs group related content into panels and switch between them in place.
Preview
Dark
Home
Profile
Contact
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 TabsExamples
Default
Preview
Dark
Home
Profile
Contact
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
Home
Profile
Contact
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
Home
Profile
Contact
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
Home
Profile
Contact
Home
Profile
Contact
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
Home
Profile
Contact
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
| Prop | Description | Type | Default |
|---|---|---|---|
variant | Visual style of the tab strip. | TabsVariant | 'underline' |
defaultValue | Value selected on first render when uncontrolled. | string | - |
value | Selected value in controlled mode. | string | - |
onChange | Callback fired with the next value when the selection changes. | (value: string) => void | - |
Tabs.TabList
| Prop | Description | Type | Default |
|---|---|---|---|
className | Extra classes for the tab strip, which also renders the active indicator. | string | - |
children | The Tabs.TabNav items. | ReactNode | - |
Tabs.TabNav
| Prop | Description | Type | Default |
|---|---|---|---|
value | Value this tab activates. Must match its Tabs.TabContent. | string | - |
icon | Icon node rendered before the label. | string | ReactNode | - |
disabled | Prevent selecting this tab. | boolean | false |
children | Tab label content. | ReactNode | - |
Tabs.TabContent
| Prop | Description | Type | Default |
|---|---|---|---|
value | Value that shows this panel. Must match a Tabs.TabNav. | string | - |
children | Panel content, rendered only while selected. | ReactNode | - |
Types
type TabsVariant = 'underline' | 'pill'