Tabs
Tabs Examples
Copy-ready examples for Tab + TabStack.
Tab (header only)
Use Tab alone and render panels conditionally below it.
Live preview
Basic header only
"use client";
import { useState } from "react";
import Tab from "@/packages/ui/src/_components/tab/Tab";
export default function Example() {
const [active, setActive] = useState(0);
return (
<div>
<Tab
activeTabIndex={active}
onActiveTabChange={setActive}
tabOption={["Overview", "Details", "Reviews"]}
/>
{active === 0 ? <div>Overview content</div> : null}
{active === 1 ? <div>Details content</div> : null}
{active === 2 ? <div>Reviews content</div> : null}
</div>
);
}Tab + TabStack (slide panels)
Minimal sliding panels without gesture bindings.
• Use `stackStyle.transform` to slide the track as the active index changes.
Live preview
Sliding panels
"use client";
import { useMemo, useRef, useState } from "react";
import Tab from "@/packages/ui/src/_components/tab/Tab";
import TabStack from "@/packages/ui/src/_components/tab/tab_stack/Tab_stack";
export default function Example() {
const [active, setActive] = useState(1);
const stackRef = useRef<HTMLDivElement>(null);
const stackBind = {};
const stackStyle = useMemo(
() => ({
transform: `translateX(-${active * 100}%)`,
transition: "transform 220ms ease",
}),
[active]
);
return (
<>
<Tab
activeTabIndex={active}
onActiveTabChange={setActive}
tabOption={["Photos", "Favorites", "Nearby"]}
/>
<TabStack stackRef={stackRef} stackBind={stackBind} stackStyle={stackStyle}>
{[
<div key="p1">Content: Photos</div>,
<div key="p2">Content: Favorites</div>,
<div key="p3">Content: Nearby</div>,
]}
</TabStack>
</>
);
}useTabsIndicator (recommended pairing)
Use the indicator hook to bind slide behavior + keep measurements in sync.
Live preview
Hook + TabStack
"use client";
import { useState } from "react";
import Tab from "@/_components/tab/Tab";
import Badges from "@/_components/badges/Badges";
import TabStack from "@/_components/tab/tab_stack/Tab_stack";
import { useTabsIndicator } from "@/_components/tab/hooks/useTabIndicator";
export default function Example() {
const [active, setActive] = useState(1);
const tabs = useTabsIndicator({
padX: 12,
activeIndex: active,
count: 3,
onChangeIndex: setActive,
});
return (
<>
<Tab
activeTabIndex={active}
onActiveTabChange={setActive}
tabOption={[
<Badges
key="photos"
iconName="Home"
label="Photos"
showInfo
baseNumber={10}
baseLimit={999}
size="small"
orientation="vertical"
labelFlow="before"
/>,
<Badges
key="fav"
iconName="favorite"
label="Favorites"
showInfo
baseNumber={3}
baseLimit={999}
size="small"
orientation="vertical"
labelFlow="before"
/>,
<Badges
key="near"
iconName="place"
label="Nearby"
showInfo={false}
baseNumber={0}
baseLimit={999}
size="small"
orientation="vertical"
labelFlow="before"
/>,
]}
/>
<TabStack
stackRef={tabs.stackRef}
stackBind={tabs.stackBind}
stackStyle={tabs.stackStyle}
>
{[<p key="c1">Content: Photos</p>, <p key="c2">Content: Favorites</p>, <p key="c3">Content: Nearby</p>]}
</TabStack>
</>
);
}Custom tab nodes
tabOption accepts any ReactNode. Great for icons, badges, counters, etc.
Live preview
Custom nodes
"use client";
import { useMemo, useState } from "react";
import Tab from "@/packages/ui/src/_components/tab/Tab";
export default function Example() {
const [active, setActive] = useState(0);
const tabOption = useMemo(
() => [
<div key="t1" style={{ display: "flex", gap: 8, alignItems: "center" }}>
<span style={{ fontWeight: 700 }}>Account</span>
<span style={{ opacity: 0.65, fontSize: 12 }}>New</span>
</div>,
<div key="t2" style={{ display: "flex", gap: 8, alignItems: "center" }}>
<span style={{ fontWeight: 700 }}>Billing</span>
<span style={{ opacity: 0.65, fontSize: 12 }}>2 issues</span>
</div>,
],
[]
);
return <Tab activeTabIndex={active} onActiveTabChange={setActive} tabOption={tabOption} />;
}Many tabs (scrollable header)
Wrap Tab with `overflowX: auto` to avoid breaking the layout.
Live preview
Scrollable header
"use client";
import { useMemo, useState } from "react";
import Tab from "@/packages/ui/src/_components/tab/Tab";
export default function Example() {
const [active, setActive] = useState(0);
const tabs = useMemo(
() => Array.from({ length: 10 }).map((_, i) => <span key={i}>Tab {i + 1}</span>),
[]
);
return (
<div style={{ overflowX: "auto", width: "100%" }}>
<Tab activeTabIndex={active} onActiveTabChange={setActive} tabOption={tabs} />
</div>
);
}Controlled from parent
External UI can set the active tab index.
Live preview
Fully controlled
"use client";
import { useState } from "react";
import Tab from "@/_components/tab/Tab";
export default function Example() {
const [active, setActive] = useState(1);
return (
<>
<button type="button" onClick={() => setActive(0)}>Go to Overview</button>
<button type="button" onClick={() => setActive(2)}>Go to Reviews</button>
<Tab
activeTabIndex={active}
onActiveTabChange={setActive}
tabOption={["Overview", "Details", "Reviews"]}
/>
</>
);
}On this page
Tabs Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| Tab.activeTabIndex | number | Yes | — | Active tab index (0-based). |
| Tab.tabOption | React.ReactNode[] | Yes | — | Tab items rendered in the header (strings, icons, badges, etc.). |
| Tab.onActiveTabChange | (index: number) => void | No | — | Called when a tab is clicked. Recommended for controlled usage. |
| TabStack.stackRef | React.RefObject<HTMLDivElement | null> | Yes | — | Ref for the viewport element (useful for measurements/gestures). |
| TabStack.stackBind | Record<string, any> | Yes | — | Event bindings spread onto the viewport (e.g., pointer/gesture handlers). |
| TabStack.stackStyle | React.CSSProperties | Yes | — | Inline styles applied to the inner track (commonly a `transform`). |
| TabStack.children | React.ReactNode[] | Yes | — | Panels. Each child becomes one slide (order must match `tabOption`). |
| TabStack.style | React.CSSProperties | No | — | Extra inline styles applied to the viewport. |
| useTabsIndicator(options).padX | number | No | 0 | Horizontal padding used for indicator/measurement calculations. |
| useTabsIndicator(options).activeIndex | number | Yes | — | Current active index (same value you pass to Tab/TabStack). |
| useTabsIndicator(options).count | number | Yes | — | Number of tabs/panels. |
| useTabsIndicator(options).onChangeIndex | (nextIndex: number) => void | Yes | — | Setter called by the hook when user interaction changes the index. |
Components