A tabbed container with animated tab switching, underline indicator, and support for both horizontal and vertical tab layouts.
| Author | RobloxUI Team |
| Framework | Roblox-TS + React-Luau |
| Category | Containers |
| Dependencies | @rbxts/react, @robloxui/theme |
| Theme Tokens | colors.bgSecondary, colors.bgTertiary, colors.primary, colors.textPrimary, spacing.sm |
| Last Updated | July 22, 2026 |
import React, { useState } from "@rbxts/react";
import { useTheme } from "@robloxui/theme";
interface Tab {
id: string;
label: string;
content: React.ReactNode;
}
interface TabContainerProps {
tabs: Tab[];
defaultTab?: string;
}
export function TabContainer({ tabs, defaultTab }: TabContainerProps) {
const { colors, spacing } = useTheme();
const [activeTab, setActiveTab] = useState(defaultTab || tabs[0]?.id);
return (
<frame BackgroundColor3={colors.bgSecondary} Size={new UDim2(1, 0, 1, 0)}>
<uicorner CornerRadius={new UDim(0, 8)} />
<frame BackgroundColor3={colors.bgTertiary} Size={new UDim2(1, 0, 0, 40)}>
<uilistlayout FillDirection={"Horizontal"} Padding={new UDim(0, 0)} />
{tabs.map((tab) => (
<textbutton
key={tab.id}
Text={tab.label}
BackgroundColor3={activeTab === tab.id ? colors.primary : colors.bgTertiary}
TextColor3={colors.textPrimary}
Size={new UDim2(0, 100, 1, 0)}
Event={{ Activated: () => setActiveTab(tab.id) }}
/>
))}
</frame>
<frame BackgroundTransparency={1} Size={new UDim2(1, -spacing.sm, 1, -56)} Position={new UDim2(0, spacing.sm, 0, 48)}>
{tabs.find((t) => t.id === activeTab)?.content}
</frame>
</frame>
);
}