Make changes to your account here. Click save when you're done.
<script setup lang="ts">
import { TabsContent, TabsIndicator, TabsList, TabsRoot, TabsTrigger } from 'akar';
import './styles.css';
</script>
<template>
<div class="akar-demo akar-demo-Tabs">
<TabsRoot
class="TabsRoot"
default-value="tab1"
>
<TabsList
class="TabsList"
aria-label="Manage your account"
>
<TabsIndicator class="TabsIndicator">
<div class="TabsIndicatorContent" />
</TabsIndicator>
<TabsTrigger
class="TabsTrigger"
value="tab1"
>
Account
</TabsTrigger>
<TabsTrigger
class="TabsTrigger"
value="tab2"
>
Password
</TabsTrigger>
</TabsList>
<TabsContent
class="TabsContent"
value="tab1"
>
<p class="Text">
Make changes to your account here. Click save when you're done.
</p>
<fieldset class="Fieldset">
<label
class="Label"
for="name"
> Name </label>
<input
id="name"
class="Input"
value="Pedro Duarte"
>
</fieldset>
<fieldset class="Fieldset">
<label
class="Label"
for="username"
> Username </label>
<input
id="username"
class="Input"
value="@peduarte"
>
</fieldset>
<div :style="{ display: 'flex', marginTop: 20, justifyContent: 'flex-end' }">
<button
class="Button green"
>
Save changes
</button>
</div>
</TabsContent>
<TabsContent
class="TabsContent"
value="tab2"
>
<p class="Text">
Change your password here. After saving, you'll be logged out.
</p>
<fieldset class="Fieldset">
<label
class="Label"
for="currentPassword"
>
Current password
</label>
<input
id="currentPassword"
class="Input"
type="password"
>
</fieldset>
<fieldset class="Fieldset">
<label
class="Label"
for="newPassword"
> New password </label>
<input
id="newPassword"
class="Input"
type="password"
>
</fieldset>
<fieldset class="Fieldset">
<label
class="Label"
for="confirmPassword"
>
Confirm password
</label>
<input
id="confirmPassword"
class="Input"
type="password"
>
</fieldset>
<div :style="{ display: 'flex', marginTop: 20, justifyContent: 'flex-end' }">
<button
class="Button green"
>
Change password
</button>
</div>
</TabsContent>
</TabsRoot>
</div>
</template>@import '../_colors/black-alpha.css';
@import '../_colors/green.css';
@import '../_colors/mauve.css';
@import '../_colors/grass.css';
@scope (.akar-demo-Tabs) {
/* reset */
button,
fieldset,
input {
all: unset;
}
.TabsRoot {
display: flex;
flex-direction: column;
width: 300px;
box-shadow: 0 2px 10px var(--black-a4);
}
.TabsList {
flex-shrink: 0;
display: flex;
border-bottom: 1px solid var(--mauve-6);
position: relative;
}
.TabsIndicator {
position: absolute;
padding-left: 2rem;
left: 0;
height: 2px;
bottom: 0;
width: var(--akar-tabs-indicator-size);
transform: translateX(var(--akar-tabs-indicator-position));
border-radius: 9999px; /* rounded-full equivalent */
transition-property: width, transform;
transition-duration: 300ms;
}
.TabsIndicatorContent {
background-color: var(--grass-8);
height: 100%;
width: 100%;
}
.TabsTrigger {
font-family: inherit;
background-color: white;
padding: 0 20px;
height: 45px;
flex: 1;
display: flex;
align-items: center;
justify-content: center;
font-size: 15px;
line-height: 1;
color: var(--mauve-11);
user-select: none;
}
.TabsTrigger:first-child {
border-top-left-radius: 6px;
}
.TabsTrigger:last-child {
border-top-right-radius: 6px;
}
.TabsTrigger:hover {
color: var(--grass-11);
}
.TabsTrigger[data-state='active'] {
color: var(--grass-11);
box-shadow:
inset 0 -1px 0 0 currentColor,
0 1px 0 0 currentColor;
}
.TabsTrigger:focus {
position: relative;
box-shadow: 0 0 0 2px black;
}
.TabsContent {
flex-grow: 1;
padding: 20px;
background-color: white;
border-bottom-left-radius: 6px;
border-bottom-right-radius: 6px;
outline: none;
}
.TabsContent:focus {
box-shadow: 0 0 0 2px black;
}
.Text {
margin-top: 0;
margin-bottom: 20px;
color: var(--mauve-11);
font-size: 15px;
line-height: 1.5;
}
.Fieldset {
margin-bottom: 15px;
width: 100%;
display: flex;
flex-direction: column;
justify-content: flex-start;
}
.Label {
font-size: 13px;
line-height: 1;
margin-bottom: 10px;
color: var(--grass-12);
display: block;
}
.Input {
flex: 1 0 auto;
border-radius: 4px;
padding: 0 10px;
font-size: 15px;
line-height: 1;
color: var(--grass-11);
box-shadow: 0 0 0 1px var(--grass-7);
height: 35px;
}
.Input:focus {
box-shadow: 0 0 0 2px var(--grass-8);
}
.Button {
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 4px;
padding: 0 15px;
font-size: 15px;
line-height: 1;
font-weight: 500;
height: 35px;
}
.Button.green {
background-color: var(--green-4);
color: var(--green-11);
}
.Button.green:hover {
background-color: var(--green-5);
}
.Button.green:focus {
box-shadow: 0 0 0 2px var(--green-7);
}
}Features
- Can be controlled or uncontrolled.
- Supports horizontal/vertical orientation.
- Supports automatic/manual activation.
- Full keyboard navigation.
Installation
Install the component from your command line.
pnpm add akar
yarn add akar
npm install akar
bun add akar
Anatomy
Import all parts and piece them together.
<script setup>
import { TabsContent, TabsIndicator, TabsList, TabsRoot, TabsTrigger } from 'akar';
</script>
<template>
<TabsRoot>
<TabsList>
<TabsIndicator />
<TabsTrigger />
</TabsList>
<TabsContent />
</TabsRoot>
</template>
API Reference
Root
Contains all the tabs component parts.
| Prop | Default | Type |
|---|---|---|
activationMode | "automatic" | "automatic" | "manual"Whether a tab is activated automatically (on focus) or manually (on click). |
as | "div" | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
defaultValue | TThe value of the tab that should be active when initially rendered. Use when you do not need to control the state of the tabs | |
dir | "ltr" | "rtl"The reading direction of the combobox when applicable. If omitted, inherits globally from | |
modelValue | TThe controlled value of the tab to activate. Can be bind as | |
orientation | "horizontal" | "vertical" | "horizontal"The orientation the tabs are laid out. Mainly so arrow navigation is done accordingly (left & right vs. up & down) |
unmountOnHide | true | booleanWhen |
| Event | Payload |
|---|---|
update:modelValue | payload: TEvent handler called when the value changes |
| Slot (default) | Payload |
|---|---|
modelValue | TCurrent input values |
| Data attribute | Value |
|---|---|
[data-orientation] | "vertical" | "horizontal" |
List
Contains the triggers that are aligned along the edge of the active content.
| Prop | Default | Type |
|---|---|---|
as | "div" | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
loop | true | booleanWhen |
| Data attribute | Value |
|---|---|
[data-orientation] | "vertical" | "horizontal" |
Trigger
The button that activates its associated content.
| Prop | Default | Type |
|---|---|---|
as | "button" | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
disabled | false | booleanWhen |
value* | string | numberA unique value that associates the trigger with a content. |
| Data attribute | Value |
|---|---|
[data-state] | "active" | "inactive" |
[data-disabled] | Present when disabled |
[data-orientation] | "vertical" | "horizontal" |
Indicator
The indicator that highlights the current active tab.
| Prop | Default | Type |
|---|---|---|
as | "div" | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
| Method | Type |
|---|---|
updateIndicatorStyle | () => void |
| CSS variable | Description |
|---|---|
--akar-tabs-indicator-size | The size of the indicator. |
--akar-tabs-indicator-thickness | The thickness of the indicator. |
--akar-tabs-indicator-position | The position of the indicator |
Content
Contains the content associated with each trigger.
| Prop | Default | Type |
|---|---|---|
as | "div" | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
forceMount | booleanUsed to force mounting when more control is needed. Useful when controlling animation with Vue animation libraries. | |
value* | string | numberA unique value that associates the content with a trigger. |
| Data attribute | Value |
|---|---|
[data-state] | "active" | "inactive" |
[data-orientation] | "vertical" | "horizontal" |
Examples
Vertical
You can create vertical tabs by using the orientation prop.
<script setup>
import { TabsContent, TabsList, TabsRoot, TabsTrigger } from 'akar'
</script>
<template>
<TabsRoot
default-value="tab1"
orientation="vertical"
>
<TabsList aria-label="tabs example">
<TabsTrigger value="tab1">
One
</TabsTrigger>
<TabsTrigger value="tab2">
Two
</TabsTrigger>
<TabsTrigger value="tab3">
Three
</TabsTrigger>
</TabsList>
<TabsContent value="tab1">
Tab one content
</TabsContent>
<TabsContent value="tab2">
Tab two content
</TabsContent>
<TabsContent value="tab3">
Tab three content
</TabsContent>
</TabsRoot>
</template>
Accessibility
Adheres to the Tabs WAI-ARIA design pattern.
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 depending on orientation and activates its associated content. |
| ArrowRight | Moves focus to the next trigger depending on orientation and activates its associated content. |
| ArrowUp | Moves focus to the previous trigger depending on orientation and activates its associated content. |
| ArrowLeft | Moves focus to the previous trigger depending on 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. |