tabs
Tabs take their content as items, the way the Accordion does. One array describes both halves: each entry is a trigger and the panel that trigger shows.
const items = [
{ value: "account", label: "Account", content: "Your name and your handle." },
{ value: "billing", label: "Billing", content: "The card on file." },
];
<Tabs items={items} defaultValue="account" />;
<script setup lang="ts">
const items = [
{ value: "account", label: "Account", content: "Your name and your handle." },
{ value: "billing", label: "Billing", content: "The card on file." },
];
</script>
<template>
<Tabs :items="items" default-value="account" />
</template>
An item can carry an icon shown before its label and a disabled flag, which disables
that one trigger without disabling the set.
Panels with more than a sentence
content on the item is a string, which covers a caption and not much else. Anything
richer goes through the adapter’s own escape hatch: renderContent in React and the
scoped content slot in Vue, both of which receive the item and fall back to its string.
renderLabel and the label slot do the same for a trigger.
<Tabs items={items} renderContent={(item) => <InvoiceTable period={item.value} />} />
<Tabs :items="items">
<template #content="{ item }">
<InvoiceTable :period="item.value" />
</template>
</Tabs>
The two variants spend the color differently
A pill is a raised surface, so the indicator stays neutral and the color goes on the
selected label. A link has no surface, so the color is the line under the trigger. Both
read the same color prop; what changes is where it lands.
The indicator
One element does the sliding, and it is worth knowing how, because it is the only part of
this component that is not obvious from the markup. Ark measures the selected trigger and
writes its position and size onto the indicator as custom properties, then sets one axis
itself: left when the tabs are horizontal, top when they are vertical. The other axis
belongs to the recipe. That is what lets the same element be a pill behind the trigger in
one variant and a two-pixel line along the list’s edge in the other.
Orientation is not a variant
orientation is horizontal or vertical, and every slot styles itself off the
attribute Ark writes rather than off a variant. That keeps the matrix at forty two
combinations instead of eighty four, and it is what the Accordion does for the same
reason.
Selection
React takes value with onValueChange, or defaultValue to leave the selection alone.
Vue takes v-model, with defaultValue as the uncontrolled counterpart.
activationMode decides whether arrowing onto a trigger selects it. It is automatic by
default, which is right when the panels are already loaded; manual is for panels that
fetch, so a reader arrowing past three tabs does not start three requests.
Slots
The same word names the recipe slot, the data-slot attribute and the key in ui.
- base
- list
- indicator
- trigger
- leadingIcon
- label
- content
Variants
Read off the recipe, so these are the values that actually resolve. A Theme layer can change which one is the default; it cannot add a value.
- variant
- pilllink
- color
- primarysecondarysuccessinfowarningerrorneutral
- size
- smmdlg
Props
Taken by both adapters, read out of the component module. The same names and the same types work in React and Vue.
- ui
TabsUI
Per-slot class overrides.
- variant
"pill" | "link"
Defaults to
"pill"- color
"primary" | "secondary" | "success" | "info" | "warning" | "error" | "neutral"
Defaults to
"primary"- size
"sm" | "md" | "lg"
Defaults to
"md"- itemsrequired
TabsItem<ReactNode>[]
The tabs to render, in order.
- activationMode
"automatic" | "manual"
Whether arrowing onto a trigger selects it.
Defaults to
"automatic"- orientation
"horizontal" | "vertical"
Defaults to
"horizontal"- unmountOnExit
boolean
Take the panels out of the DOM while their tab is not selected.
- lazyMount
boolean
Wait until first selection to mount a panel.
React only
Also takes Omit<React.HTMLAttributes<HTMLDivElement>, "color" | "defaultValue" | "dir">, and Pick<TabsRootProps, "value" | "defaultValue" | "onValueChange" | "ids">. A className among them reaches the base slot.
- renderLabel
(item: TabsItem<React.ReactNode>) => React.ReactNode
Replace a tab's trigger text with arbitrary markup. Falls back to `item.label`.
- renderContent
(item: TabsItem<React.ReactNode>) => React.ReactNode
Replace a tab's panel with arbitrary markup. Falls back to `item.content`.