Tabs
Tab navigation with segmented and underline styles.
Tab navigation for switching between related views or panels.
Tabs is backed by @areia/slots and wires accessible tab semantics, keyboard navigation, an animated indicator, optional content panels, and horizontal overflow scrolling after mount.
Ilha bind:group
Inside an Ilha island (including nested child islands), bind the active tab value:
const Panel = ilha
.state("tab", "overview")
.render(({ state }) => (
<Tabs variant="segmented" bind:group={state.tab}>
<Tabs.List>
<Tabs.Trigger value="overview">Overview</Tabs.Trigger>
<Tabs.Trigger value="settings">Settings</Tabs.Trigger>
</Tabs.List>
</Tabs>
));
value + onValueChange remains supported. Requires ilha ^0.8.0.
Segmented
Underline
import { ilha } from "ilha";
import { Tabs } from "areia";
export default ilha.render(() => (
<div class="flex flex-col gap-6">
<div>
<p class="mb-2 text-sm text-areia-subtle">Segmented</p>
<Tabs
tabs={[
{ value: "overview", label: "Overview" },
{ value: "analytics", label: "Analytics" },
{ value: "settings", label: "Settings" },
]}
selectedValue="overview"
/>
</div>
<div>
<p class="mb-2 text-sm text-areia-subtle">Underline</p>
<Tabs
variant="underline"
tabs={[
{ value: "overview", label: "Overview" },
{ value: "analytics", label: "Analytics" },
{ value: "settings", label: "Settings" },
]}
selectedValue="overview"
/>
</div>
</div>
));Import
import { Tabs } from "areia";
Usage
Call Tabs(...) for interactive behavior. Prefer composing Tabs.List, Tabs.Trigger, and Tabs.Content as children; the tabs array remains available as a shortcut. It initializes the tabs controller, syncs ARIA and data-state attributes, and emits tabs:change when the active tab changes.
import { ilha } from "ilha";
import { Tabs } from "areia";
export default ilha.render(() => (
<Tabs selectedValue="overview">
<Tabs.List>
<Tabs.Trigger value="overview">Overview</Tabs.Trigger>
<Tabs.Trigger value="settings">Settings</Tabs.Trigger>
</Tabs.List>
</Tabs>
));Use Tabs.Static(...) for static markup or when you want to initialize behavior yourself. For most application usage, call Tabs(...).
Examples
Segmented
A pill-shaped indicator slides between tabs on a subtle background.
import { ilha } from "ilha";
import { Tabs } from "areia";
export default ilha.render(() => (
<Tabs
variant="segmented"
tabs={[
{ value: "tab-1", label: "Tab 1" },
{ value: "tab-2", label: "Tab 2" },
{ value: "tab-3", label: "Tab 3" },
]}
selectedValue="tab-1"
/>
));Underline
The underline variant uses a bottom border and primary-colored indicator.
import { ilha } from "ilha";
import { Tabs } from "areia";
export default ilha.render(() => (
<Tabs
variant="underline"
tabs={[
{ value: "tab-1", label: "Tab 1" },
{ value: "tab-2", label: "Tab 2" },
{ value: "tab-3", label: "Tab 3" },
]}
selectedValue="tab-1"
/>
));Small Size
Use size="sm" for compact tabs inside toolbars and filter rows.
import { ilha } from "ilha";
import { Tabs } from "areia";
export default ilha.render(() => (
<div class="flex flex-col gap-6">
<Tabs
size="sm"
tabs={[
{ value: "open", label: "Open" },
{ value: "closed", label: "Closed" },
{ value: "all", label: "All" },
]}
selectedValue="open"
/>
<Tabs
variant="underline"
size="sm"
tabs={[
{ value: "daily", label: "Daily" },
{ value: "weekly", label: "Weekly" },
{ value: "monthly", label: "Monthly" },
]}
selectedValue="daily"
/>
</div>
));Many Tabs
Segmented tabs scroll horizontally when there are more items than available space. Mouse users can drag the tab list to scroll.
import { ilha } from "ilha";
import { Tabs } from "areia";
export default ilha.render(() => (
<div class="w-full max-w-md">
<Tabs
tabs={[
{ value: "overview", label: "Overview" },
{ value: "analytics", label: "Analytics" },
{ value: "reports", label: "Reports" },
{ value: "notifications", label: "Notifications" },
{ value: "settings", label: "Settings" },
{ value: "billing", label: "Billing" },
{ value: "security", label: "Security" },
{ value: "integrations", label: "Integrations" },
]}
selectedValue="overview"
/>
</div>
));Change Events
Use onValueChange to respond when the active tab changes.
import { ilha } from "ilha";
import { Tabs } from "areia";
export default ilha
.state("activeTab", "overview")
.render(({ state }) => (
<div class="space-y-4">
<Tabs
tabs={[
{ value: "overview", label: "Overview" },
{ value: "usage", label: "Usage" },
{ value: "settings", label: "Settings" },
]}
selectedValue={state.activeTab()}
onValueChange={(value) => {
state.activeTab(value);
}}
/>
<p class="text-sm text-areia-subtle">
Active tab: <code>{state.activeTab}</code>
</p>
</div>
));Content Panels
Compose Tabs.Content children for panel content. This keeps panel JSX colocated with each tab value.
import { ilha } from "ilha";
import { Tabs } from "areia";
export default ilha.render(() => (
<Tabs selectedValue="overview">
<Tabs.List>
<Tabs.Trigger value="overview">Overview</Tabs.Trigger>
<Tabs.Trigger value="activity">Activity</Tabs.Trigger>
<Tabs.Trigger value="settings">Settings</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="overview">
<div class="rounded-lg bg-areia-surface-muted p-4 text-sm">
Project health and key metrics.
</div>
</Tabs.Content>
<Tabs.Content value="activity">
<div class="rounded-lg bg-areia-surface-muted p-4 text-sm">
Recent project activity appears here.
</div>
</Tabs.Content>
<Tabs.Content value="settings">
<div class="rounded-lg bg-areia-surface-muted p-4 text-sm">
Workspace preferences and access controls.
</div>
</Tabs.Content>
</Tabs>
));Data Shortcut
Use the tabs array for concise data-driven tabs. Pass content on each item to generate content panels.
import { ilha } from "ilha";
import { Tabs } from "areia";
export default ilha.render(() => (
<Tabs
selectedValue="overview"
tabs={[
{
value: "overview",
label: "Overview",
content: "Overview panel",
},
{
value: "settings",
label: "Settings",
content: "Settings panel",
},
]}
/>
));Disabled Item
Disable individual tabs with disabled: true.
import { ilha } from "ilha";
import { Tabs } from "areia";
export default ilha.render(() => (
<Tabs
tabs={[
{ value: "profile", label: "Profile" },
{ value: "team", label: "Team" },
{ value: "billing", label: "Billing", disabled: true },
]}
selectedValue="profile"
/>
));API
Tabs(input)
| Prop | Type | Default | Description |
|---|---|---|---|
tabs |
TabsItem[] |
[] |
Items rendered as tab triggers. |
children |
unknown |
— | Composed Tabs.List, Tabs.Trigger, and Tabs.Content markup. Takes priority over tabs. |
value |
string |
— | Initial active value. |
selectedValue |
string |
— | Initial active value for uncontrolled usage. |
defaultValue |
string |
— | Alias for the initial active value. |
variant |
"segmented" | "underline" |
"segmented" |
Visual style. |
size |
"base" | "sm" |
"base" |
Tab size. |
activationMode |
"auto" | "manual" |
"manual" |
Keyboard activation mode passed to the tabs controller. |
activateOnFocus |
boolean |
false |
Uses automatic arrow-key activation when true. |
class |
string |
— | Additional classes for the root. |
className |
string |
— | Alias for class. |
listClass |
string |
— | Additional classes for the tab list. |
listClassName |
string |
— | Alias for listClass. |
contentClass |
string |
— | Additional classes for every generated content panel. |
contentClassName |
string |
— | Alias for contentClass. |
indicatorClass |
string |
— | Additional classes for the active indicator. |
indicatorClassName |
string |
— | Alias for indicatorClass. |
onValueChange |
(value: string) => void |
— | Called when the active tab changes. |
TabsItem
| Property | Type | Required | Description |
|---|---|---|---|
value |
string |
Yes | Unique tab value. |
label |
unknown |
No | Visible tab label. |
children |
unknown |
No | Custom tab content. Takes priority over label. |
content |
unknown |
No | Panel content for this tab. |
disabled |
boolean |
No | Disables the tab trigger. |
class |
string |
No | Additional classes for the trigger. |
className |
string |
No | Alias for class. |
contentClass |
string |
No | Additional classes for this tab’s panel. |
contentClassName |
string |
No | Alias for contentClass. |
Slots
Tabs renders these data slots:
tabstabs-listtabs-triggertabs-contenttabs-indicator
The root and triggers receive attributes from the tabs controller:
data-valuedata-statearia-selectedaria-controlstabindex