Tabs

Use the following default tabs component example to show a list of links that the user can navigate from on your website.

Loading...
<twig:Tabs defaultValue="profile" class="max-w-xl w-full">
    <twig:Tabs:List>
        <twig:Tabs:Trigger value="profile">Profile</twig:Tabs:Trigger>
        <twig:Tabs:Trigger value="dashboard">Dashboard</twig:Tabs:Trigger>
        <twig:Tabs:Trigger value="settings">Settings</twig:Tabs:Trigger>
        <twig:Tabs:Trigger value="contact">Contact</twig:Tabs:Trigger>
        <twig:Tabs:Trigger value="disabled" disabled>Disabled</twig:Tabs:Trigger>
    </twig:Tabs:List>
    <twig:Tabs:Content value="profile">
        <div class="p-4 bg-neutral-secondary text-medium text-body rounded-base w-full">
            <p class="text-sm text-body">This is some placeholder content the <strong class="font-medium text-heading">Profile tab's associated content</strong>. Clicking another tab will toggle the visibility of this one for the next. The tab JavaScript swaps classes to control the content visibility and styling.</p>
        </div>
    </twig:Tabs:Content>
    <twig:Tabs:Content value="dashboard">
        <div class="p-4 bg-neutral-secondary text-medium text-body rounded-base w-full">
            <p class="text-sm text-body">This is some placeholder content the <strong class="font-medium text-heading">Dashboard tab's associated content</strong>. Clicking another tab will toggle the visibility of this one for the next. The tab JavaScript swaps classes to control the content visibility and styling.</p>
        </div>
    </twig:Tabs:Content>
    <twig:Tabs:Content value="settings">
        <div class="p-4 bg-neutral-secondary text-medium text-body rounded-base w-full">
            <p class="text-sm text-body">This is some placeholder content the <strong class="font-medium text-heading">Settings tab's associated content</strong>. Clicking another tab will toggle the visibility of this one for the next. The tab JavaScript swaps classes to control the content visibility and styling.</p>
        </div>
    </twig:Tabs:Content>
    <twig:Tabs:Content value="contact">
        <div class="p-4 bg-neutral-secondary text-medium text-body rounded-base w-full">
            <p class="text-sm text-body">This is some placeholder content the <strong class="font-medium text-heading">Contact tab's associated content</strong>. Clicking another tab will toggle the visibility of this one for the next. The tab JavaScript swaps classes to control the content visibility and styling.</p>
        </div>
    </twig:Tabs:Content>
</twig:Tabs>

Installation

bin/console ux:install tabs --kit flowbite-4

That's it!

Install the following Composer dependencies:

composer require symfony/ux-icons 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:

assets/controllers/tabs_controller.js
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';
        });
    }
}
templates/components/Tabs.html.twig
{# @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-controller="tabs"
    data-tabs-active-tab-value="{{ defaultValue }}"
    data-orientation="{{ orientation }}"
    class="{{ ('gap-4 group/tabs flex data-[orientation=horizontal]:flex-col ' ~ attributes.render('class'))|tailwind_merge }}"
    {{ attributes }}
>
    {% block content %}{% endblock %}
</div>
templates/components/Tabs/Content.html.twig
{# @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_content_id = _tab_id ~ '-description' -%}
{%- set open = defaultValue is same as(value) -%}
<div
    id="{{ _tab_content_id }}"
    data-tabs-target="tab"
    data-tab-id="{{ value }}"
    role="tabpanel"
    aria-labelledby="{{ _tab_id }}"
    data-state="{{ open ? 'active' : 'inactive' }}"
    class="{{ ('data-[state=inactive]:hidden ' ~ attributes.render('class'))|tailwind_merge }}"
    {{ attributes }}
>
    {%- block content %}{% endblock -%}
</div>
templates/components/Tabs/List.html.twig
{# @prop variant 'default'|'line' The visual style variant. Defaults to `default` #}
{# @block content The default block #}
{%- props variant = 'default' -%}
{%- set style = html_cva(
    base: 'group/tabs-list flex flex-wrap -mb-px group-data-[orientation=vertical]/tabs:space-y-4 group-data-[orientation=vertical]/tabs:flex-col',
    variants: {
        variant: {
            default: 'border-b border-default',
            line: 'border-b border-default',
        },
    },
) -%}

<ul
    role="tablist"
    data-variant="{{ variant }}"
    class="{{ style.apply({variant: variant}, attributes.render('class'))|tailwind_merge }}"
    {{ attributes }}>
    {% block content %}{% endblock %}
</ul>
templates/components/Tabs/Trigger.html.twig
{# @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_content_id = _tab_id ~ '-description' -%}
{%- set open = defaultValue is same as(value) -%}
<li class="me-2" role="presentation">
    <button
        id="{{ _tab_id }}"
        data-action="click->tabs#open"
        data-tabs-target="trigger"
        data-tab-id="{{ value }}"
        role="tab"
        aria-controls="{{ _tab_content_id }}"
        aria-selected="{{ open ? 'true' : 'false' }}"
        data-state="{{ open ? 'active' : 'inactive' }}"
        class="{{ ('inline-flex items-center p-4 disabled:text-fg-disabled disabled:cursor-not-allowed group-data-[variant=default]/tabs-list:hover:text-heading group-data-[variant=default]/tabs-list:hover:bg-neutral-secondary-soft group-data-[variant=default]/tabs-list:rounded-t-base data-[state=active]:text-fg-brand group-data-[variant=default]/tabs-list:data-[state=active]:bg-neutral-secondary-soft group-data-[variant=line]/tabs-list:hover:text-fg-brand group-data-[variant=line]/tabs-list:border-b group-data-[variant=line]/tabs-list:border-transparent group-data-[variant=line]/tabs-list:hover:border-fg-brand group-data-[variant=line]/tabs-list:data-[state=active]:border-brand group-data-[variant=pill]/tabs-list:rounded-base group-data-[variant=pill]/tabs-list:py-2.5 group-data-[variant=pill]/tabs-list:hover:bg-neutral-secondary-soft group-data-[variant=pill]/tabs-list:hover:text-heading group-data-[variant=pill]/tabs-list:data-[state=active]:bg-brand group-data-[variant=pill]/tabs-list:data-[state=active]:text-white [&_svg:not([class*=\'size-\'])]:size-4 whitespace-nowrap transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 ' ~ attributes.render('class'))|tailwind_merge }}"
        {{ attributes }}
    >
        {%- block content -%}{%- endblock -%}
    </button>
</li>

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

Tabs with underline

Use this alternative tabs component style with an underline instead of a background when hovering and being active on a certain page.

Loading...
<twig:Tabs defaultValue="dashboard" class="max-w-[800px] w-full">
    <twig:Tabs:List variant="line">
        <twig:Tabs:Trigger value="profile">Profile</twig:Tabs:Trigger>
        <twig:Tabs:Trigger value="dashboard">Dashboard</twig:Tabs:Trigger>
        <twig:Tabs:Trigger value="settings">Settings</twig:Tabs:Trigger>
        <twig:Tabs:Trigger value="contacts">Contacts</twig:Tabs:Trigger>
        <twig:Tabs:Trigger value="disabled" disabled>Disabled</twig:Tabs:Trigger>
    </twig:Tabs:List>
</twig:Tabs>

Tabs with icons

This is an example of the tabs component where you can also use a SVG powered icon to complement the text within the navigational tabs.

Loading...
<twig:Tabs defaultValue="dashboard" class="max-w-[800px] w-full">
    <twig:Tabs:List variant="line">
        <twig:Tabs:Trigger value="profile">
            <twig:ux:icon name="flowbite:user-circle-outline" class="size-4 me-2" aria-hidden="true"/>
            Profile
        </twig:Tabs:Trigger>
        <twig:Tabs:Trigger value="dashboard">
            <twig:ux:icon name="flowbite:grid-outline" class="size-4 me-2" aria-hidden="true"/>
            Dashboard
        </twig:Tabs:Trigger>
        <twig:Tabs:Trigger value="settings">
            <twig:ux:icon name="flowbite:adjustments-vertical-outline" class="size-4 me-2" aria-hidden="true"/>
            Settings
        </twig:Tabs:Trigger>
        <twig:Tabs:Trigger value="contacts">
            <twig:ux:icon name="flowbite:user-headset-outline" class="size-4 me-2" aria-hidden="true"/>
            Contacts
        </twig:Tabs:Trigger>
        <twig:Tabs:Trigger value="disabled" disabled>
            Disabled
        </twig:Tabs:Trigger>
    </twig:Tabs:List>
</twig:Tabs>

Pills tabs

If you want to use pills as a style for the tabs component you can do so by using this example.

Loading...
<twig:Tabs defaultValue="dashboard" class="max-w-[800px] w-full">
    <twig:Tabs:List variant="pill">
        <twig:Tabs:Trigger value="profile">Profile</twig:Tabs:Trigger>
        <twig:Tabs:Trigger value="dashboard">Dashboard</twig:Tabs:Trigger>
        <twig:Tabs:Trigger value="settings">Settings</twig:Tabs:Trigger>
        <twig:Tabs:Trigger value="contacts">Contacts</twig:Tabs:Trigger>
        <twig:Tabs:Trigger value="disabled" disabled>Disabled</twig:Tabs:Trigger>
    </twig:Tabs:List>
</twig:Tabs>

Vertical

Use this example to show a vertically aligned set of tabs on the left side of the page.

Loading...
<twig:Tabs defaultValue="profile" orientation="vertical" class="max-w-xl w-full">
    <twig:Tabs:List variant="pill">
        <twig:Tabs:Trigger value="profile">Profile</twig:Tabs:Trigger>
        <twig:Tabs:Trigger value="dashboard">Dashboard</twig:Tabs:Trigger>
        <twig:Tabs:Trigger value="settings">Settings</twig:Tabs:Trigger>
        <twig:Tabs:Trigger value="contact">Contact</twig:Tabs:Trigger>
        <twig:Tabs:Trigger value="disabled" disabled>Disabled</twig:Tabs:Trigger>
    </twig:Tabs:List>
    <twig:Tabs:Content value="profile">
        <div class="p-6 bg-neutral-secondary text-medium text-body rounded-base w-full h-full">
            <h3 class="text-lg font-semibold text-heading mb-4">Profile Tab</h3>
            <p class="mb-2">This is some placeholder content the Profile tab's associated content, clicking another tab will toggle the visibility of this one for the next.</p>
            <p>The tab JavaScript swaps classes to control the content visibility and styling.</p>
        </div>
    </twig:Tabs:Content>
    <twig:Tabs:Content value="dashboard">
        <div class="p-6 bg-neutral-secondary text-medium text-body rounded-base w-full h-full">
            <h3 class="text-lg font-semibold text-heading mb-4">Dashboard Tab</h3>
        </div>
    </twig:Tabs:Content>
    <twig:Tabs:Content value="settings">
        <div class="p-6 bg-neutral-secondary text-medium text-body rounded-base w-full h-full">
            <h3 class="text-lg font-semibold text-heading mb-4">Settings Tab</h3>
        </div>
    </twig:Tabs:Content>
    <twig:Tabs:Content value="contact">
        <div class="p-6 bg-neutral-secondary text-medium text-body rounded-base w-full h-full">
            <h3 class="text-lg font-semibold text-heading mb-4">Contact Tab</h3>
        </div>
    </twig:Tabs:Content>
</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