Skip to content
Areia
Esc
navigateopen⌘Jpreview
On this page

Tabs

Tab navigation with segmented and underline styles.

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

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)

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:

  • 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

Was this page helpful?