Components

Tabs

Switch active panel via tab selection while preserving keyboard navigation semantics.

Release Scope: Canonical Implementation: Core Site Visibility: Listed Adapters: React, Vanilla, Solid, Vue, Svelte Category: navigation

1. Live Preview

vanilla live preview activates on the client; source HTML keeps a readable preview island and primitive contract.

vanilla tabs preview activates on the client.

2. Installation

Install adapter-specific dependencies first, then complete setup via the installation guide. installation docs

pnpm add @nake-ui/primitives @nake-ui/adapter-vanilla @nake-ui/theme-vanilla

3. Usage

Minimal runnable usage for the current adapter.

import "@nake-ui/theme-vanilla/styles.css";
import { bindTabs } from "@nake-ui/adapter-vanilla";

const binding = bindTabs(nodes, items, options);
binding.destroy();

4. Examples

Line

Set `data-variant="line"` on the tabs list for a line style.

Track delivery readiness and milestone status.

Use data-variant="line" on the tabs list for a line style.

import "@nake-ui/theme-vanilla/styles.css";
import { bindTabs } from "@nake-ui/adapter-vanilla";

if (typeof window !== "undefined" && typeof document !== "undefined") {
  list.dataset.variant = "line";

  const binding = bindTabs(
    {
      list,
      tabs: { overview: tabOverview, activity: tabActivity, members: tabMembers },
      panels: { overview: panelOverview, activity: panelActivity, members: panelMembers }
    },
    [
      { id: "overview" },
      { id: "activity" },
      { id: "members" }
    ],
    {
      id: "vanilla-tabs-line-controller",
      defaultValue: "overview"
    }
  );

  // cleanup on unmount
  binding.destroy();
}

tabOverview.textContent = "Overview";

Vertical

Use `orientation="vertical"` for vertical tabs.

Edit profile metadata and public identity fields.

Use orientation="vertical" for vertical tabs.

import "@nake-ui/theme-vanilla/styles.css";
import { bindTabs } from "@nake-ui/adapter-vanilla";

if (typeof window !== "undefined" && typeof document !== "undefined") {
  const binding = bindTabs(
    {
      list,
      tabs: { profile: tabProfile, security: tabSecurity, billing: tabBilling },
      panels: { profile: panelProfile, security: panelSecurity, billing: panelBilling }
    },
    [
      { id: "profile" },
      { id: "security" },
      { id: "billing" }
    ],
    {
      id: "vanilla-tabs-vertical-controller",
      defaultValue: "profile",
      orientation: "vertical"
    }
  );

  // cleanup on unmount
  binding.destroy();
}

tabProfile.textContent = "Profile";

Disabled

Disabled tabs stay visible but cannot be focused or activated.

Service overview and release health.

Disabled tabs stay visible but cannot be focused or activated.

import "@nake-ui/theme-vanilla/styles.css";
import { bindTabs } from "@nake-ui/adapter-vanilla";

if (typeof window !== "undefined" && typeof document !== "undefined") {
  const binding = bindTabs(
    {
      list,
      tabs: { overview: tabOverview, audit: tabAudit, alerts: tabAlerts },
      panels: { overview: panelOverview, audit: panelAudit, alerts: panelAlerts }
    },
    [
      { id: "overview" },
      { id: "audit", disabled: true },
      { id: "alerts" }
    ],
    {
      id: "vanilla-tabs-disabled-controller",
      defaultValue: "overview"
    }
  );

  // cleanup on unmount
  binding.destroy();
}

tabAudit.textContent = "Audit Logs";
tabAudit.disabled = true;

Icons

Compose icons and labels inside tabs triggers.

Overview metrics and release summary.

Compose icons and labels inside tabs triggers for denser navigation hints.

import "@nake-ui/theme-vanilla/styles.css";
import { bindTabs } from "@nake-ui/adapter-vanilla";

const tabs = [
  { id: "overview", label: "Overview", icon: "OV" },
  { id: "insights", label: "Insights", icon: "IN" },
  { id: "settings", label: "Settings", icon: "ST" },
];

if (typeof window !== "undefined" && typeof document !== "undefined") {
  const binding = bindTabs(
    {
      list,
      tabs: Object.fromEntries(tabs.map((tab) => [tab.id, tabElements[tab.id]])),
      panels: Object.fromEntries(tabs.map((tab) => [tab.id, panelElements[tab.id]]))
    },
    tabs.map((tab) => ({ id: tab.id })),
    {
      id: "vanilla-tabs-icons-controller",
      defaultValue: tabs[0]?.id ?? null
    }
  );

  // cleanup on unmount
  binding.destroy();
}

5. RTL

The same structure running inside a `dir="rtl"` container.

vanilla tabs preview activates on the client.

6. Closure Coverage

Complete

Closure Score: 100% (19/19)

Required Checks

  • schemaRegistered
  • specDocumented
  • reactMapper
  • siteExampleCoverage
  • siteA11yCoverage
  • siteVisible
  • schemaValidatorCoverage
  • schemaAriaCoverage
  • specValidatorRulesSection
  • specTestChecklistSection
  • specSsrHydrationSection
  • reactSsrHydrationCoverage
  • reactRenderLevelSsrCoverage
  • solidSsrCoverage
  • coreImplemented
  • primitiveUnitTest
  • domContractSnapshot
  • vanillaBinding
  • adapterParitySnapshot

All required closure checks are currently satisfied.

7. Purpose / Non-goals

Switch active panel via tab selection while preserving keyboard navigation semantics.

  • Panel layout/styling
  • Async data loading strategy

8. Anatomy & Slot

  • list
  • tab (repeatable)
  • panel (repeatable)

9. DOM Contract

<div data-ui="tabs" data-slot="list" role="tablist" data-orientation="horizontal">
  <button data-slot="tab" role="tab" aria-controls="panel-a" aria-selected="true"></button>
</div>
<div id="panel-a" data-slot="panel" role="tabpanel" aria-labelledby="tab-a"></div>

10. State & Events

State Model

  • selected tab id
  • controlled: value + onValueChange
  • uncontrolled: defaultValue
  • activation mode: manual / automatic

Event Model

  • arrow/home/end move roving focus
  • Enter/Space activate in manual mode

11. Keyboard & Focus

  • roving tabindex required
  • exactly one tabIndex=0 tab at a time

12. ARIA

  • role=tablist/tab/tabpanel
  • aria-selected, aria-controls, aria-labelledby

13. Validator Rules

  • state.multiple-active-roving-item
  • focus.invalid-tabindex-roving
  • aria.role-mismatch

14. Adapter Notes

Vanilla

  • Primary binder: bindTabs.
  • Binds explicit DOM nodes and should be cleaned up on unmount.

15. Example Mapping

16. API

Core

  • createTabsController value
  • TabsController type
  • TabsControllerOptions type
  • TabsKeyboardResult type
  • TabsRegistration type

Vanilla

  • bindTabs function
  • TabsElements interface