Tabs
Flexible navigation tool with various modes and features.
You can explore the tabs component in the following curated examples.
Select Tabs
Render a Tabs component within a Select.
Tabs with links
Render a Tabs component with links as the Tab Trigger.
Anatomy
To set up the tabs correctly, you'll need to understand its anatomy and how we name its parts.
Each part includes a
data-part
attribute to help identify them in the DOM.
Examples
Learn how to use the Tabs
component in your project. Let's take a look at the most basic example:
import { Tabs } from '@ark-ui/react/tabs'
export const Basic = () => (
<Tabs.Root>
<Tabs.List>
<Tabs.Trigger value="react">React</Tabs.Trigger>
<Tabs.Trigger value="vue">Vue</Tabs.Trigger>
<Tabs.Trigger value="solid">Solid</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="react">React Content</Tabs.Content>
<Tabs.Content value="vue">Vue Content</Tabs.Content>
<Tabs.Content value="solid">Solid Content</Tabs.Content>
</Tabs.Root>
)
import { Tabs } from '@ark-ui/solid/tabs'
export const Basic = () => (
<Tabs.Root>
<Tabs.List>
<Tabs.Trigger value="react">React</Tabs.Trigger>
<Tabs.Trigger value="vue">Vue</Tabs.Trigger>
<Tabs.Trigger value="solid">Solid</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="react">React Content</Tabs.Content>
<Tabs.Content value="vue">Vue Content</Tabs.Content>
<Tabs.Content value="solid">Solid Content</Tabs.Content>
</Tabs.Root>
)
<script setup lang="ts">
import { Tabs } from '@ark-ui/vue/tabs'
</script>
<template>
<Tabs.Root>
<Tabs.List>
<Tabs.Trigger value="react">React</Tabs.Trigger>
<Tabs.Trigger value="vue">Vue</Tabs.Trigger>
<Tabs.Trigger value="solid">Solid</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="react">React Content</Tabs.Content>
<Tabs.Content value="vue">Vue Content</Tabs.Content>
<Tabs.Content value="solid">Solid Content</Tabs.Content>
</Tabs.Root>
</template>
Initial Tab
To set a default tab on initial render, use the defaultValue
prop:
Tab Indicator
To provide a visual cue for the selected tab, use the Tabs.Indicator
component:
import { Tabs } from '@ark-ui/react/tabs'
export const Indicator = () => (
<Tabs.Root>
<Tabs.List>
<Tabs.Trigger value="react">React</Tabs.Trigger>
<Tabs.Trigger value="vue">Vue</Tabs.Trigger>
<Tabs.Trigger value="solid">Solid</Tabs.Trigger>
<Tabs.Indicator />
</Tabs.List>
<Tabs.Content value="react">React Content</Tabs.Content>
<Tabs.Content value="vue">Vue Content</Tabs.Content>
<Tabs.Content value="solid">Solid Content</Tabs.Content>
</Tabs.Root>
)
import { Tabs } from '@ark-ui/solid/tabs'
export const Indicator = () => (
<Tabs.Root>
<Tabs.List>
<Tabs.Trigger value="react">React</Tabs.Trigger>
<Tabs.Trigger value="vue">Vue</Tabs.Trigger>
<Tabs.Trigger value="solid">Solid</Tabs.Trigger>
<Tabs.Indicator />
</Tabs.List>
<Tabs.Content value="react">React Content</Tabs.Content>
<Tabs.Content value="vue">Vue Content</Tabs.Content>
<Tabs.Content value="solid">Solid Content</Tabs.Content>
</Tabs.Root>
)
<script setup lang="ts">
import { Tabs } from '@ark-ui/vue/tabs'
</script>
<template>
<Tabs.Root>
<Tabs.List>
<Tabs.Trigger value="react">React</Tabs.Trigger>
<Tabs.Trigger value="vue">Vue</Tabs.Trigger>
<Tabs.Trigger value="solid">Solid</Tabs.Trigger>
<Tabs.Indicator />
</Tabs.List>
<Tabs.Content value="react">React Content</Tabs.Content>
<Tabs.Content value="vue">Vue Content</Tabs.Content>
<Tabs.Content value="solid">Solid Content</Tabs.Content>
</Tabs.Root>
</template>
Lazy Mounting
Lazy mounting is a feature that allows the content of a tab to be rendered only when the tab is
first activated. This is useful for performance optimization, especially when tab content is large
or complex. To enable lazy mounting, use the lazyMount
prop on the Tabs.Content
component.
In addition, the unmountOnExit
prop can be used in conjunction with lazyMount
to unmount the tab
content when the tab is deactivated, freeing up resources. The next time the tab is activated, its
content will be re-rendered.
import { Tabs } from '@ark-ui/react/tabs'
export const LazyMount = () => (
<Tabs.Root lazyMount unmountOnExit>
<Tabs.List>
<Tabs.Trigger value="react">React</Tabs.Trigger>
<Tabs.Trigger value="vue">Vue</Tabs.Trigger>
<Tabs.Trigger value="solid">Solid</Tabs.Trigger>
<Tabs.Indicator />
</Tabs.List>
<Tabs.Content value="react">React Content</Tabs.Content>
<Tabs.Content value="vue">Vue Content</Tabs.Content>
<Tabs.Content value="solid">Solid Content</Tabs.Content>
</Tabs.Root>
)
import { Tabs } from '@ark-ui/solid/tabs'
export const LazyMount = () => (
<Tabs.Root lazyMount unmountOnExit>
<Tabs.List>
<Tabs.Trigger value="react">React</Tabs.Trigger>
<Tabs.Trigger value="vue">Vue</Tabs.Trigger>
<Tabs.Trigger value="solid">Solid</Tabs.Trigger>
<Tabs.Indicator />
</Tabs.List>
<Tabs.Content value="react">React Content</Tabs.Content>
<Tabs.Content value="vue">Vue Content</Tabs.Content>
<Tabs.Content value="solid">Solid Content</Tabs.Content>
</Tabs.Root>
)
<script setup lang="ts">
import { Tabs } from '@ark-ui/vue/tabs'
</script>
<template>
<Tabs.Root lazyMount unmountOnExit>
<Tabs.List>
<Tabs.Trigger value="react">React</Tabs.Trigger>
<Tabs.Trigger value="vue">Vue</Tabs.Trigger>
<Tabs.Trigger value="solid">Solid</Tabs.Trigger>
<Tabs.Indicator />
</Tabs.List>
<Tabs.Content value="react">React Content</Tabs.Content>
<Tabs.Content value="vue">Vue Content</Tabs.Content>
<Tabs.Content value="solid">Solid Content</Tabs.Content>
</Tabs.Root>
</template>
Disabled Tab
To disable a tab, simply pass the disabled
prop to the Tabs.Trigger
component:
import { Tabs } from '@ark-ui/react/tabs'
export const DisabledTab = () => (
<Tabs.Root defaultValue="react">
<Tabs.List>
<Tabs.Trigger value="react">React</Tabs.Trigger>
<Tabs.Trigger value="vue" disabled>
Vue
</Tabs.Trigger>
<Tabs.Trigger value="solid">Solid</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="react">React Content</Tabs.Content>
<Tabs.Content value="vue">Vue Content</Tabs.Content>
<Tabs.Content value="solid">Solid Content</Tabs.Content>
</Tabs.Root>
)
import { Tabs } from '@ark-ui/solid/tabs'
export const DisabledTab = () => (
<Tabs.Root value="react">
<Tabs.List>
<Tabs.Trigger value="react">React</Tabs.Trigger>
<Tabs.Trigger value="vue" disabled>
Vue
</Tabs.Trigger>
<Tabs.Trigger value="solid">Solid</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="react">React Content</Tabs.Content>
<Tabs.Content value="vue">Vue Content</Tabs.Content>
<Tabs.Content value="solid">Solid Content</Tabs.Content>
</Tabs.Root>
)
<script setup lang="ts">
import { Tabs } from '@ark-ui/vue/tabs'
</script>
<template>
<Tabs.Root defaultValue="react">
<Tabs.List>
<Tabs.Trigger value="react">React</Tabs.Trigger>
<Tabs.Trigger value="vue" disabled>Vue</Tabs.Trigger>
<Tabs.Trigger value="solid">Solid</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="react">React Content</Tabs.Content>
<Tabs.Content value="vue">Vue Content</Tabs.Content>
<Tabs.Content value="solid">Solid Content</Tabs.Content>
</Tabs.Root>
</template>
Controlled Tabs
To create a controlled Tabs component, manage the current selected tab using the value
prop and
update it when the onValueChange
event handler is called:
import { Tabs } from '@ark-ui/react/tabs'
import { useState } from 'react'
export const Controlled = () => {
const [value, setValue] = useState<string | null>('react')
return (
<Tabs.Root value={value} onValueChange={(e) => setValue(e.value)}>
<Tabs.List>
<Tabs.Trigger value="react">React</Tabs.Trigger>
<Tabs.Trigger value="vue">Vue</Tabs.Trigger>
<Tabs.Trigger value="solid">Solid</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="react">React Content</Tabs.Content>
<Tabs.Content value="vue">Vue Content</Tabs.Content>
<Tabs.Content value="solid">Solid Content</Tabs.Content>
</Tabs.Root>
)
}
import { Tabs } from '@ark-ui/solid/tabs'
import { createSignal } from 'solid-js'
export const Controlled = () => {
const [value, setValue] = createSignal<string | null>('react')
return (
<Tabs.Root value={value()} onValueChange={(e) => setValue(e.value)}>
<Tabs.List>
<Tabs.Trigger value="react">React</Tabs.Trigger>
<Tabs.Trigger value="vue">Vue</Tabs.Trigger>
<Tabs.Trigger value="solid">Solid</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="react">React Content</Tabs.Content>
<Tabs.Content value="vue">Vue Content</Tabs.Content>
<Tabs.Content value="solid">Solid Content</Tabs.Content>
</Tabs.Root>
)
}
<script setup lang="ts">
import { Tabs } from '@ark-ui/vue/tabs'
import { ref } from 'vue'
const value = ref('react')
</script>
<template>
<Tabs.Root v-model="value">
<Tabs.List>
<Tabs.Trigger value="react">React</Tabs.Trigger>
<Tabs.Trigger value="vue">Vue</Tabs.Trigger>
<Tabs.Trigger value="solid">Solid</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="react">React Content</Tabs.Content>
<Tabs.Content value="vue">Vue Content</Tabs.Content>
<Tabs.Content value="solid">Solid Content</Tabs.Content>
</Tabs.Root>
</template>
Router Controlled Tabs
When using frameworks like Next.js, Remix, or React Router, controlling the active tabs based on the URL can be useful.
To achieve this, you need to do two things:
- Set the
value
prop to the current URL path. - Listen to the
onValueChange
event and update the URL path.
Here's an example using Remix Router
import { Tabs } from '@ark-ui/react/tabs'
import { useLocation, useNavigate, Link } from '@remix-run/react'
export default function App() {
const { pathname } = useLocation()
const navigate = useNavigate()
const lastPathFragment = pathname.substring(pathname.lastIndexOf('/') + 1)
const activeTab = lastPathFragment.length > 0 ? lastPathFragment : 'homepage'
return (
<Tabs.Root
value={activeTab}
onValueChange={({ value }) => {
navigate(`/${value === 'home' ? '' : value}`)
}}
>
<Tabs.List>
<Tabs.Trigger asChild value="home">
<Link to="">Home</Link>
</Tabs.Trigger>
<Tabs.Trigger asChild value="page-1">
<Link to="page-1">Page 1</Link>
</Tabs.Trigger>
<Tabs.Trigger asChild value="page-2">
<Link to="page-2">Page 2</Link>
</Tabs.Trigger>
</Tabs.List>
</Tabs.Root>
)
}
Vertical Tabs
The default orientation of the tabs is horizontal
. To change the orientation, set the
orientation
prop to vertical
.
import { Tabs } from '@ark-ui/react/tabs'
export const Vertical = () => (
<Tabs.Root orientation="vertical" defaultValue="react">
<Tabs.List>
<Tabs.Trigger value="react">React</Tabs.Trigger>
<Tabs.Trigger value="vue">Vue</Tabs.Trigger>
<Tabs.Trigger value="solid">Solid</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="react">React Content</Tabs.Content>
<Tabs.Content value="vue">Vue Content</Tabs.Content>
<Tabs.Content value="solid">Solid Content</Tabs.Content>
</Tabs.Root>
)
import { Tabs } from '@ark-ui/solid/tabs'
export const Vertical = () => (
<Tabs.Root orientation="vertical" value="react">
<Tabs.List>
<Tabs.Trigger value="react">React</Tabs.Trigger>
<Tabs.Trigger value="vue">Vue</Tabs.Trigger>
<Tabs.Trigger value="solid">Solid</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="react">React Content</Tabs.Content>
<Tabs.Content value="vue">Vue Content</Tabs.Content>
<Tabs.Content value="solid">Solid Content</Tabs.Content>
</Tabs.Root>
)
<script setup lang="ts">
import { Tabs } from '@ark-ui/vue/tabs'
import { ref } from 'vue'
const value = ref('react')
</script>
<template>
<Tabs.Root v-model="value" orientation="vertical">
<Tabs.List>
<Tabs.Trigger value="react">React</Tabs.Trigger>
<Tabs.Trigger value="vue">Vue</Tabs.Trigger>
<Tabs.Trigger value="solid">Solid</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="react">React Content</Tabs.Content>
<Tabs.Content value="vue">Vue Content</Tabs.Content>
<Tabs.Content value="solid">Solid Content</Tabs.Content>
</Tabs.Root>
</template>
Manual Activation
By default, the tab can be selected when it receives focus from either the keyboard or pointer interaction. This is called automatic tab activation.
In contrast, manual tab activation means the tab is selected with the
Enter key or by clicking on the tab.
import { Tabs } from '@ark-ui/react/tabs'
export const Manual = () => (
<Tabs.Root activationMode="manual" defaultValue="react">
<Tabs.List>
<Tabs.Trigger value="react">React</Tabs.Trigger>
<Tabs.Trigger value="vue">Vue</Tabs.Trigger>
<Tabs.Trigger value="solid">Solid</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="react">React Content</Tabs.Content>
<Tabs.Content value="vue">Vue Content</Tabs.Content>
<Tabs.Content value="solid">Solid Content</Tabs.Content>
</Tabs.Root>
)
import { Tabs } from '@ark-ui/solid/tabs'
export const Manual = () => (
<Tabs.Root activationMode="manual" value="react">
<Tabs.List>
<Tabs.Trigger value="react">React</Tabs.Trigger>
<Tabs.Trigger value="vue">Vue</Tabs.Trigger>
<Tabs.Trigger value="solid">Solid</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="react">React Content</Tabs.Content>
<Tabs.Content value="vue">Vue Content</Tabs.Content>
<Tabs.Content value="solid">Solid Content</Tabs.Content>
</Tabs.Root>
)
<script setup lang="ts">
import { Tabs } from '@ark-ui/vue/tabs'
import { ref } from 'vue'
const value = ref('react')
</script>
<template>
<Tabs.Root v-model="value" activationMode="manual">
<Tabs.List>
<Tabs.Trigger value="react">React</Tabs.Trigger>
<Tabs.Trigger value="vue">Vue</Tabs.Trigger>
<Tabs.Trigger value="solid">Solid</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="react">React Content</Tabs.Content>
<Tabs.Content value="vue">Vue Content</Tabs.Content>
<Tabs.Content value="solid">Solid Content</Tabs.Content>
</Tabs.Root>
</template>
Using the Root Provider
The RootProvider
component provides a context for the tabs. It accepts the value of the useTabs
hook.
You can leverage it to access the component state and methods from outside the tabs.
import { Tabs, useTabs } from '@ark-ui/react/tabs'
export const RootProvider = () => {
const tabs = useTabs()
return (
<>
<button onClick={() => tabs.focus()}>Focus</button>
<Tabs.RootProvider value={tabs}>
<Tabs.List>
<Tabs.Trigger value="react">React</Tabs.Trigger>
<Tabs.Trigger value="vue">Vue</Tabs.Trigger>
<Tabs.Trigger value="solid">Solid</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="react">React Content</Tabs.Content>
<Tabs.Content value="vue">Vue Content</Tabs.Content>
<Tabs.Content value="solid">Solid Content</Tabs.Content>
</Tabs.RootProvider>
</>
)
}
import { Tabs, useTabs } from '@ark-ui/solid/tabs'
export const RootProvider = () => {
const tabs = useTabs()
return (
<>
<button onClick={() => tabs().focus()}>Focus</button>
<Tabs.RootProvider value={tabs}>
<Tabs.List>
<Tabs.Trigger value="react">React</Tabs.Trigger>
<Tabs.Trigger value="vue">Vue</Tabs.Trigger>
<Tabs.Trigger value="solid">Solid</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="react">React Content</Tabs.Content>
<Tabs.Content value="vue">Vue Content</Tabs.Content>
<Tabs.Content value="solid">Solid Content</Tabs.Content>
</Tabs.RootProvider>
</>
)
}
<script setup lang="ts">
import { Tabs, useTabs } from '@ark-ui/vue/tabs'
const tabs = useTabs()
</script>
<template>
<button @click="tabs.focus()">Focus</button>
<Tabs.RootProvider :value="tabs">
<Tabs.List>
<Tabs.Trigger value="react">React</Tabs.Trigger>
<Tabs.Trigger value="vue">Vue</Tabs.Trigger>
<Tabs.Trigger value="solid">Solid</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="react">React Content</Tabs.Content>
<Tabs.Content value="vue">Vue Content</Tabs.Content>
<Tabs.Content value="solid">Solid Content</Tabs.Content>
</Tabs.RootProvider>
</template>
If you're using the
RootProvider
component, you don't need to use theRoot
component.
API Reference
Root
Prop | Default | Type |
---|---|---|
activationMode | 'automatic' | 'manual' | 'automatic' The activation mode of the tabs. Can be `manual` or `automatic` - `manual`: Tabs are activated when clicked or press `enter` key. - `automatic`: Tabs are activated when receiving focus |
asChild | (props: ParentProps<'div'>) => Element Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. | |
composite | boolean Whether the tab is composite | |
defaultValue | string The initial value of the tabs when it is first rendered. Use when you do not need to control the state of the tabs. | |
deselectable | boolean Whether the active tab can be deselected when clicking on it. | |
ids | Partial<{
root: string
trigger: string
list: string
content: string
indicator: string
}> The ids of the elements in the tabs. Useful for composition. | |
lazyMount | false | boolean Whether to enable lazy mounting |
loopFocus | true | boolean Whether the keyboard navigation will loop from last tab to first, and vice versa. |
onFocusChange | (details: FocusChangeDetails) => void Callback to be called when the focused tab changes | |
onValueChange | (details: ValueChangeDetails) => void Callback to be called when the selected/active tab changes | |
orientation | 'horizontal' | 'horizontal' | 'vertical' The orientation of the tabs. Can be `horizontal` or `vertical` - `horizontal`: only left and right arrow key navigation will work. - `vertical`: only up and down arrow key navigation will work. |
translations | IntlTranslations Specifies the localized strings that identifies the accessibility elements and their states | |
unmountOnExit | false | boolean Whether to unmount on exit. |
value | string The selected tab id |
Data Attribute | Value |
---|---|
[data-scope] | tabs |
[data-part] | root |
[data-orientation] | The orientation of the tabs |
[data-focus] | Present when focused |
TabContent
Prop | Default | Type |
---|---|---|
value | string The value of the tab | |
asChild | (props: ParentProps<'div'>) => Element Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
TabIndicator
Prop | Default | Type |
---|---|---|
asChild | (props: ParentProps<'div'>) => Element Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
TabList
Prop | Default | Type |
---|---|---|
asChild | (props: ParentProps<'div'>) => Element Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
TabTrigger
Prop | Default | Type |
---|---|---|
value | string The value of the tab | |
asChild | (props: ParentProps<'button'>) => Element Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. | |
disabled | boolean Whether the tab is disabled |
RootProvider
Prop | Default | Type |
---|---|---|
value | UseTabsReturn | |
asChild | (props: ParentProps<'div'>) => Element Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. | |
lazyMount | false | boolean Whether to enable lazy mounting |
unmountOnExit | false | boolean Whether to unmount on exit. |
Accessibility
Complies with the Tabs WAI-ARIA design pattern.
Keyboard Support
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. |
EnterSpace | In manual mode, when a trigger is focused, moves focus to its associated content. |