Skip to content

Tabs

Tab navigation for switching between related views or panels.

Source Code

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()} => {
          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.

Project health and key metrics.

Recent project activity appears here.

Workspace preferences and access controls.

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.

Overview panel
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)

PropTypeDefaultDescription
tabsTabsItem[][]Items rendered as tab triggers.
childrenunknownComposed Tabs.List, Tabs.Trigger, and Tabs.Content markup. Takes priority over tabs.
valuestringInitial active value.
selectedValuestringInitial active value for uncontrolled usage.
defaultValuestringAlias 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.
activateOnFocusbooleanfalseUses automatic arrow-key activation when true.
classstringAdditional classes for the root.
classNamestringAlias for class.
listClassstringAdditional classes for the tab list.
listClassNamestringAlias for listClass.
contentClassstringAdditional classes for every generated content panel.
contentClassNamestringAlias for contentClass.
indicatorClassstringAdditional classes for the active indicator.
indicatorClassNamestringAlias for indicatorClass.
onValueChange(value: string) => voidCalled when the active tab changes.

TabsItem

PropertyTypeRequiredDescription
valuestringYesUnique tab value.
labelunknownNoVisible tab label.
childrenunknownNoCustom tab content. Takes priority over label.
contentunknownNoPanel content for this tab.
disabledbooleanNoDisables the tab trigger.
classstringNoAdditional classes for the trigger.
classNamestringNoAlias for class.
contentClassstringNoAdditional classes for this tab's panel.
contentClassNamestringNoAlias for contentClass.

Slots

Tabs renders these data slots:

  • tabs
  • tabs-list
  • tabs-trigger
  • tabs-content
  • tabs-indicator

The root and triggers receive attributes from the tabs controller:

  • data-value
  • data-state
  • aria-selected
  • aria-controls
  • tabindex