Fiber UI LogoFiberUI

Tabs

A set of layered sections of content, displayed one at a time.

Tabs organize content into multiple sections, allowing users to navigate between them. Built on React Aria's Tabs component with full keyboard navigation and accessibility support.

Basic Usage

A simple tabs component with three panels.

Account Settings
Manage your account settings and preferences.

Configure your account details, profile information, and personal preferences here.

import { Tabs, TabList, Tab, TabPanel } from "@repo/ui/components/tabs";
import {
    Card,
    CardContent,
    CardDescription,
    CardHeader,
    CardTitle,
} from "@repo/ui/components/card";

/* BASIC USAGE EXAMPLE */
export const Example1 = () => {
    return (
        <div className="w-full max-w-md">
            <Tabs>
                <TabList aria-label="Settings">
                    <Tab id="account">Account</Tab>
                    <Tab id="password">Password</Tab>
                    <Tab id="notifications">Notifications</Tab>
                </TabList>
                <TabPanel id="account">
                    <Card>
                        <CardHeader>
                            <CardTitle>Account Settings</CardTitle>
                            <CardDescription>
                                Manage your account settings and preferences.
                            </CardDescription>
                        </CardHeader>
                        <CardContent>
                            <p className="text-muted-foreground text-sm">
                                Configure your account details, profile
                                information, and personal preferences here.
                            </p>
                        </CardContent>
                    </Card>
                </TabPanel>
                <TabPanel id="password">
                    <Card>
                        <CardHeader>
                            <CardTitle>Password & Security</CardTitle>
                            <CardDescription>
                                Change your password and security settings.
                            </CardDescription>
                        </CardHeader>
                        <CardContent>
                            <p className="text-muted-foreground text-sm">
                                Update your password, enable two-factor
                                authentication, and manage security preferences.
                            </p>
                        </CardContent>
                    </Card>
                </TabPanel>
                <TabPanel id="notifications">
                    <Card>
                        <CardHeader>
                            <CardTitle>Notification Preferences</CardTitle>
                            <CardDescription>
                                Configure how you receive notifications.
                            </CardDescription>
                        </CardHeader>
                        <CardContent>
                            <p className="text-muted-foreground text-sm">
                                Choose which notifications you want to receive
                                via email, push, or in-app alerts.
                            </p>
                        </CardContent>
                    </Card>
                </TabPanel>
            </Tabs>
        </div>
    );
};

Controlled Tabs

Use selectedKey and onSelectionChange props to control the active tab programmatically.

Dashboard Overview
Key metrics and summaries at a glance.

1,234

Total Users

567

Active Today

Selected: overview

"use client";

import { useState } from "react";
import type { Key } from "react-aria-components";
import { Tabs, TabList, Tab, TabPanel } from "@repo/ui/components/tabs";
import {
    Card,
    CardContent,
    CardDescription,
    CardHeader,
    CardTitle,
} from "@repo/ui/components/card";

/* CONTROLLED TABS EXAMPLE */
export const Example2 = () => {
    const [selectedTab, setSelectedTab] = useState<Key>("overview");

    return (
        <div className="w-full max-w-md space-y-4">
            <Tabs selectedKey={selectedTab} onSelectionChange={setSelectedTab}>
                <TabList aria-label="Dashboard">
                    <Tab id="overview">Overview</Tab>
                    <Tab id="analytics">Analytics</Tab>
                    <Tab id="reports">Reports</Tab>
                </TabList>
                <TabPanel id="overview">
                    <Card>
                        <CardHeader>
                            <CardTitle>Dashboard Overview</CardTitle>
                            <CardDescription>
                                Key metrics and summaries at a glance.
                            </CardDescription>
                        </CardHeader>
                        <CardContent>
                            <div className="grid grid-cols-2 gap-4">
                                <div className="bg-muted rounded-lg p-4">
                                    <p className="text-2xl font-bold">1,234</p>
                                    <p className="text-muted-foreground text-xs">
                                        Total Users
                                    </p>
                                </div>
                                <div className="bg-muted rounded-lg p-4">
                                    <p className="text-2xl font-bold">567</p>
                                    <p className="text-muted-foreground text-xs">
                                        Active Today
                                    </p>
                                </div>
                            </div>
                        </CardContent>
                    </Card>
                </TabPanel>
                <TabPanel id="analytics">
                    <Card>
                        <CardHeader>
                            <CardTitle>Analytics</CardTitle>
                            <CardDescription>
                                Detailed performance data and insights.
                            </CardDescription>
                        </CardHeader>
                        <CardContent>
                            <div className="border-muted flex h-32 items-center justify-center rounded-lg border-2 border-dashed">
                                <p className="text-muted-foreground text-sm">
                                    Analytics Chart
                                </p>
                            </div>
                        </CardContent>
                    </Card>
                </TabPanel>
                <TabPanel id="reports">
                    <Card>
                        <CardHeader>
                            <CardTitle>Reports</CardTitle>
                            <CardDescription>
                                Generate and view detailed reports.
                            </CardDescription>
                        </CardHeader>
                        <CardContent>
                            <div className="space-y-2">
                                <div className="bg-muted flex items-center justify-between rounded-lg p-3">
                                    <span className="text-sm">
                                        Monthly Report
                                    </span>
                                    <span className="text-muted-foreground text-xs">
                                        Jan 2024
                                    </span>
                                </div>
                                <div className="bg-muted flex items-center justify-between rounded-lg p-3">
                                    <span className="text-sm">
                                        Weekly Report
                                    </span>
                                    <span className="text-muted-foreground text-xs">
                                        Week 4
                                    </span>
                                </div>
                            </div>
                        </CardContent>
                    </Card>
                </TabPanel>
            </Tabs>
            <p className="text-muted-foreground text-center text-sm">
                Selected:{" "}
                <span className="text-foreground font-medium">
                    {selectedTab}
                </span>
            </p>
        </div>
    );
};

Disabled Tabs

Use isDisabled on individual tabs to prevent interaction.

Active Feature
This feature is available and fully accessible.

You have full access to this feature. Explore all the available options and settings.

import { Tabs, TabList, Tab, TabPanel } from "@repo/ui/components/tabs";
import {
    Card,
    CardContent,
    CardDescription,
    CardHeader,
    CardTitle,
} from "@repo/ui/components/card";
import { Lock } from "lucide-react";

/* DISABLED TABS EXAMPLE */
export const Example3 = () => {
    return (
        <div className="w-full max-w-md">
            <Tabs>
                <TabList aria-label="Features">
                    <Tab id="active">Active</Tab>
                    <Tab id="disabled" isDisabled>
                        Disabled
                    </Tab>
                    <Tab id="premium" isDisabled>
                        <Lock className="size-3" />
                        Premium
                    </Tab>
                    <Tab id="settings">Settings</Tab>
                </TabList>
                <TabPanel id="active">
                    <Card>
                        <CardHeader>
                            <CardTitle>Active Feature</CardTitle>
                            <CardDescription>
                                This feature is available and fully accessible.
                            </CardDescription>
                        </CardHeader>
                        <CardContent>
                            <p className="text-muted-foreground text-sm">
                                You have full access to this feature. Explore
                                all the available options and settings.
                            </p>
                        </CardContent>
                    </Card>
                </TabPanel>
                <TabPanel id="disabled">
                    <Card>
                        <CardContent>
                            <p className="text-muted-foreground text-sm">
                                This content is not accessible.
                            </p>
                        </CardContent>
                    </Card>
                </TabPanel>
                <TabPanel id="premium">
                    <Card>
                        <CardContent>
                            <p className="text-muted-foreground text-sm">
                                Premium content requires an upgrade.
                            </p>
                        </CardContent>
                    </Card>
                </TabPanel>
                <TabPanel id="settings">
                    <Card>
                        <CardHeader>
                            <CardTitle>Settings</CardTitle>
                            <CardDescription>
                                Adjust your preferences and configuration.
                            </CardDescription>
                        </CardHeader>
                        <CardContent>
                            <p className="text-muted-foreground text-sm">
                                Customize your experience with various settings
                                and options available here.
                            </p>
                        </CardContent>
                    </Card>
                </TabPanel>
            </Tabs>
        </div>
    );
};

Default Selected

Use defaultSelectedKey to set the initially selected tab for uncontrolled usage.

Billing
View and manage your billing details.
Current PlanPro
Next billing dateFeb 1, 2024
import { Tabs, TabList, Tab, TabPanel } from "@repo/ui/components/tabs";
import {
    Card,
    CardContent,
    CardDescription,
    CardHeader,
    CardTitle,
} from "@repo/ui/components/card";

/* DEFAULT SELECTED TAB EXAMPLE */
export const Example4 = () => {
    return (
        <div className="w-full max-w-md">
            <Tabs defaultSelectedKey="billing">
                <TabList aria-label="Account settings">
                    <Tab id="profile">Profile</Tab>
                    <Tab id="billing">Billing</Tab>
                    <Tab id="team">Team</Tab>
                </TabList>
                <TabPanel id="profile">
                    <Card>
                        <CardHeader>
                            <CardTitle>Profile</CardTitle>
                            <CardDescription>
                                Manage your public profile information.
                            </CardDescription>
                        </CardHeader>
                        <CardContent>
                            <div className="flex items-center gap-4">
                                <div className="bg-muted flex size-12 items-center justify-center rounded-full text-lg font-medium">
                                    JD
                                </div>
                                <div>
                                    <p className="font-medium">John Doe</p>
                                    <p className="text-muted-foreground text-sm">
                                        john@example.com
                                    </p>
                                </div>
                            </div>
                        </CardContent>
                    </Card>
                </TabPanel>
                <TabPanel id="billing">
                    <Card>
                        <CardHeader>
                            <CardTitle>Billing</CardTitle>
                            <CardDescription>
                                View and manage your billing details.
                            </CardDescription>
                        </CardHeader>
                        <CardContent>
                            <div className="space-y-3">
                                <div className="flex items-center justify-between">
                                    <span className="text-sm">
                                        Current Plan
                                    </span>
                                    <span className="bg-primary/10 text-primary rounded-full px-2 py-1 text-xs font-medium">
                                        Pro
                                    </span>
                                </div>
                                <div className="flex items-center justify-between">
                                    <span className="text-sm">
                                        Next billing date
                                    </span>
                                    <span className="text-muted-foreground text-sm">
                                        Feb 1, 2024
                                    </span>
                                </div>
                            </div>
                        </CardContent>
                    </Card>
                </TabPanel>
                <TabPanel id="team">
                    <Card>
                        <CardHeader>
                            <CardTitle>Team</CardTitle>
                            <CardDescription>
                                Invite and manage team members.
                            </CardDescription>
                        </CardHeader>
                        <CardContent>
                            <div className="space-y-2">
                                <div className="bg-muted flex items-center gap-3 rounded-lg p-2">
                                    <div className="bg-background flex size-8 items-center justify-center rounded-full text-sm font-medium">
                                        JD
                                    </div>
                                    <div className="flex-1">
                                        <p className="text-sm font-medium">
                                            John Doe
                                        </p>
                                        <p className="text-muted-foreground text-xs">
                                            Owner
                                        </p>
                                    </div>
                                </div>
                                <div className="bg-muted flex items-center gap-3 rounded-lg p-2">
                                    <div className="bg-background flex size-8 items-center justify-center rounded-full text-sm font-medium">
                                        JS
                                    </div>
                                    <div className="flex-1">
                                        <p className="text-sm font-medium">
                                            Jane Smith
                                        </p>
                                        <p className="text-muted-foreground text-xs">
                                            Member
                                        </p>
                                    </div>
                                </div>
                            </div>
                        </CardContent>
                    </Card>
                </TabPanel>
            </Tabs>
        </div>
    );
};

With Icons

Tabs can include icons alongside text labels.

Profile Settings
Update your profile information and avatar.
U

Upload a new avatar

JPG, PNG or GIF. Max 2MB.

import { Tabs, TabList, Tab, TabPanel } from "@repo/ui/components/tabs";
import {
    Card,
    CardContent,
    CardDescription,
    CardHeader,
    CardTitle,
} from "@repo/ui/components/card";
import { User, Bell, Shield } from "lucide-react";

/* TABS WITH ICONS EXAMPLE */
export const Example5 = () => {
    return (
        <div className="w-full max-w-md">
            <Tabs>
                <TabList aria-label="Settings">
                    <Tab id="profile">
                        <User className="size-4" />
                        Profile
                    </Tab>
                    <Tab id="notifications">
                        <Bell className="size-4" />
                        Alerts
                    </Tab>
                    <Tab id="security">
                        <Shield className="size-4" />
                        Security
                    </Tab>
                </TabList>
                <TabPanel id="profile">
                    <Card>
                        <CardHeader>
                            <CardTitle className="flex items-center gap-2">
                                <User className="size-5" />
                                Profile Settings
                            </CardTitle>
                            <CardDescription>
                                Update your profile information and avatar.
                            </CardDescription>
                        </CardHeader>
                        <CardContent>
                            <div className="flex items-center gap-4">
                                <div className="flex size-16 items-center justify-center rounded-full bg-gradient-to-br from-blue-500 to-purple-500 text-xl font-bold text-white">
                                    U
                                </div>
                                <div className="space-y-1">
                                    <p className="font-medium">
                                        Upload a new avatar
                                    </p>
                                    <p className="text-muted-foreground text-sm">
                                        JPG, PNG or GIF. Max 2MB.
                                    </p>
                                </div>
                            </div>
                        </CardContent>
                    </Card>
                </TabPanel>
                <TabPanel id="notifications">
                    <Card>
                        <CardHeader>
                            <CardTitle className="flex items-center gap-2">
                                <Bell className="size-5" />
                                Notification Settings
                            </CardTitle>
                            <CardDescription>
                                Configure your notification preferences.
                            </CardDescription>
                        </CardHeader>
                        <CardContent>
                            <div className="space-y-3">
                                <div className="flex items-center justify-between">
                                    <span className="text-sm">
                                        Email notifications
                                    </span>
                                    <span className="rounded-full bg-green-500/10 px-2 py-0.5 text-xs font-medium text-green-600">
                                        On
                                    </span>
                                </div>
                                <div className="flex items-center justify-between">
                                    <span className="text-sm">
                                        Push notifications
                                    </span>
                                    <span className="rounded-full bg-green-500/10 px-2 py-0.5 text-xs font-medium text-green-600">
                                        On
                                    </span>
                                </div>
                                <div className="flex items-center justify-between">
                                    <span className="text-sm">SMS alerts</span>
                                    <span className="bg-muted text-muted-foreground rounded-full px-2 py-0.5 text-xs font-medium">
                                        Off
                                    </span>
                                </div>
                            </div>
                        </CardContent>
                    </Card>
                </TabPanel>
                <TabPanel id="security">
                    <Card>
                        <CardHeader>
                            <CardTitle className="flex items-center gap-2">
                                <Shield className="size-5" />
                                Security Settings
                            </CardTitle>
                            <CardDescription>
                                Manage security and privacy settings.
                            </CardDescription>
                        </CardHeader>
                        <CardContent>
                            <div className="space-y-3">
                                <div className="flex items-center justify-between">
                                    <span className="text-sm">
                                        Two-factor authentication
                                    </span>
                                    <span className="rounded-full bg-green-500/10 px-2 py-0.5 text-xs font-medium text-green-600">
                                        Enabled
                                    </span>
                                </div>
                                <div className="flex items-center justify-between">
                                    <span className="text-sm">
                                        Last password change
                                    </span>
                                    <span className="text-muted-foreground text-sm">
                                        30 days ago
                                    </span>
                                </div>
                            </div>
                        </CardContent>
                    </Card>
                </TabPanel>
            </Tabs>
        </div>
    );
};

Dynamic Tabs

Use the items prop on TabList for dynamic collections.

Project Alpha
Main development project
StatusActive
Progress75%
"use client";

import { useState } from "react";
import { Tabs, TabList, Tab, TabPanel } from "@repo/ui/components/tabs";
import {
    Card,
    CardContent,
    CardDescription,
    CardHeader,
    CardTitle,
} from "@repo/ui/components/card";

/* DYNAMIC TABS EXAMPLE */
export const Example6 = () => {
    const [tabs] = useState([
        {
            id: "1",
            title: "Project Alpha",
            description: "Main development project",
            status: "Active",
            progress: 75,
        },
        {
            id: "2",
            title: "Project Beta",
            description: "Research and development",
            status: "In Progress",
            progress: 45,
        },
        {
            id: "3",
            title: "Project Gamma",
            description: "Client deliverable",
            status: "Review",
            progress: 90,
        },
    ]);

    return (
        <div className="w-full max-w-md">
            <Tabs>
                <TabList aria-label="Projects" items={tabs}>
                    {(item) => <Tab id={item.id}>{item.title}</Tab>}
                </TabList>
                {tabs.map((item) => (
                    <TabPanel key={item.id} id={item.id}>
                        <Card>
                            <CardHeader>
                                <CardTitle>{item.title}</CardTitle>
                                <CardDescription>
                                    {item.description}
                                </CardDescription>
                            </CardHeader>
                            <CardContent className="space-y-4">
                                <div className="flex items-center justify-between">
                                    <span className="text-sm">Status</span>
                                    <span className="bg-primary/10 text-primary rounded-full px-2 py-0.5 text-xs font-medium">
                                        {item.status}
                                    </span>
                                </div>
                                <div className="space-y-2">
                                    <div className="flex items-center justify-between text-sm">
                                        <span>Progress</span>
                                        <span className="font-medium">
                                            {item.progress}%
                                        </span>
                                    </div>
                                    <div className="bg-muted h-2 rounded-full">
                                        <div
                                            className="bg-primary h-full rounded-full transition-all"
                                            style={{
                                                width: `${item.progress}%`,
                                            }}
                                        />
                                    </div>
                                </div>
                            </CardContent>
                        </Card>
                    </TabPanel>
                ))}
            </Tabs>
        </div>
    );
};

With Cards

Combine Tabs with Card components for a polished form interface.

Account
Make changes to your account here. Click save when you're done.
import { Tabs, TabList, Tab, TabPanel } from "@repo/ui/components/tabs";
import { Button } from "@repo/ui/components/button";
import { Input } from "@repo/ui/components/input";
import { Label } from "@repo/ui/components/label";
import {
    Card,
    CardContent,
    CardDescription,
    CardFooter,
    CardHeader,
    CardTitle,
} from "@repo/ui/components/card";

/* TABS WITH CARDS EXAMPLE */
export const Example7 = () => {
    return (
        <div className="w-full max-w-md">
            <Tabs defaultSelectedKey="account">
                <TabList aria-label="Account settings">
                    <Tab id="account">Account</Tab>
                    <Tab id="password">Password</Tab>
                </TabList>
                <TabPanel id="account">
                    <Card>
                        <CardHeader>
                            <CardTitle>Account</CardTitle>
                            <CardDescription>
                                Make changes to your account here. Click save
                                when you&apos;re done.
                            </CardDescription>
                        </CardHeader>
                        <CardContent className="grid gap-4">
                            <div className="grid gap-2">
                                <Label htmlFor="name">Name</Label>
                                <Input id="name" defaultValue="Pedro Duarte" />
                            </div>
                            <div className="grid gap-2">
                                <Label htmlFor="username">Username</Label>
                                <Input id="username" defaultValue="@peduarte" />
                            </div>
                        </CardContent>
                        <CardFooter>
                            <Button>Save changes</Button>
                        </CardFooter>
                    </Card>
                </TabPanel>
                <TabPanel id="password">
                    <Card>
                        <CardHeader>
                            <CardTitle>Password</CardTitle>
                            <CardDescription>
                                Change your password here. After saving,
                                you&apos;ll be logged out.
                            </CardDescription>
                        </CardHeader>
                        <CardContent className="grid gap-4">
                            <div className="grid gap-2">
                                <Label htmlFor="current">
                                    Current password
                                </Label>
                                <Input id="current" type="password" />
                            </div>
                            <div className="grid gap-2">
                                <Label htmlFor="new">New password</Label>
                                <Input id="new" type="password" />
                            </div>
                        </CardContent>
                        <CardFooter>
                            <Button>Save password</Button>
                        </CardFooter>
                    </Card>
                </TabPanel>
            </Tabs>
        </div>
    );
};

Component Code

"use client";

import {
    Tabs as AriaTabs,
    TabList as AriaTabList,
    Tab as AriaTab,
    TabPanel as AriaTabPanel,
    composeRenderProps,
    type TabsProps as AriaTabsProps,
    type TabListProps as AriaTabListProps,
    type TabProps as AriaTabProps,
    type TabPanelProps as AriaTabPanelProps,
} from "react-aria-components";
import { cn, tv } from "tailwind-variants";
import { focusRing } from "@repo/ui/lib/utils";

/* -----------------------------------------------------------------------------
 * Tabs (Root)
 * ---------------------------------------------------------------------------*/

interface TabsProps extends AriaTabsProps {}

const tabsStyles = tv({
    variants: {
        orientation: {
            horizontal: "flex-col",
            vertical: "flex-row",
        },
    },
    defaultVariants: {
        orientation: "horizontal",
    },
});

export const Tabs = ({ className, ...props }: TabsProps) => {
    return (
        <AriaTabs
            data-slot="tabs"
            className={composeRenderProps(className, (className, renderProps) =>
                tabsStyles({ ...renderProps, className }),
            )}
            {...props}
        />
    );
};

/* -----------------------------------------------------------------------------
 * TabList
 * ---------------------------------------------------------------------------*/

interface TabListProps<T extends object> extends AriaTabListProps<T> {}

const tabListStyles = tv({
    base: "bg-muted text-muted-foreground inline-flex w-fit items-center justify-center rounded-lg p-1",
    variants: {
        orientation: {
            horizontal: "flex-row",
            vertical: "flex-col items-start",
        },
    },
    defaultVariants: {
        orientation: "horizontal",
    },
});

export const TabList = <T extends object>({
    className,
    ...props
}: TabListProps<T>) => {
    return (
        <AriaTabList
            data-slot="tab-list"
            className={composeRenderProps(className, (className, renderProps) =>
                tabListStyles({ ...renderProps, className }),
            )}
            {...props}
        />
    );
};

/* -----------------------------------------------------------------------------
 * Tab (Trigger)
 * ---------------------------------------------------------------------------*/

interface TabProps extends AriaTabProps {}

const tabStyles = tv({
    extend: focusRing,
    base: [
        "inline-flex flex-1 items-center justify-center gap-1.5 whitespace-nowrap rounded-md border border-transparent px-3 py-1.5 text-sm font-medium transition-[color,box-shadow]",
        "text-foreground dark:text-muted-foreground",
        "disabled:pointer-events-none disabled:opacity-50",
        "[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
    ],
    variants: {
        isSelected: {
            true: [
                "bg-background text-foreground shadow-sm",
                "dark:bg-input/30 dark:text-foreground dark:border-input",
            ],
            false: "hover:text-foreground hover:bg-background/50",
        },
        isDisabled: {
            true: "pointer-events-none opacity-50",
        },
    },
    defaultVariants: {
        isSelected: false,
    },
});

export const Tab = ({ className, ...props }: TabProps) => {
    return (
        <AriaTab
            data-slot="tab-trigger"
            className={composeRenderProps(
                cn("cursor-default", className),
                (className, renderProps) =>
                    tabStyles({ ...renderProps, className }),
            )}
            {...props}
        />
    );
};

/* -----------------------------------------------------------------------------
 * TabPanel (Content)
 * ---------------------------------------------------------------------------*/

interface TabPanelProps extends AriaTabPanelProps {}

const tabPanelStyles = tv({
    extend: focusRing,
    base: "text-foreground flex-1 pt-2 text-sm outline-none",
});

export const TabPanel = ({ className, ...props }: TabPanelProps) => {
    return (
        <AriaTabPanel
            data-slot="tab-panel"
            className={composeRenderProps(className, (className, renderProps) =>
                tabPanelStyles({ ...renderProps, className }),
            )}
            {...props}
        />
    );
};