Tabs
A set of layered sections of content known as tab panels that are displayed one at a time.
Loading...
<twig:Tabs defaultValue="overview" class="w-[400px]">
<twig:Tabs:List>
<twig:Tabs:Trigger value="overview">Overview</twig:Tabs:Trigger>
<twig:Tabs:Trigger value="analytics">Analytics</twig:Tabs:Trigger>
<twig:Tabs:Trigger value="reports">Reports</twig:Tabs:Trigger>
<twig:Tabs:Trigger value="settings">Settings</twig:Tabs:Trigger>
</twig:Tabs:List>
<twig:Tabs:Content value="overview">
<twig:Card>
<twig:Card:Header>
<twig:Card:Title>Overview</twig:Card:Title>
<twig:Card:Description>
View your key metrics and recent project activity. Track progress
across all your active projects.
</twig:Card:Description>
</twig:Card:Header>
<twig:Card:Content class="text-muted-foreground text-sm">
You have 12 active projects and 3 pending tasks.
</twig:Card:Content>
</twig:Card>
</twig:Tabs:Content>
<twig:Tabs:Content value="analytics">
<twig:Card>
<twig:Card:Header>
<twig:Card:Title>Analytics</twig:Card:Title>
<twig:Card:Description>
Track performance and user engagement metrics. Monitor trends and
identify growth opportunities.
</twig:Card:Description>
</twig:Card:Header>
<twig:Card:Content class="text-muted-foreground text-sm">
Page views are up 25% compared to last month.
</twig:Card:Content>
</twig:Card>
</twig:Tabs:Content>
<twig:Tabs:Content value="reports">
<twig:Card>
<twig:Card:Header>
<twig:Card:Title>Reports</twig:Card:Title>
<twig:Card:Description>
Generate and download your detailed reports. Export data in
multiple formats for analysis.
</twig:Card:Description>
</twig:Card:Header>
<twig:Card:Content class="text-muted-foreground text-sm">
You have 5 reports ready and available to export.
</twig:Card:Content>
</twig:Card>
</twig:Tabs:Content>
<twig:Tabs:Content value="settings">
<twig:Card>
<twig:Card:Header>
<twig:Card:Title>Settings</twig:Card:Title>
<twig:Card:Description>
Manage your account preferences and options. Customize your
experience to fit your needs.
</twig:Card:Description>
</twig:Card:Header>
<twig:Card:Content class="text-muted-foreground text-sm">
Configure notifications, security, and themes.
</twig:Card:Content>
</twig:Card>
</twig:Tabs:Content>
</twig:Tabs>
Installation
bin/console ux:install tabs --kit shadcn
That's it!
Install the following Composer dependencies:
composer require twig/extra-bundle twig/html-extra:^3.12.0 tales-from-a-dev/twig-tailwind-extra:^1.0.0
Copy the following file(s) into your Symfony app:
import { Controller } from '@hotwired/stimulus';
export default class extends Controller {
static targets = ['trigger', 'tab'];
static values = { activeTab: String };
open(e) {
this.activeTabValue = e.currentTarget.dataset.tabId;
}
activeTabValueChanged() {
this.triggerTargets.forEach((trigger) => {
const isActive = trigger.dataset.tabId === this.activeTabValue;
trigger.dataset.state = isActive ? 'active' : 'inactive';
trigger.ariaSelected = isActive;
});
this.tabTargets.forEach((tab) => {
tab.dataset.state = tab.dataset.tabId === this.activeTabValue ? 'active' : 'inactive';
});
}
}
{# @prop defaultValue string define the open Tabs at initial rendering. Defaults to `` #}
{# @prop orientation 'horizontal'|'vertical' define the visual orientation. Defaults to `horizontal` #}
{# @block content The default block #}
{%- props defaultValue = '', orientation = 'horizontal' -%}
<div
data-slot="tabs"
data-controller="tabs"
data-tabs-active-tab-value="{{ defaultValue }}"
data-orientation="{{ orientation }}"
class="{{ ('gap-2 group/tabs flex data-[orientation=horizontal]:flex-col ' ~ attributes.render('class'))|tailwind_merge }}"
{{ attributes }}
>
{% block content %}{% endblock %}
</div>
{# @prop value string Unique suffix identifier for generating Tabs internal IDs #}
{# @block content The default block #}
{%- props value -%}
{%- set _tab_id = 'tab-' ~ value -%}
{%- set _tab_title_id = _tab_id ~ '-title' -%}
{%- set _tab_description_id = _tab_id ~ '-description' -%}
{%- set open = defaultValue is same as(value) -%}
<div
id="{{ _tab_description_id }}"
data-slot="tabs-content"
data-tabs-target="tab"
data-tab-id="{{ value }}"
role="tabpanel"
aria-labelledby="{{ _tab_title_id }}"
data-state="{{ open ? 'active' : 'inactive' }}"
class="{{ ('text-sm flex-1 outline-none data-[state=inactive]:hidden ' ~ attributes.render('class'))|tailwind_merge }}"
{{ attributes }}
>
{%- block content %}{% endblock -%}
</div>
{# @prop variant 'default'|'line' The visual style variant. Defaults to `default` #}
{# @block content The default block #}
{%- props variant = 'default' -%}
{%- set style = html_cva(
base: 'rounded-lg p-[3px] group-data-[orientation=horizontal]/tabs:h-8 data-[variant=line]:rounded-none group/tabs-list inline-flex w-fit items-center justify-center group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col',
variants: {
variant: {
default: 'bg-muted',
line: 'gap-1 bg-transparent',
},
},
) -%}
<nav
data-slot="tabs-list"
role="tablist"
data-variant="{{ variant }}"
class="{{ style.apply({variant: variant}, attributes.render('class'))|tailwind_merge }}"
{{ attributes }}>
{% block content %}{% endblock %}
</nav>
{# @prop value string Unique suffix identifier for generating Tabs internal IDs #}
{# @block content The default block #}
{%- props value -%}
{%- set _tab_id = 'tab-' ~ value -%}
{%- set _tab_title_id = _tab_id ~ '-title' -%}
{%- set _tab_description_id = _tab_id ~ '-description' -%}
{%- set open = defaultValue is same as(value) -%}
<button
id="{{ _tab_id }}"
data-slot="tabs-trigger"
data-action="click->tabs#open"
data-tabs-target="trigger"
data-tab-id="{{ value }}"
role="tab"
aria-controls="{{ _tab_description_id }}"
aria-selected="{{ open ? 'true' : 'false' }}"
data-state="{{ open ? 'active' : 'inactive' }}"
class="{{ ('gap-1.5 rounded-md border border-transparent px-1.5 py-0.5 text-sm font-medium group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none [&_svg:not([class*=\'size-\'])]:size-4 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring text-foreground/60 hover:text-foreground dark:text-muted-foreground dark:hover:text-foreground relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center whitespace-nowrap transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:border-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent data-[state=active]:bg-background dark:data-[state=active]:text-foreground dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 data-[state=active]:text-foreground after:bg-foreground after:absolute after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-[state=active]:after:opacity-100 ' ~ attributes.render('class'))|tailwind_merge }}"
{{ attributes }}
>
{%- block content -%}{%- endblock -%}
</button>
Happy coding!
Usage
<twig:Tabs defaultValue="account" class="w-[400px]">
<twig:Tabs:List>
<twig:Tabs:Trigger value="account">Account</twig:Tabs:Trigger>
<twig:Tabs:Trigger value="password">Password</twig:Tabs:Trigger>
</twig:Tabs:List>
<twig:Tabs:Content value="account">Make changes to your account here.</twig:Tabs:Content>
<twig:Tabs:Content value="password">Change your password here.</twig:Tabs:Content>
</twig:Tabs>
Examples
Line
Loading...
<twig:Tabs defaultValue="overview">
<twig:Tabs:List variant="line">
<twig:Tabs:Trigger value="overview">Overview</twig:Tabs:Trigger>
<twig:Tabs:Trigger value="analytics">Analytics</twig:Tabs:Trigger>
<twig:Tabs:Trigger value="reports">Reports</twig:Tabs:Trigger>
</twig:Tabs:List>
</twig:Tabs>
Vertical
Loading...
<twig:Tabs defaultValue="account" orientation="vertical">
<twig:Tabs:List>
<twig:Tabs:Trigger value="account">Account</twig:Tabs:Trigger>
<twig:Tabs:Trigger value="password">Password</twig:Tabs:Trigger>
<twig:Tabs:Trigger value="notifications">Notifications</twig:Tabs:Trigger>
</twig:Tabs:List>
</twig:Tabs>
Disabled
Loading...
<twig:Tabs defaultValue="home">
<twig:Tabs:List>
<twig:Tabs:Trigger value="home">Home</twig:Tabs:Trigger>
<twig:Tabs:Trigger value="settings" disabled>
Disabled
</twig:Tabs:Trigger>
</twig:Tabs:List>
</twig:Tabs>
Icons
Loading...
<twig:Tabs defaultValue="preview">
<twig:Tabs:List>
<twig:Tabs:Trigger value="preview">
<twig:ux:icon name="lucide:app-window" />
Preview
</twig:Tabs:Trigger>
<twig:Tabs:Trigger value="code">
<twig:ux:icon name="lucide:code" />
Code
</twig:Tabs:Trigger>
</twig:Tabs:List>
</twig:Tabs>
API Reference
Tabs
| Prop | Type | Description |
|---|---|---|
defaultValue |
string |
define the open Tabs at initial rendering. Defaults to `` |
orientation |
'horizontal'|'vertical' |
define the visual orientation. Defaults to horizontal |
| Block | Description |
|---|---|
content |
The default block |
Tabs:Content
| Prop | Type | Description |
|---|---|---|
value |
string |
Unique suffix identifier for generating Tabs internal IDs |
| Block | Description |
|---|---|
content |
The default block |
Tabs:List
| Prop | Type | Description |
|---|---|---|
variant |
'default'|'line' |
The visual style variant. Defaults to default |
| Block | Description |
|---|---|
content |
The default block |
Tabs:Trigger
| Prop | Type | Description |
|---|---|---|
value |
string |
Unique suffix identifier for generating Tabs internal IDs |
| Block | Description |
|---|---|
content |
The default block |