Skip to content
Kappa
Tabs
@dicehub/kappav0.4.2

Tabs

Organizes related views behind a precise, keyboard-navigable selection rail.

<script setup>
import { ref } from "vue";
import { Tabs } from "@dicehub/kappa/components/tabs";

const selected = ref("overview");
</script>

<template>
  <Tabs.Root v-model="selected">
    <Tabs.List>
      <Tabs.Trigger value="overview">Overview</Tabs.Trigger>
      <Tabs.Trigger value="runs">Runs</Tabs.Trigger>
      <Tabs.Trigger value="artifacts">Artifacts</Tabs.Trigger>
      <Tabs.Indicator />
    </Tabs.List>
    <Tabs.Content value="overview">Case overview and current status.</Tabs.Content>
    <Tabs.Content value="runs">Recent solver runs and timings.</Tabs.Content>
    <Tabs.Content value="artifacts">Meshes, logs, and exported fields.</Tabs.Content>
  </Tabs.Root>
</template>

Installation

Barrel

import { Tabs } from "@dicehub/kappa";

Granular

import { Tabs } from "@dicehub/kappa/components/tabs";

Usage

Give each Trigger and Content the same stable value attribute. The root owns the selected value, while Ark UI supplies the tablist, panel relationships, roving focus, and activation behavior. Set visual variant and size on Tabs.List.

<script setup>
import { Tabs } from "@dicehub/kappa/components/tabs";
</script>

<template>
  <Tabs.Root default-value="mesh">
    <Tabs.List>
      <Tabs.Trigger value="mesh">Mesh</Tabs.Trigger>
      <Tabs.Trigger value="solver">Solver</Tabs.Trigger>
      <Tabs.Trigger value="results">Results</Tabs.Trigger>
      <Tabs.Indicator />
    </Tabs.List>
    <Tabs.Content value="mesh">The mesh contains 2.4 million cells.</Tabs.Content>
    <Tabs.Content value="solver">PIMPLE is using six outer correctors.</Tabs.Content>
    <Tabs.Content value="results">Pressure and force histories are ready.</Tabs.Content>
  </Tabs.Root>
</template>

Composition

Typical part hierarchy
Tabs.Root
├── Tabs.List
│   ├── Tabs.Trigger
│   ├── Tabs.Trigger
│   └── Tabs.Indicator (optional)
├── Tabs.Content
└── Tabs.Content

Use the compound namespace for concise templates or named exports for explicit imports. Place Indicator inside List so Ark UI can measure and move it to the selected trigger. The Context part exposes the reactive Ark UI API to a scoped slot when a custom control needs to call selectNext or inspect focus.

<script setup>
import {
  TabsRoot,
  TabsList,
  TabsTrigger,
  TabsContent,
  TabsIndicator,
} from "@dicehub/kappa/components/tabs";
</script>

<template>
  <TabsRoot default-value="summary">
    <TabsList>
      <TabsTrigger value="summary">Summary</TabsTrigger>
      <TabsTrigger value="log">Log</TabsTrigger>
      <TabsIndicator />
    </TabsList>
    <TabsContent value="summary">Run summary.</TabsContent>
    <TabsContent value="log">Solver log excerpt.</TabsContent>
  </TabsRoot>
</template>

Examples

Basic

Use one root with a list, triggers, optional indicator, and matching content panels.

<Tabs.Root default-value="overview">
  <Tabs.List>
    <Tabs.Trigger value="overview">Overview</Tabs.Trigger>
    <Tabs.Trigger value="runs">Runs</Tabs.Trigger>
    <Tabs.Trigger value="artifacts">Artifacts</Tabs.Trigger>
    <Tabs.Indicator />
  </Tabs.List>
  <Tabs.Content value="overview">Case overview and current status.</Tabs.Content>
  <Tabs.Content value="runs">Recent solver runs and timings.</Tabs.Content>
  <Tabs.Content value="artifacts">Meshes, logs, and exported fields.</Tabs.Content>
</Tabs.Root>

Variants

Use the segmented rail for compact view switches or the line rail for full-width sections.

<div class="tabs-stack">
  <Tabs.Root default-value="preview">
    <Tabs.List variant="segmented">
      <Tabs.Trigger value="preview">Preview</Tabs.Trigger>
      <Tabs.Trigger value="code">Code</Tabs.Trigger>
      <Tabs.Trigger value="history">History</Tabs.Trigger>
      <Tabs.Indicator />
    </Tabs.List>
    <Tabs.Content value="preview">Preview is selected.</Tabs.Content>
    <Tabs.Content value="code">Code is selected.</Tabs.Content>
    <Tabs.Content value="history">History is selected.</Tabs.Content>
  </Tabs.Root>

  <Tabs.Root default-value="preview">
    <Tabs.List variant="line">
      <Tabs.Trigger value="preview">Preview</Tabs.Trigger>
      <Tabs.Trigger value="code">Code</Tabs.Trigger>
      <Tabs.Trigger value="history">History</Tabs.Trigger>
      <Tabs.Indicator />
    </Tabs.List>
    <Tabs.Content value="preview">Preview is selected.</Tabs.Content>
    <Tabs.Content value="code">Code is selected.</Tabs.Content>
    <Tabs.Content value="history">History is selected.</Tabs.Content>
  </Tabs.Root>
</div>

Sizes

Use the small size in dense toolbars and the base size for standard page controls.

<div class="tabs-stack">
  <Tabs.Root default-value="daily">
    <Tabs.List size="base">
      <Tabs.Trigger value="daily">Daily</Tabs.Trigger>
      <Tabs.Trigger value="weekly">Weekly</Tabs.Trigger>
      <Tabs.Trigger value="monthly">Monthly</Tabs.Trigger>
      <Tabs.Indicator />
    </Tabs.List>
    <Tabs.Content value="daily">Daily data.</Tabs.Content>
    <Tabs.Content value="weekly">Weekly data.</Tabs.Content>
    <Tabs.Content value="monthly">Monthly data.</Tabs.Content>
  </Tabs.Root>

  <Tabs.Root default-value="daily">
    <Tabs.List size="sm">
      <Tabs.Trigger value="daily">Daily</Tabs.Trigger>
      <Tabs.Trigger value="weekly">Weekly</Tabs.Trigger>
      <Tabs.Trigger value="monthly">Monthly</Tabs.Trigger>
      <Tabs.Indicator />
    </Tabs.List>
    <Tabs.Content value="daily">Daily data.</Tabs.Content>
    <Tabs.Content value="weekly">Weekly data.</Tabs.Content>
    <Tabs.Content value="monthly">Monthly data.</Tabs.Content>
  </Tabs.Root>
</div>

Icons

Place a decorative icon before a concise label. The text keeps the trigger name clear.

<script setup>
import { ChartLine, File, Settings } from "@lucide/vue";
import { Tabs } from "@dicehub/kappa/components/tabs";
</script>

<template>
  <Tabs.Root default-value="analytics">
    <Tabs.List variant="line">
      <Tabs.Trigger value="analytics"><ChartLine />Analytics</Tabs.Trigger>
      <Tabs.Trigger value="files"><File />Files</Tabs.Trigger>
      <Tabs.Trigger value="settings"><Settings />Settings</Tabs.Trigger>
      <Tabs.Indicator />
    </Tabs.List>
    <Tabs.Content value="analytics">Analytics are ready.</Tabs.Content>
    <Tabs.Content value="files">Files are ready.</Tabs.Content>
    <Tabs.Content value="settings">Settings are ready.</Tabs.Content>
  </Tabs.Root>
</template>

Many Tabs

Keep a long tab set on one line. A wider constrained rail supports fast scanning and horizontal scrolling.

<script setup>
import { Tabs } from "@dicehub/kappa/components/tabs";

const tabs = [
  ["overview", "Overview"],
  ["analytics", "Analytics"],
  ["reports", "Reports"],
  ["notifications", "Notifications"],
  ["settings", "Settings"],
  ["billing", "Billing"],
  ["security", "Security"],
  ["integrations", "Integrations"],
];
</script>

<template>
  <Tabs.Root default-value="overview" class="many-tabs">
    <Tabs.List>
      <Tabs.Trigger v-for="tab in tabs" :key="tab[0]" :value="tab[0]">
        {{ tab[1] }}
      </Tabs.Trigger>
      <Tabs.Indicator />
    </Tabs.List>
    <Tabs.Content v-for="tab in tabs" :key="tab[0]" :value="tab[0]">
      {{ tab[1] }} view is selected.
    </Tabs.Content>
  </Tabs.Root>
</template>

<style scoped>
.many-tabs {
  inline-size: min(100%, 29rem);
}
</style>

Horizontal Overflow

Constrain the rail when space is tight. Edge controls reveal off-screen tabs without widening the page.

<script setup>
import { ChevronLeft, ChevronRight } from "@lucide/vue";
import { nextTick, onBeforeUnmount, onMounted, ref } from "vue";
import { Tabs } from "@dicehub/kappa/components/tabs";

const tabs = ["Overview", "Analytics", "Reports", "Notifications", "Settings"];
const rail = ref(null);
const canScrollBack = ref(false);
const canScrollForward = ref(false);

const getList = () => rail.value?.querySelector('[role="tablist"]');
const updateOverflow = () => {
  const list = getList();
  if (!list) return;
  const max = list.scrollWidth - list.clientWidth;
  canScrollBack.value = list.scrollLeft > 1;
  canScrollForward.value = list.scrollLeft < max - 1;
};
const scrollTabs = (direction) => {
  getList()?.scrollBy({ left: direction * 200, behavior: "smooth" });
};

let resizeObserver;
onMounted(async () => {
  await nextTick();
  const list = getList();
  if (!list) return;
  resizeObserver = new ResizeObserver(updateOverflow);
  resizeObserver.observe(list);
  updateOverflow();
});
onBeforeUnmount(() => resizeObserver?.disconnect());
</script>

<template>
  <Tabs.Root default-value="Overview" class="overflow-tabs">
    <div ref="rail" class="overflow-tabs__rail">
      <Tabs.List class="overflow-tabs__list" @scroll.passive="updateOverflow">
        <Tabs.Trigger v-for="tab in tabs" :key="tab" :value="tab">{{ tab }}</Tabs.Trigger>
        <Tabs.Indicator />
      </Tabs.List>
      <button
        v-if="canScrollBack"
        type="button"
        class="overflow-tabs__button overflow-tabs__button--start"
        aria-label="Scroll tabs backward"
        @click="scrollTabs(-1)"
      >
        <ChevronLeft aria-hidden="true" />
      </button>
      <button
        v-if="canScrollForward"
        type="button"
        class="overflow-tabs__button overflow-tabs__button--end"
        aria-label="Scroll tabs forward"
        @click="scrollTabs(1)"
      >
        <ChevronRight aria-hidden="true" />
      </button>
    </div>
    <Tabs.Content v-for="tab in tabs" :key="tab" :value="tab">
      {{ tab }} view is selected.
    </Tabs.Content>
  </Tabs.Root>
</template>

<style scoped>
.overflow-tabs {
  inline-size: min(100%, 17.5rem);
}

.overflow-tabs__rail {
  position: relative;
}

.overflow-tabs__list {
  scroll-padding-inline: 2.5rem;
}

.overflow-tabs__button {
  position: absolute;
  z-index: 2;
  inset-block: 1px;
  inline-size: 2rem;
  border: 0;
}

.overflow-tabs__button--start {
  inset-inline-start: 1px;
}

.overflow-tabs__button--end {
  inset-inline-end: 1px;
}
</style>

Right to Left

Set dir to rtl when the component must override its inherited document direction.

<Tabs.Root default-value="overview" dir="rtl">
  <Tabs.List variant="line">
    <Tabs.Trigger value="overview">Overview</Tabs.Trigger>
    <Tabs.Trigger value="reports">Reports</Tabs.Trigger>
    <Tabs.Trigger value="settings">Settings</Tabs.Trigger>
    <Tabs.Indicator />
  </Tabs.List>
  <Tabs.Content value="overview">Direction-aware overview.</Tabs.Content>
  <Tabs.Content value="reports">Direction-aware reports.</Tabs.Content>
  <Tabs.Content value="settings">Direction-aware settings.</Tabs.Content>
</Tabs.Root>

Manual Activation

Set activationMode to manual when arrow-key focus should not change content until Enter or Space.

<Tabs.Root default-value="setup" activation-mode="manual">
  <Tabs.List variant="line">
    <Tabs.Trigger value="setup">Setup</Tabs.Trigger>
    <Tabs.Trigger value="review">Review</Tabs.Trigger>
    <Tabs.Trigger value="submit">Submit</Tabs.Trigger>
    <Tabs.Indicator />
  </Tabs.List>
  <Tabs.Content value="setup">Configure the case.</Tabs.Content>
  <Tabs.Content value="review">Review the input values.</Tabs.Content>
  <Tabs.Content value="submit">Submit a validated run.</Tabs.Content>
</Tabs.Root>

Vertical

Use vertical orientation for an inspector or settings rail. Ark UI switches navigation to Up and Down.

<Tabs.Root default-value="geometry" orientation="vertical">
  <Tabs.List variant="line">
    <Tabs.Trigger value="geometry">Geometry</Tabs.Trigger>
    <Tabs.Trigger value="mesh">Mesh</Tabs.Trigger>
    <Tabs.Trigger value="boundaries">Boundaries</Tabs.Trigger>
    <Tabs.Indicator />
  </Tabs.List>
  <Tabs.Content value="geometry">The geometry is watertight.</Tabs.Content>
  <Tabs.Content value="mesh">All mesh quality checks passed.</Tabs.Content>
  <Tabs.Content value="boundaries">Seven boundary patches are assigned.</Tabs.Content>
</Tabs.Root>

Controlled

Bind v-model when route state, a parent workflow, or another control owns the selected value.

<script setup>
import { ref } from "vue";
import { Tabs } from "@dicehub/kappa/components/tabs";

const selected = ref("residuals");
</script>

<template>
  <Tabs.Root v-model="selected">
    <Tabs.List>
      <Tabs.Trigger value="residuals">Residuals</Tabs.Trigger>
      <Tabs.Trigger value="forces">Forces</Tabs.Trigger>
      <Tabs.Indicator />
    </Tabs.List>
    <Tabs.Content value="residuals">All residuals are below 1e-5.</Tabs.Content>
    <Tabs.Content value="forces">Mean drag coefficient is 0.31.</Tabs.Content>
  </Tabs.Root>
  <output aria-live="polite">Selected: {{ selected }}</output>
</template>

Disabled

Disable one trigger while preserving the surrounding tab order and accessible state.

<Tabs.Root default-value="available">
  <Tabs.List>
    <Tabs.Trigger value="available">Available</Tabs.Trigger>
    <Tabs.Trigger value="pending" disabled>Pending data</Tabs.Trigger>
    <Tabs.Trigger value="history">History</Tabs.Trigger>
    <Tabs.Indicator />
  </Tabs.List>
  <Tabs.Content value="available">Current diagnostics are ready.</Tabs.Content>
  <Tabs.Content value="pending">This panel is not available yet.</Tabs.Content>
  <Tabs.Content value="history">Previous diagnostics are archived.</Tabs.Content>
</Tabs.Root>

Dynamic Tab Count

Change the number of tabs at runtime. Overflow controls disappear when the remaining seven tabs fit.

<script setup>
import { ChevronLeft, ChevronRight } from "@lucide/vue";
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from "vue";
import { Tabs } from "@dicehub/kappa/components/tabs";

const standardTabs = [
  "Overview",
  "Metrics",
  "Releases",
  "Observability",
  "Domains",
  "Access",
  "Settings",
];
const extraTabs = ["Analytics", "Logs", "Security"];
const showExtraTabs = ref(true);
const selectedTab = ref("Settings");
const tabs = computed(() =>
  showExtraTabs.value ? [...standardTabs, ...extraTabs] : standardTabs,
);
const rail = ref(null);
const canScrollBack = ref(false);
const canScrollForward = ref(false);

const getList = () => rail.value?.querySelector('[role="tablist"]');
const updateOverflow = () => {
  const list = getList();
  if (!list) return;
  const max = list.scrollWidth - list.clientWidth;
  canScrollBack.value = list.scrollLeft > 1;
  canScrollForward.value = list.scrollLeft < max - 1;
};
const scrollTabs = (direction) => {
  getList()?.scrollBy({ left: direction * 300, behavior: "smooth" });
};
const revealSelectedTab = async () => {
  await nextTick();
  const selected = getList()?.querySelector('[role="tab"][aria-selected="true"]');
  selected?.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "nearest" });
};

let resizeObserver;
onMounted(async () => {
  await nextTick();
  const list = getList();
  if (!list) return;
  resizeObserver = new ResizeObserver(updateOverflow);
  resizeObserver.observe(list);
  updateOverflow();
});
onBeforeUnmount(() => resizeObserver?.disconnect());
watch(tabs, async () => {
  await nextTick();
  updateOverflow();
});
watch(selectedTab, revealSelectedTab);
</script>

<template>
  <div class="dynamic-tabs">
    <Tabs.Root v-model="selectedTab">
      <div ref="rail" class="dynamic-tabs__rail">
        <Tabs.List size="sm" @scroll.passive="updateOverflow">
          <Tabs.Trigger v-for="tab in tabs" :key="tab" :value="tab">
            {{ tab }}
          </Tabs.Trigger>
          <Tabs.Indicator />
        </Tabs.List>
        <button
          v-if="canScrollBack"
          type="button"
          aria-label="Scroll tabs backward"
          @click="scrollTabs(-1)"
        >
          <ChevronLeft aria-hidden="true" />
        </button>
        <button
          v-if="canScrollForward"
          type="button"
          aria-label="Scroll tabs forward"
          @click="scrollTabs(1)"
        >
          <ChevronRight aria-hidden="true" />
        </button>
      </div>
      <Tabs.Content v-for="tab in tabs" :key="tab" :value="tab">
        {{ tab }} view is selected.
      </Tabs.Content>
    </Tabs.Root>
    <div class="dynamic-tabs__controls">
      <button type="button" @click="showExtraTabs = !showExtraTabs">Toggle extra tabs</button>
      <output aria-live="polite">{{ tabs.length }} tabs</output>
    </div>
  </div>
</template>

<style scoped>
.dynamic-tabs {
  display: grid;
  inline-size: min(100%, 42rem);
  gap: 0.75rem;
}

.dynamic-tabs__rail {
  position: relative;
}

.dynamic-tabs__rail :deep([role="tablist"]) {
  inline-size: 100%;
  scroll-padding-inline: 3.25rem;
}

.dynamic-tabs__rail > button {
  position: absolute;
  z-index: 2;
  inset-block: 1px;
  inline-size: 2.5rem;
  border: 0;
}

.dynamic-tabs__rail > button[aria-label="Scroll tabs backward"] {
  inset-inline-start: 1px;
}

.dynamic-tabs__rail > button[aria-label="Scroll tabs forward"] {
  inset-inline-end: 1px;
}

.dynamic-tabs__controls {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}
</style>

Dynamic and Lazy

Combine v-if with lazyMount and unmountOnExit for panels that appear as data becomes available.

<script setup>
import { ref } from "vue";
import { Tabs } from "@dicehub/kappa/components/tabs";

const showInspect = ref(false);
</script>

<template>
  <Tabs.Root lazy-mount unmount-on-exit default-value="overview">
    <Tabs.List>
      <Tabs.Trigger value="overview">Overview</Tabs.Trigger>
      <Tabs.Trigger value="logs">Logs</Tabs.Trigger>
      <Tabs.Trigger v-if="showInspect" value="inspect">Inspect</Tabs.Trigger>
      <Tabs.Indicator />
    </Tabs.List>
    <Tabs.Content value="overview">Overview is mounted only while selected.</Tabs.Content>
    <Tabs.Content value="logs">Logs mount only when selected.</Tabs.Content>
    <Tabs.Content v-if="showInspect" value="inspect">Inspect is dynamic content.</Tabs.Content>
  </Tabs.Root>
  <button type="button" @click="showInspect = !showInspect">
    {{ showInspect ? "Remove inspect" : "Add inspect" }}
  </button>
</template>

Content-rich

Panels accept normal Vue content, including metrics, tables, forms, and review notes.

<Tabs.Root default-value="metrics">
  <Tabs.List>
    <Tabs.Trigger value="metrics">Metrics</Tabs.Trigger>
    <Tabs.Trigger value="boundaries">Boundaries</Tabs.Trigger>
    <Tabs.Trigger value="notes">Notes</Tabs.Trigger>
    <Tabs.Indicator />
  </Tabs.List>
  <Tabs.Content value="metrics">
    <dl>
      <div><dt>Cells</dt><dd>2.4M</dd></div>
      <div><dt>Residual</dt><dd>8.4e-6</dd></div>
      <div><dt>Courant max</dt><dd>0.82</dd></div>
    </dl>
  </Tabs.Content>
  <Tabs.Content value="boundaries">
    <table><caption>Patch summary</caption>...</table>
  </Tabs.Content>
  <Tabs.Content value="notes">Add review notes alongside any Vue content.</Tabs.Content>
</Tabs.Root>

Accessibility

  • The list uses role="tablist"; each trigger uses role="tab" and each panel uses role="tabpanel".
  • Tab enters the selected trigger. Arrow keys move between enabled triggers; Home and End move to the first and last trigger.
  • Horizontal lists use Left and Right arrows. Vertical lists use Up and Down arrows. Direction follows the root locale and dir.
  • Automatic activation selects a tab on focus. Manual activation moves focus first and waits for Enter or Space to select.
  • Disabled triggers are unavailable and skipped by directional keyboard navigation.
  • Keep trigger labels concise and give the root an accessible name when the visible context is not enough.
  • Keep focus visible. Indicator and trigger transitions honor prefers-reduced-motion.

Kappa preserves Ark UI behavior.

Official Ark UI Tabs documentation

API Reference

Tabs.Root

PropTypeDefaultDescription
modelValuestring-Controlled selected value; supports v-model.
defaultValuestring-Initial selected value for uncontrolled tabs.
activationModeautomatic | manualautomaticActivate on focus, or wait for Enter or Space after focus.
orientationhorizontal | verticalhorizontalChanges layout semantics and directional keyboard navigation.
dirltr | rtlinheritedOverrides the inherited Ark UI locale direction.
loopFocusbooleantrueWraps arrow-key focus from the last enabled trigger to the first.
deselectablebooleanfalseAllows the active tab to be cleared when clicked again.
compositebooleantrueUses a roving tab stop for the tablist and panel focus behavior.
lazyMountbooleanfalseDelays each content panel until it is selected for the first time.
unmountOnExitbooleanfalseRemoves deselected content panels from the DOM.
idstringgeneratedStable identifier for the Ark UI state machine.
idsTabsRootProps['ids']generatedOverrides generated root, list, trigger, content, and indicator IDs.
navigate(details) => void-Handles navigation when triggers are composed as links.
translationsTabsRootProps['translations']-Localized tablist labels used by Ark UI.
asChildbooleanfalseMerges root behavior into one custom child element.

Tabs.List

PropTypeDefaultDescription
variantsegmented | linesegmentedUses a compact selected surface or a full-width selection line.
sizesm | basebaseControls the list height, trigger spacing, and type scale.
asChildbooleanfalseComposes tablist behavior onto one custom element.

Tabs.Trigger

PropTypeDefaultDescription
valuestringrequiredUnique value paired with one content panel.
disabledbooleanfalsePrevents activation and removes the trigger from arrow-key navigation.
asChildbooleanfalseComposes tab behavior onto one custom button or link.

Tabs.Content

PropTypeDefaultDescription
valuestringrequiredValue that selects this content panel.
asChildbooleanfalseComposes panel semantics onto one custom element.

Parts

PartElementDescription
Tabs.RootdivOwns selected value, activation mode, orientation, locale direction, and render strategy.
Tabs.RootProviderdivRoot variant driven by an external useTabs machine.
Tabs.Listdiv[role=tablist]Keyboard navigation container with the localized tablist semantics.
Tabs.Triggerbutton[role=tab]Selects a value and participates in Ark UI roving focus.
Tabs.Contentdiv[role=tabpanel]Associated panel with managed visibility and labelled-by relationship.
Tabs.IndicatordivOptional measured surface that follows the selected trigger.
Tabs.ContextrenderlessExposes the reactive Ark UI tabs API to a scoped slot.

Events

EventPayloadDescription
update:modelValuestringEmitted when the selected tab changes; drives v-model.
valueChange{ value: string }Ark UI detail emitted after the selected value changes.
focusChange{ focusedValue: string }Ark UI detail emitted when the focused trigger changes.

Exports

ExportDescription
TabsCompound API exposing Root, RootProvider, List, Trigger, Content, Indicator, and Context.
TabsRootUnaugmented root with controlled and uncontrolled selected value.
TabsRootProviderRoot backed by an external useTabs state machine.
TabsListAccessible tablist container.
TabsTriggerKeyboard-operable tab trigger.
TabsContentAssociated tabpanel with optional lazy rendering.
TabsIndicatorOptional moving selection surface.
TabsContextScoped-slot state access.
useTabsCreates a Tabs state machine for RootProvider.
useTabsContextReads Tabs state inside the compound component.
tabsAnatomyArk UI part anatomy metadata.
TabsPropsPublic root prop contract.
TabsEmitsRoot event contract.
TabsVariantSegmented or line list treatment.
TabsSizeSmall or base list geometry.
TABS_VARIANTSSupported visual variant values.
TABS_SIZESSupported size values.
TabsValueChangeDetailsPayload for valueChange.
TabsFocusChangeDetailsPayload for focusChange.