Components / Controls
Tabs
Pill or underline tabs. A panel is not mounted until its tab is opened.
import {
CancelCircleIcon,
CheckmarkCircle02Icon,
DashboardSquare01Icon,
DeliveryTruck01Icon,
Loading03Icon,
Menu01Icon,
Package01Icon,
} from "@hugeicons/core-free-icons";
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/components/ui/hiip-ui/tabs";
// Counts are the point of this one: a status filter is unusable without them,
// because "Delivery failed" reads the same whether it holds 3 rows or none.
// Typed off `TabsTrigger` itself rather than `as const`: only some of these
// rows carry an icon or a tone, and a const-asserted union has no `icon` at all
// on the ones that don't — which is a type error at the spread, not a fact
// about the data.
const SHIPPING: {
value: string;
label: string;
icon?: React.ComponentProps<typeof TabsTrigger>["icon"];
tone?: React.ComponentProps<typeof TabsTrigger>["tone"];
count: number;
}[] = [
{ value: "all", label: "All", count: 159 },
{
value: "not-shipped",
label: "Not shipped",
icon: Loading03Icon,
count: 24,
},
{
value: "created",
label: "Order created",
icon: Package01Icon,
tone: "info",
count: 0,
},
{
value: "transit",
label: "In transit",
icon: DeliveryTruck01Icon,
tone: "warning",
count: 4,
},
{
value: "delivered",
label: "Delivered",
icon: CheckmarkCircle02Icon,
tone: "success",
count: 128,
},
{
value: "failed",
label: "Delivery failed",
icon: CancelCircleIcon,
tone: "danger",
count: 3,
},
];
const PANEL =
"rounded-xl border border-dashed p-6 text-center text-sm text-muted-foreground";
function TabsDemo() {
return (
<div className="flex w-full flex-col gap-8">
{/* The pill: a control inside a toolbar, switching a view of one thing. */}
<Tabs defaultSelectedKey="board" className="gap-3">
<TabsList className="self-center">
<TabsTrigger id="board" icon={DashboardSquare01Icon}>
Board
</TabsTrigger>
<TabsTrigger id="table" icon={Menu01Icon}>
Table
</TabsTrigger>
</TabsList>
<TabsContent id="board" className={PANEL}>
Cards, grouped by column
</TabsContent>
<TabsContent id="table" className={PANEL}>
Rows, sorted by date
</TabsContent>
</Tabs>
{/* The underline: page-level sections, with a count on each. Wide enough
to overflow on a phone, so the list scrolls rather than widening the
page. */}
<Tabs defaultSelectedKey="created" className="gap-3">
<div className="max-w-full overflow-x-auto">
<TabsList variant="line" className="w-max">
{SHIPPING.map((s) => (
<TabsTrigger
key={s.value}
id={s.value}
icon={s.icon}
tone={s.tone}
count={s.count}
>
{s.label}
</TabsTrigger>
))}
</TabsList>
</div>
{SHIPPING.map((s) => (
<TabsContent key={s.value} id={s.value} className={PANEL}>
{s.count} {s.count === 1 ? "order" : "orders"} — {s.label}
</TabsContent>
))}
</Tabs>
</div>
);
}Install — needs @hiip registered in components.json
bunx shadcn@latest add @hiip/tabsOr by URL, with the token in the query
bunx shadcn@latest add "https://ui.hiip.ai/r/tabs.json?token=$HIIP_REGISTRY_TOKEN"Both need a token — set one up.
Anatomy
Import from components/ui/hiip-ui/tabs.
<Tabs defaultSelectedKey="board">
<TabsList variant="pill"> {/* or "line" */}
<TabsTrigger id="board" icon={…} count={12}>Board</TabsTrigger>
<TabsTrigger id="table">Table</TabsTrigger>
</TabsList>
{/* Only the open panel is mounted — `keepMounted` opts one out. */}
<TabsContent id="board">…</TabsContent>
<TabsContent id="table" keepMounted>…</TabsContent>
</Tabs>API Reference
Generated from the source. Props forwarded to the underlying element are not listed.
Tabs
Groups a list with its panels and owns which one is active.
| Prop | Type | Default |
|---|---|---|
className | string | undefined | — |
defaultSelectedKeyThe tab active on mount. | string | undefined | — |
onSelectionChangeFires for a click or an arrow key — and once on mount when the root is uncontrolled and `defaultSelectedKey` names no mounted tab, reporting the tab that was… | ((key: string) => void) | undefined | — |
orientationThe component orientation (layout flow direction). | Orientation | undefined | — |
renderAllows you to replace the component's HTML element with a different tag, or compose it with another component. | ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, TabsRootState> | undefined | — |
selectedKeyThe active tab's `id`. | string | undefined | — |
styleStyle applied to the element, or a function that returns a style object based on the component's state. | CSSProperties | ((state: TabsRootState) => CSSProperties | undefined) | undefined | — |
TabsList
The row of triggers, and the indicator that travels between them.
| Prop | Type | Default |
|---|---|---|
activateOnFocusWhether to automatically change the active tab on arrow key focus. | boolean | undefined | — |
className | string | undefined | — |
loopFocusWhether to loop keyboard focus back to the first item when the end of the list is reached while using the arrow keys. | boolean | undefined | — |
renderAllows you to replace the component's HTML element with a different tag, or compose it with another component. | ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, TabsListState> | undefined | — |
styleStyle applied to the element, or a function that returns a style object based on the component's state. | CSSProperties | ((state: TabsListState) => CSSProperties | undefined) | undefined | — |
variant`pill` is a control in a toolbar; `line` is page-level navigation. | TabsVariant | undefined | "pill" |
TabsTrigger
One trigger.
| Prop | Type | Default |
|---|---|---|
idrequiredThis tab's key, matched against the `TabsContent` carrying the same one. | string | — |
className | string | undefined | — |
countShown as a badge after the label. | number | undefined | — |
disabledWhether the Tab is disabled. | boolean | undefined | — |
icon | IconSvgElement | undefined | — |
nativeButtonWhether the component renders a native `<button>` element when replacing it via the `render` prop. | boolean | undefined | — |
renderAllows you to replace the component's HTML element with a different tag, or compose it with another component. | ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, TabsTabState> | undefined | — |
styleStyle applied to the element, or a function that returns a style object based on the component's state. | CSSProperties | ((state: TabsTabState) => CSSProperties | undefined) | undefined | — |
toneColours the ICON only — the label stays on the trigger's own colour. | "info" | "success" | "warning" | "default" | "danger" | undefined | — |
TabsContent
The panel for the trigger of the same `id`.
| Prop | Type | Default |
|---|---|---|
idrequiredThe `id` of the trigger that opens this panel. | string | — |
className | string | undefined | — |
keepMountedWhether to keep the HTML element in the DOM while the panel is hidden. | boolean | undefined | — |
renderAllows you to replace the component's HTML element with a different tag, or compose it with another component. | ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, TabsPanelState> | undefined | — |
styleStyle applied to the element, or a function that returns a style object based on the component's state. | CSSProperties | ((state: TabsPanelState) => CSSProperties | undefined) | undefined | — |
- Type
registry:ui- npm packages
@base-ui/react @hugeicons/react- Files written
- components/ui/hiip-ui/tabs.tsx