---
title: Tabs
description: Switch between related panels with accessible tab navigation.
---
import { StoryEmbed } from '/snippets/ui/StoryEmbed.mdx';
import TabsReference from '/snippets/ui/generated/navigation/tabs.mdx';
`Tabs` connects each tab to a panel using a shared `value`. Keep the values unique within a root.
## Anatomy
Place tabs inside `Tabs.List` and their panels alongside the list, all within the same `Tabs.Root`.
```text
Tabs.Root
├── Tabs.List
│ ├── Tabs.Tab
│ └── Tabs.Tab
├── Tabs.Panel
└── Tabs.Panel
```
| Part | Requirement | Purpose |
| ------- | -------------------- | -------------------------------------------------------------------------- |
| `Root` | Required | Owns the active value and orientation. |
| `List` | Required | Groups the tabs and supplies keyboard navigation and the active indicator. |
| `Tab` | One per panel | Activates the panel with the same `value`. |
| `Panel` | One per content view | Displays its content when its `value` is active. |
Give the list an accessible name and match each selectable tab's `value` to its panel's `value`. Twenty UI includes the active indicator in `List`. Use `Tab.startIcon` and `Tab.badge` for optional supporting content while keeping the tab label in its children.
## Uncontrolled state
Use `defaultValue` to set the initial active tab. The tabs own subsequent selection changes.
```tsx
import { Tabs } from 'twenty-ui/primitives/navigation';
export const RecordTabs = () => (
Overview
Activity
Company and contact details.
Recent calls and emails.
);
```
`Tabs.Tab` accepts `startIcon`, `badge`, and a `size` of `sm` or `md` (default `sm`). The active indicator is included in `Tabs.List`.
## Controlled state
Pass `value` and update it with `onValueChange` to own the same active tab in React state. The callback receives the next value and event details.
```tsx
import { useState } from 'react';
import { Tabs } from 'twenty-ui/primitives/navigation';
export const RecordTabs = () => {
const [tab, setTab] = useState('overview');
return (
Overview
Activity
Company and contact details.
Recent calls and emails.
);
};
```
## Manual activation
Keyboard activation is independent of state ownership. Use `activateOnFocus={false}` with either controlled or uncontrolled tabs.
```tsx
import { useState } from 'react';
import { Tabs } from 'twenty-ui/primitives/navigation';
export const SettingsTabs = () => {
const [tab, setTab] = useState('profile');
return (
Profile
Notifications
Billing
Profile settings.
Notification settings.
);
};
```
With `activateOnFocus={false}`, arrow keys move focus and Enter or Space activates the focused tab. `loopFocus` defaults to `true`. Disabled tabs are skipped. Use `orientation="vertical"` on `Root` for vertical navigation.
## Panel lifetime
Use `keepMounted` on a panel when its contents must remain mounted while inactive.
## Props
The reference is generated from the public component types. Native attributes, including accessible names and event handlers, are also accepted on parts that render elements.