A set of layered sections of content—known as tab panels—that are displayed one at a time.
Make changes to your account here. Click save when you're done.
Change your password here. After saving, you'll be logged out.
<Tabs.root
id="tabs-primitive"
class="DemoTabsRoot"
default_value="tab1"
>
<Tabs.list
class="DemoTabsList"
aria-label="Manage your account"
>
<Tabs.trigger
class="DemoTabsTrigger"
value="tab1"
>
Account
</Tabs.trigger>
<Tabs.trigger
class="DemoTabsTrigger"
value="tab2"
>
Password
</Tabs.trigger>
</Tabs.list>
<Tabs.content
class="DemoTabsContent"
value="tab1"
>
<p class="DemoText">
Make changes to your account here. Click save when you're done.
</p>
<fieldset class="DemoFieldset">
<label
class="DemoLabel"
for="name"
>
Name
</label>
<input
class="DemoInput"
id="name"
value="Pedro Duarte"
/>
</fieldset>
<fieldset class="DemoFieldset">
<label
class="DemoLabel"
for="username"
>
Username
</label>
<input
class="DemoInput"
id="username"
value="@peduarte"
/>
</fieldset>
<div style="display: flex; margin-top: 20px; justify-content: flex-end;">
<button class="DemoButton green">Save changes</button>
</div>
</Tabs.content>
<Tabs.content
class="DemoTabsContent"
value="tab2"
>
<p class="DemoText">
Change your password here. After saving, you'll be logged out.
</p>
<fieldset class="DemoFieldset">
<label
class="DemoLabel"
for="currentPassword"
>
Current password
</label>
<input
class="DemoInput"
id="currentPassword"
type="password"
/>
</fieldset>
<fieldset class="DemoFieldset">
<label
class="DemoLabel"
for="newPassword"
>
New password
</label>
<input
class="DemoInput"
id="newPassword"
type="password"
/>
</fieldset>
<fieldset class="DemoFieldset">
<label
class="DemoLabel"
for="confirmPassword"
>
Confirm password
</label>
<input
class="DemoInput"
id="confirmPassword"
type="password"
/>
</fieldset>
<div style="display: flex; margin-top: 20px; justify-content: flex-end;">
<button class="DemoButton green">Change password</button>
</div>
</Tabs.content>
</Tabs.root>
/* reset */
button,
fieldset,
input {
all: unset;
}
.DemoTabsRoot {
display: flex;
flex-direction: column;
width: 300px;
box-shadow: 0 2px 10px var(--black-a4);
}
.DemoTabsList {
flex-shrink: 0;
display: flex;
border-bottom: 1px solid var(--mauve-6);
}
.DemoTabsTrigger {
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;
}
.DemoTabsTrigger:first-child {
border-top-left-radius: 6px;
}
.DemoTabsTrigger:last-child {
border-top-right-radius: 6px;
}
.DemoTabsTrigger:hover {
color: var(--violet-11);
}
.DemoTabsTrigger[data-state="active"] {
color: var(--violet-11);
box-shadow:
inset 0 -1px 0 0 currentColor,
0 1px 0 0 currentColor;
}
.DemoTabsTrigger:focus {
position: relative;
box-shadow: 0 0 0 2px black;
}
.DemoTabsContent {
flex-grow: 1;
padding: 20px;
background-color: white;
border-bottom-left-radius: 6px;
border-bottom-right-radius: 6px;
outline: none;
}
.DemoTabsContent:focus {
box-shadow: 0 0 0 2px black;
}
.DemoText {
margin-top: 0;
margin-bottom: 20px;
color: var(--mauve-11);
font-size: 15px;
line-height: 1.5;
}
.DemoFieldset {
margin-bottom: 15px;
width: 100%;
display: flex;
flex-direction: column;
justify-content: flex-start;
}
.DemoLabel {
font-size: 13px;
line-height: 1;
margin-bottom: 10px;
color: var(--violet-12);
display: block;
user-select: none;
}
.DemoInput {
flex: 1 0 auto;
border-radius: 4px;
padding: 0 10px;
font-size: 15px;
line-height: 1;
color: var(--violet-11);
box-shadow: 0 0 0 1px var(--violet-7);
height: 35px;
}
.DemoInput:focus {
box-shadow: 0 0 0 2px var(--violet-8);
}
.DemoButton {
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;
}
.DemoButton.green {
background-color: var(--green-4);
color: var(--green-11);
}
.DemoButton.green:hover {
background-color: var(--green-5);
}
.DemoButton.green:focus {
box-shadow: 0 0 0 2px var(--green-7);
}
•
Can be controlled or uncontrolled.
•
Supports horizontal/vertical orientation.
•
Supports automatic/manual activation.
•
Full keyboard navigation.
Anatomy
Import all parts and piece them together.
<Tabs.root>
<Tabs.list>
<Tabs.trigger />
</Tabs.list>
<Tabs.content />
</Tabs.root>
Anatomy
-
Root - Contains all the tabs component parts.
-
List - Contains the triggers that are aligned along the edge of the active content.
-
Trigger - The button that activates its associated content.
-
Content - Contains the content associated with each trigger.
API Reference
Root
Contains all the tabs component parts.
Use value with on_value_change for controlled tab state in LiveView:
<Tabs.root id="account-tabs" value={@tab} on_value_change="tabs_value_change">
…
</Tabs.root>
def handle_event("tabs_value_change", %{"value" => value}, socket) do
{:noreply, assign(socket, :tab, value)}
end
| Prop | Type | Default | Description |
|---|---|---|---|
activation_mode
|
string
|
"automatic"
|
|
default_value
|
string
|
nil
|
|
dir
|
string
|
"ltr"
|
|
id
|
string
|
—
|
|
on_value_change
|
string
|
nil
|
|
orientation
|
string
|
"horizontal"
|
|
value
|
string
|
nil
|
activation_mode
Type
string
Default
"automatic"
default_value
Type
string
Default
nil
dir
Type
string
Default
"ltr"
id
Type
string
Default
—
on_value_change
Type
string
Default
nil
orientation
Type
string
Default
"horizontal"
value
Type
string
Default
nil
Data attributes
| Attribute | Values | Description |
|---|---|---|
[data-orientation]
|
vertical | horizontal
|
The orientation of the component. |
List
Contains the triggers that are aligned along the edge of the active content.
| Prop | Type | Default | Description |
|---|---|---|---|
loop
|
boolean
|
true
|
|
orientation
|
string
|
"horizontal"
|
loop
Type
boolean
Default
true
orientation
Type
string
Default
"horizontal"
Data attributes
| Attribute | Values | Description |
|---|---|---|
[data-orientation]
|
vertical | horizontal
|
The orientation of the component. |
Trigger
The button that activates its associated content.
| Prop | Type | Default | Description |
|---|---|---|---|
disabled
|
boolean
|
false
|
|
id
|
string
|
nil
|
|
orientation
|
string
|
"horizontal"
|
|
value
|
string
|
—
|
disabled
Type
boolean
Default
false
id
Type
string
Default
nil
orientation
Type
string
Default
"horizontal"
value
Type
string
Default
—
Data attributes
| Attribute | Values | Description |
|---|---|---|
[data-state]
|
active | inactive
|
Reflects whether the tab is active. |
[data-disabled]
|
Present when disabled
|
Present when the tab is disabled. |
[data-orientation]
|
vertical | horizontal
|
The orientation of the component. |
Content
Contains the content associated with each trigger.
| Prop | Type | Default | Description |
|---|---|---|---|
force_mount
|
boolean
|
false
|
|
id
|
string
|
nil
|
|
orientation
|
string
|
"horizontal"
|
|
value
|
string
|
—
|
force_mount
Type
boolean
Default
false
id
Type
string
Default
nil
orientation
Type
string
Default
"horizontal"
value
Type
string
Default
—
Data attributes
| Attribute | Values | Description |
|---|---|---|
[data-state]
|
active | inactive
|
Reflects whether the tab is active. |
[data-orientation]
|
vertical | horizontal
|
The orientation of the component. |
Examples
Vertical
You can create vertical tabs by using the orientation prop.
<Tabs.root id="tabs-vertical" default_value="tab1" orientation="vertical" class="DemoTabsRoot DemoTabsRootVertical">
<Tabs.list aria-label="Tabs example" class="DemoTabsList DemoTabsListVertical">
<Tabs.trigger value="tab1" class="DemoTabsTrigger">One</Tabs.trigger>
<Tabs.trigger value="tab2" class="DemoTabsTrigger">Two</Tabs.trigger>
<Tabs.trigger value="tab3" class="DemoTabsTrigger">Three</Tabs.trigger>
</Tabs.list>
<Tabs.content value="tab1" class="DemoTabsContent">Tab one content</Tabs.content>
<Tabs.content value="tab2" class="DemoTabsContent">Tab two content</Tabs.content>
<Tabs.content value="tab3" class="DemoTabsContent">Tab three content</Tabs.content>
</Tabs.root>
.DemoTabsRootVertical {
flex-direction: row;
}
.DemoTabsListVertical {
flex-direction: column;
border-bottom: none;
border-right: 1px solid var(--mauve-6);
}
.DemoTabsRootVertical .DemoTabsTrigger:first-child {
border-top-left-radius: 6px;
border-top-right-radius: 0;
}
.DemoTabsRootVertical .DemoTabsTrigger:last-child {
border-top-right-radius: 0;
}
.DemoTabsRootVertical .DemoTabsContent {
border-bottom-left-radius: 0;
border-top-right-radius: 6px;
}
Accessibility
Adheres to the Tabs WAI-ARIA design pattern.
Keyboard Interactions
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. |