PettyUI/.firecrawl/kobalte.dev-docs-core-components-tabs.md
2026-03-30 12:08:51 +07:00

20 KiB

Tabs

A set of layered sections of content, known as tab panels, that display one panel of content at a time.

Import

Copyts
import { Tabs } from "@kobalte/core/tabs";
// or
import { Root, List, ... } from "@kobalte/core/tabs";
// or (deprecated)
import { Tabs } from "@kobalte/core";
Copyts
import { Tabs } from "@kobalte/core/tabs";
// or
import { Root, List, ... } from "@kobalte/core/tabs";
// or (deprecated)
import { Tabs } from "@kobalte/core";

Features

  • Follow the WAI ARIA Tabs design pattern, semantically linking tabs and their associated tab panels.
  • Support for LTR and RTL keyboard navigation.
  • Support for disabled tabs.
  • Supports horizontal/vertical orientation.
  • Supports automatic/manual activation.
  • Focus management for tab panels without any focusable children.
  • Can be controlled or uncontrolled.

Anatomy

The tabs consist of:

  • Tabs: The root container for tabs and tab contents.
  • Tabs.List: Contains the tabs that are aligned along the edge of the active tab content.
  • Tabs.Trigger: The button that activates its associated tab content.
  • Tabs.Indicator: The visual indicator displayed at the bottom of the tab list to indicate the selected tab.
  • Tabs.Content: Contains the content associated with a tab trigger.
Copytsx
<Tabs>
	<Tabs.List>
		<Tabs.Trigger />
		<Tabs.Indicator />
	</Tabs.List>
	<Tabs.Content />
</Tabs>
Copytsx
<Tabs>
	<Tabs.List>
		<Tabs.Trigger />
		<Tabs.Indicator />
	</Tabs.List>
	<Tabs.Content />
</Tabs>

Example

ProfileDashboardSettingsContact

Profile details

index.tsxstyle.css

Copytsx
import { Tabs } from "@kobalte/core/tabs";
import "./style.css";

function App() {
  return (
    <Tabs aria-label="Main navigation" class="tabs">
      <Tabs.List class="tabs__list">
        <Tabs.Trigger class="tabs__trigger" value="profile">Profile</Tabs.Trigger>
        <Tabs.Trigger class="tabs__trigger" value="dashboard">Dashboard</Tabs.Trigger>
        <Tabs.Trigger class="tabs__trigger" value="settings">Settings</Tabs.Trigger>
        <Tabs.Trigger class="tabs__trigger" value="contact">Contact</Tabs.Trigger>
        <Tabs.Indicator class="tabs__indicator" />
      </Tabs.List>
      <Tabs.Content class="tabs__content" value="profile">Profile details</Tabs.Content>
      <Tabs.Content class="tabs__content" value="dashboard">Dashboard details</Tabs.Content>
      <Tabs.Content class="tabs__content" value="settings">Settings details</Tabs.Content>
      <Tabs.Content class="tabs__content" value="contact">Contact details</Tabs.Content>
    </Tabs>
  );
}
Copytsx
import { Tabs } from "@kobalte/core/tabs";
import "./style.css";

function App() {
  return (
    <Tabs aria-label="Main navigation" class="tabs">
      <Tabs.List class="tabs__list">
        <Tabs.Trigger class="tabs__trigger" value="profile">Profile</Tabs.Trigger>
        <Tabs.Trigger class="tabs__trigger" value="dashboard">Dashboard</Tabs.Trigger>
        <Tabs.Trigger class="tabs__trigger" value="settings">Settings</Tabs.Trigger>
        <Tabs.Trigger class="tabs__trigger" value="contact">Contact</Tabs.Trigger>
        <Tabs.Indicator class="tabs__indicator" />
      </Tabs.List>
      <Tabs.Content class="tabs__content" value="profile">Profile details</Tabs.Content>
      <Tabs.Content class="tabs__content" value="dashboard">Dashboard details</Tabs.Content>
      <Tabs.Content class="tabs__content" value="settings">Settings details</Tabs.Content>
      <Tabs.Content class="tabs__content" value="contact">Contact details</Tabs.Content>
    </Tabs>
  );
}

Usage

Default value

A default selected tab can be provided using the defaultValue prop, which should correspond to the value prop provided to each tab.

ProfileDashboardSettingsContact

Dashboard details

Copytsx
<Tabs defaultValue="dashboard">
	<Tabs.List>
		<Tabs.Trigger value="profile">Profile</Tabs.Trigger>
		<Tabs.Trigger value="dashboard">Dashboard</Tabs.Trigger>
		<Tabs.Trigger value="settings">Settings</Tabs.Trigger>
		<Tabs.Trigger value="contact">Contact</Tabs.Trigger>
		<Tabs.Indicator />
	</Tabs.List>
	<Tabs.Content value="profile">Profile details</Tabs.Content>
	<Tabs.Content value="dashboard">Dashboard details</Tabs.Content>
	<Tabs.Content value="settings">Settings details</Tabs.Content>
	<Tabs.Content value="contact">Contact details</Tabs.Content>
</Tabs>
Copytsx
<Tabs defaultValue="dashboard">
	<Tabs.List>
		<Tabs.Trigger value="profile">Profile</Tabs.Trigger>
		<Tabs.Trigger value="dashboard">Dashboard</Tabs.Trigger>
		<Tabs.Trigger value="settings">Settings</Tabs.Trigger>
		<Tabs.Trigger value="contact">Contact</Tabs.Trigger>
		<Tabs.Indicator />
	</Tabs.List>
	<Tabs.Content value="profile">Profile details</Tabs.Content>
	<Tabs.Content value="dashboard">Dashboard details</Tabs.Content>
	<Tabs.Content value="settings">Settings details</Tabs.Content>
	<Tabs.Content value="contact">Contact details</Tabs.Content>
</Tabs>

Controlled value

Selected tab can be controlled using the value prop, paired with the onChange event. The value prop from the selected tab will be passed into the callback when the tab is selected, allowing you to update state accordingly.

ProfileDashboardSettingsContact

Settings details

Selected tab: settings

Copytsx
import { createSignal } from "solid-js";

function ControlledExample() {
	const [selectedTab, setSelectedTab] = createSignal("settings");

	return (
		<>
			<Tabs value={selectedTab()} onChange={setSelectedTab}>
				<Tabs.List>
					<Tabs.Trigger value="profile">Profile</Tabs.Trigger>
					<Tabs.Trigger value="dashboard">Dashboard</Tabs.Trigger>
					<Tabs.Trigger value="settings">Settings</Tabs.Trigger>
					<Tabs.Trigger value="contact">Contact</Tabs.Trigger>
					<Tabs.Indicator />
				</Tabs.List>
				<Tabs.Content value="profile">Profile details</Tabs.Content>
				<Tabs.Content value="dashboard">Dashboard details</Tabs.Content>
				<Tabs.Content value="settings">Settings details</Tabs.Content>
				<Tabs.Content value="contact">Contact details</Tabs.Content>
			</Tabs>
			<p>Selected tab: {selectedTab()}</p>
		</>
	);
}
Copytsx
import { createSignal } from "solid-js";

function ControlledExample() {
	const [selectedTab, setSelectedTab] = createSignal("settings");

	return (
		<>
			<Tabs value={selectedTab()} onChange={setSelectedTab}>
				<Tabs.List>
					<Tabs.Trigger value="profile">Profile</Tabs.Trigger>
					<Tabs.Trigger value="dashboard">Dashboard</Tabs.Trigger>
					<Tabs.Trigger value="settings">Settings</Tabs.Trigger>
					<Tabs.Trigger value="contact">Contact</Tabs.Trigger>
					<Tabs.Indicator />
				</Tabs.List>
				<Tabs.Content value="profile">Profile details</Tabs.Content>
				<Tabs.Content value="dashboard">Dashboard details</Tabs.Content>
				<Tabs.Content value="settings">Settings details</Tabs.Content>
				<Tabs.Content value="contact">Contact details</Tabs.Content>
			</Tabs>
			<p>Selected tab: {selectedTab()}</p>
		</>
	);
}

Focusable content

When the tab content doesn't contain any focusable content, the entire content is given a tabIndex=0 so that the content can be navigated to with the keyboard.

When the tab content contains focusable content, such as an <input>, then the tabIndex is omitted because the content itself can receive focus.

This example uses the same Tabs components from above. Try navigating from the tabs to each content using the keyboard.

ProfileDashboardSettingsContact

Copytsx
function FocusableContentExample() {
	return (
		<Tabs>
			<Tabs.List>
				<Tabs.Trigger value="profile">Profile</Tabs.Trigger>
				<Tabs.Trigger value="dashboard">Dashboard</Tabs.Trigger>
				<Tabs.Trigger value="settings">Settings</Tabs.Trigger>
				<Tabs.Trigger value="contact">Contact</Tabs.Trigger>
				<Tabs.Indicator />
			</Tabs.List>
			<Tabs.Content value="profile">
				<input placeholder="Change password" />
			</Tabs.Content>
			<Tabs.Content value="dashboard">Dashboard details</Tabs.Content>
			<Tabs.Content value="settings">Settings details</Tabs.Content>
			<Tabs.Content value="contact">Contact details</Tabs.Content>
		</Tabs>
	);
}
Copytsx
function FocusableContentExample() {
	return (
		<Tabs>
			<Tabs.List>
				<Tabs.Trigger value="profile">Profile</Tabs.Trigger>
				<Tabs.Trigger value="dashboard">Dashboard</Tabs.Trigger>
				<Tabs.Trigger value="settings">Settings</Tabs.Trigger>
				<Tabs.Trigger value="contact">Contact</Tabs.Trigger>
				<Tabs.Indicator />
			</Tabs.List>
			<Tabs.Content value="profile">
				<input placeholder="Change password" />
			</Tabs.Content>
			<Tabs.Content value="dashboard">Dashboard details</Tabs.Content>
			<Tabs.Content value="settings">Settings details</Tabs.Content>
			<Tabs.Content value="contact">Contact details</Tabs.Content>
		</Tabs>
	);
}

Dynamic tabs

Tabs can be added/removed dynamically by using a signal and SolidJS For.

Add tabRemove tab

Tab 1Tab 2Tab 3

Tab body 1

Copytsx
import { createSignal } from "solid-js";

function DynamicContentExample() {
	const [tabs, setTabs] = createSignal([\
		{ id: "1", title: "Tab 1", content: "Tab body 1" },\
		{ id: "2", title: "Tab 2", content: "Tab body 2" },\
		{ id: "3", title: "Tab 3", content: "Tab body 3" },\
	]);

	const addTab = () => {
		setTabs(prev => [\
			...prev,\
			{\
				id: String(prev.length + 1),\
				title: `Tab ${prev.length + 1}`,\
				content: `Tab Body ${prev.length + 1}`,\
			},\
		]);
	};

	const removeTab = () => {
		if (tabs().length > 1) {
			setTabs(prev => prev.slice(0, -1));
		}
	};

	return (
		<>
			<button onClick={addTab}>Add tab</button>
			<button onClick={removeTab}>Remove tab</button>
			<Tabs>
				<Tabs.List>
					<For each={tabs()}>{tab => <Tabs.Trigger value={tab.id}>{tab.title}</Tabs.Trigger>}</For>
					<Tabs.Indicator />
				</Tabs.List>
				<For each={tabs()}>{tab => <Tabs.Content value={tab.id}>{tab.content}</Tabs.Content>}</For>
			</Tabs>
		</>
	);
}
Copytsx
import { createSignal } from "solid-js";

function DynamicContentExample() {
	const [tabs, setTabs] = createSignal([\
		{ id: "1", title: "Tab 1", content: "Tab body 1" },\
		{ id: "2", title: "Tab 2", content: "Tab body 2" },\
		{ id: "3", title: "Tab 3", content: "Tab body 3" },\
	]);

	const addTab = () => {
		setTabs(prev => [\
			...prev,\
			{\
				id: String(prev.length + 1),\
				title: `Tab ${prev.length + 1}`,\
				content: `Tab Body ${prev.length + 1}`,\
			},\
		]);
	};

	const removeTab = () => {
		if (tabs().length > 1) {
			setTabs(prev => prev.slice(0, -1));
		}
	};

	return (
		<>
			<button onClick={addTab}>Add tab</button>
			<button onClick={removeTab}>Remove tab</button>
			<Tabs>
				<Tabs.List>
					<For each={tabs()}>{tab => <Tabs.Trigger value={tab.id}>{tab.title}</Tabs.Trigger>}</For>
					<Tabs.Indicator />
				</Tabs.List>
				<For each={tabs()}>{tab => <Tabs.Content value={tab.id}>{tab.content}</Tabs.Content>}</For>
			</Tabs>
		</>
	);
}

Activation mode

By default, pressing the arrow keys while focus is on a Tab will switch selection to the adjacent Tab in that direction, updating the content displayed accordingly.

If you would like to prevent selection change from happening automatically you can set the activationMode prop to manual. This will prevent tab selection from changing on arrow key press, requiring a subsequent Enter or Space key press to confirm tab selection.

ProfileDashboardSettingsContact

Profile details

Copytsx
<Tabs activationMode="manual">
	<Tabs.List>
		<Tabs.Trigger value="profile">Profile</Tabs.Trigger>
		<Tabs.Trigger value="dashboard">Dashboard</Tabs.Trigger>
		<Tabs.Trigger value="settings">Settings</Tabs.Trigger>
		<Tabs.Trigger value="contact">Contact</Tabs.Trigger>
		<Tabs.Indicator />
	</Tabs.List>
	<Tabs.Content value="profile">Profile details</Tabs.Content>
	<Tabs.Content value="dashboard">Dashboard details</Tabs.Content>
	<Tabs.Content value="settings">Settings details</Tabs.Content>
	<Tabs.Content value="contact">Contact details</Tabs.Content>
</Tabs>
Copytsx
<Tabs activationMode="manual">
	<Tabs.List>
		<Tabs.Trigger value="profile">Profile</Tabs.Trigger>
		<Tabs.Trigger value="dashboard">Dashboard</Tabs.Trigger>
		<Tabs.Trigger value="settings">Settings</Tabs.Trigger>
		<Tabs.Trigger value="contact">Contact</Tabs.Trigger>
		<Tabs.Indicator />
	</Tabs.List>
	<Tabs.Content value="profile">Profile details</Tabs.Content>
	<Tabs.Content value="dashboard">Dashboard details</Tabs.Content>
	<Tabs.Content value="settings">Settings details</Tabs.Content>
	<Tabs.Content value="contact">Contact details</Tabs.Content>
</Tabs>

Orientation

By default, tabs are horizontally oriented. The orientation prop can be set to vertical to change this. This affects keyboard navigation. You are responsible for styling your tabs accordingly.

ProfileDashboardSettingsContact

Profile details

Copytsx
<Tabs orientation="vertical">
	<Tabs.List>
		<Tabs.Trigger value="profile">Profile</Tabs.Trigger>
		<Tabs.Trigger value="dashboard">Dashboard</Tabs.Trigger>
		<Tabs.Trigger value="settings">Settings</Tabs.Trigger>
		<Tabs.Trigger value="contact">Contact</Tabs.Trigger>
		<Tabs.Indicator />
	</Tabs.List>
	<Tabs.Content value="profile">Profile details</Tabs.Content>
	<Tabs.Content value="dashboard">Dashboard details</Tabs.Content>
	<Tabs.Content value="settings">Settings details</Tabs.Content>
	<Tabs.Content value="contact">Contact details</Tabs.Content>
</Tabs>
Copytsx
<Tabs orientation="vertical">
	<Tabs.List>
		<Tabs.Trigger value="profile">Profile</Tabs.Trigger>
		<Tabs.Trigger value="dashboard">Dashboard</Tabs.Trigger>
		<Tabs.Trigger value="settings">Settings</Tabs.Trigger>
		<Tabs.Trigger value="contact">Contact</Tabs.Trigger>
		<Tabs.Indicator />
	</Tabs.List>
	<Tabs.Content value="profile">Profile details</Tabs.Content>
	<Tabs.Content value="dashboard">Dashboard details</Tabs.Content>
	<Tabs.Content value="settings">Settings details</Tabs.Content>
	<Tabs.Content value="contact">Contact details</Tabs.Content>
</Tabs>

Disabled

All tabs can be disabled using the disabled prop.

ProfileDashboardSettingsContact

Profile details

Copytsx
<Tabs disabled>
	<Tabs.List>
		<Tabs.Trigger value="profile">Profile</Tabs.Trigger>
		<Tabs.Trigger value="dashboard">Dashboard</Tabs.Trigger>
		<Tabs.Trigger value="settings">Settings</Tabs.Trigger>
		<Tabs.Trigger value="contact">Contact</Tabs.Trigger>
		<Tabs.Indicator />
	</Tabs.List>
	<Tabs.Content value="profile">Profile details</Tabs.Content>
	<Tabs.Content value="dashboard">Dashboard details</Tabs.Content>
	<Tabs.Content value="settings">Settings details</Tabs.Content>
	<Tabs.Content value="contact">Contact details</Tabs.Content>
</Tabs>
Copytsx
<Tabs disabled>
	<Tabs.List>
		<Tabs.Trigger value="profile">Profile</Tabs.Trigger>
		<Tabs.Trigger value="dashboard">Dashboard</Tabs.Trigger>
		<Tabs.Trigger value="settings">Settings</Tabs.Trigger>
		<Tabs.Trigger value="contact">Contact</Tabs.Trigger>
		<Tabs.Indicator />
	</Tabs.List>
	<Tabs.Content value="profile">Profile details</Tabs.Content>
	<Tabs.Content value="dashboard">Dashboard details</Tabs.Content>
	<Tabs.Content value="settings">Settings details</Tabs.Content>
	<Tabs.Content value="contact">Contact details</Tabs.Content>
</Tabs>

Individual tab can be disabled using the disabled prop on the tab itself.

ProfileDashboardSettingsContact

Profile details

Copytsx
<Tabs>
	<Tabs.List>
		<Tabs.Trigger value="profile">Profile</Tabs.Trigger>
		<Tabs.Trigger value="dashboard">Dashboard</Tabs.Trigger>
		<Tabs.Trigger value="settings" disabled>
			Settings
		</Tabs.Trigger>
		<Tabs.Trigger value="contact">Contact</Tabs.Trigger>
		<Tabs.Indicator />
	</Tabs.List>
	<Tabs.Content value="profile">Profile details</Tabs.Content>
	<Tabs.Content value="dashboard">Dashboard details</Tabs.Content>
	<Tabs.Content value="settings">Settings details</Tabs.Content>
	<Tabs.Content value="contact">Contact details</Tabs.Content>
</Tabs>
Copytsx
<Tabs>
	<Tabs.List>
		<Tabs.Trigger value="profile">Profile</Tabs.Trigger>
		<Tabs.Trigger value="dashboard">Dashboard</Tabs.Trigger>
		<Tabs.Trigger value="settings" disabled>
			Settings
		</Tabs.Trigger>
		<Tabs.Trigger value="contact">Contact</Tabs.Trigger>
		<Tabs.Indicator />
	</Tabs.List>
	<Tabs.Content value="profile">Profile details</Tabs.Content>
	<Tabs.Content value="dashboard">Dashboard details</Tabs.Content>
	<Tabs.Content value="settings">Settings details</Tabs.Content>
	<Tabs.Content value="contact">Contact details</Tabs.Content>
</Tabs>

API Reference

Tabs

Tabs is equivalent to the Root import from @kobalte/core/tabs (and deprecated Tabs.Root).

Prop Description
value string
The controlled value of the tab to activate.
defaultValue string
The value of the tab that should be active when initially rendered. Useful when you do not need to control the state.
onChange (value: string) => void
Event handler called when the value changes.
orientation `'horizontal'
activationMode `'automatic'
disabled boolean
Whether the tabs are disabled.
Data attribute Description
data-orientation='horizontal' Present when the separator has horizontal orientation.
data-orientation='vertical' Present when the separator has vertical orientation.

Tabs.List, Tabs.Trigger, Tabs.Indicator and Tabs.Content share the same data-attributes.

Tabs.Trigger

Prop Description
value string
The unique key that associates the tab with a tab panel.
disabled boolean
Whether the tab should be disabled.
Data attribute Description
data-selected Present when the trigger is selected.
data-disabled Present when the trigger is disabled.
data-highlighted Present when the trigger is highlighted.

Tabs.Content

Prop Description
value string
The unique key that associates the tab panel with a tab.
forceMount boolean
Used to force mounting when more control is needed. Useful when controlling animation with SolidJS animation libraries.
Data attribute Description
data-selected Present when the associated trigger is selected.

Rendered elements

Component Default rendered element
Tabs div
Tabs.List div
Tabs.Trigger button
Tabs.Indicator div
Tabs.Content div

Accessibility

Keyboard Interactions

Key Description
Tab When focus moves onto the tabs, focuses the active trigger.
When a trigger is focused, moves focus to the active content.
ArrowDown Moves focus to the next trigger in vertical orientation and activates its associated content.
ArrowRight Moves focus to the next trigger in horizontal orientation and activates its associated content.
ArrowUp Moves focus to the previous trigger in vertical orientation and activates its associated content.
ArrowLeft Moves focus to the previous trigger in horizontal orientation and activates its associated content.
Home Moves focus to the first trigger and activates its associated content.
End Moves focus to the last trigger and activates its associated content.
Enter In manual mode, when a trigger is focused, moves focus to its associated content.
Space In manual mode, when a trigger is focused, moves focus to its associated content.

Previous←SwitchNextText Field→