Dropdown

The dropdown component can be used to show a list of menu items when clicking on an element such as a button and hiding it when focusing outside of the triggering element.

Loading...
<twig:Dropdown id="demo" open>
    <twig:Dropdown:Trigger>
        <twig:Button {{ ...dropdown_trigger_attrs }}>
            Dropdown button
            <twig:ux:icon name="flowbite:chevron-down-outline" class="size-4" aria-hidden="true" />
        </twig:Button>
    </twig:Dropdown:Trigger>

    <twig:Dropdown:Content>
        <twig:Dropdown:Group>
            <twig:Dropdown:Item href="#">Dashboard</twig:Dropdown:Item>
            <twig:Dropdown:Item href="#">Settings</twig:Dropdown:Item>
            <twig:Dropdown:Item href="#">Earnings</twig:Dropdown:Item>
            <twig:Dropdown:Item href="#">Sign out</twig:Dropdown:Item>
        </twig:Dropdown:Group>
    </twig:Dropdown:Content>
</twig:Dropdown>

Installation

bin/console ux:install dropdown --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/dropdown_controller.js
import { Controller } from '@hotwired/stimulus';
import { Dropdown } from 'flowbite';

export default class extends Controller {
    dropdown = null;
    static targets = ['trigger', 'content'];
    static values = {
        placement: String,
        triggerType: String,
        open: Boolean,
        delay: Number,
        offsetDistance: Number,
    };

    connect() {
        const options = {
            placement: this.placementValue,
            triggerType: this.triggerTypeValue,
            delay: this.delayValue,
            offsetDistance: this.offsetDistanceValue,
        };
        this.dropdown = new Dropdown(this.contentTarget, this.triggerTarget, options);
        this.dropdown.updateOnShow(() => {
            this.triggerTarget.setAttribute('aria-expanded', 'true');
        });
        this.dropdown.updateOnHide(() => {
            this.triggerTarget.setAttribute('aria-expanded', 'false');
        });

        this._onTriggerKeydown = this._handleTriggerKeydown.bind(this);
        this._onContentKeydown = this._handleContentKeydown.bind(this);
        this.triggerTarget.addEventListener('keydown', this._onTriggerKeydown);
        this.contentTarget.addEventListener('keydown', this._onContentKeydown);

        if (this.openValue) {
            this.dropdown.show();
        }
    }

    disconnect() {
        this.triggerTarget.removeEventListener('keydown', this._onTriggerKeydown);
        this.contentTarget.removeEventListener('keydown', this._onContentKeydown);
        this.dropdown?.destroy();
        this.dropdown = null;
    }

    _getMenuItems() {
        // Get menuitems belonging to this menu level only (not nested submenu contents).
        // Items can be: li > a[role="menuitem"] (Item) or li > div[data-controller] > button[role="menuitem"] (SubTrigger)
        const items = [];
        for (const item of this.contentTarget.querySelectorAll('[role="menuitem"]')) {
            if (item.closest('[data-dropdown-target="content"]') === this.contentTarget) {
                items.push(item);
            }
        }
        return items;
    }

    _focusItem(index) {
        const items = this._getMenuItems();
        if (items.length === 0) return;
        const i = ((index % items.length) + items.length) % items.length;
        items[i].focus();
    }

    _focusFirstItem() {
        this._focusItem(0);
    }

    _focusLastItem() {
        this._focusItem(-1);
    }

    _show(focusFirst = true) {
        this.dropdown.show();
        if (focusFirst) {
            // Small delay to ensure the content is visible before focusing
            requestAnimationFrame(() => this._focusFirstItem());
        }
    }

    _hide() {
        this.dropdown.hide();
        this.triggerTarget.focus();
    }

    _handleTriggerKeydown(event) {
        // If this trigger is a SubTrigger (role="menuitem"), arrow keys should be
        // handled by the parent menu for navigation, not by this submenu's trigger.
        const isSubTrigger = this.triggerTarget.getAttribute('role') === 'menuitem';

        switch (event.key) {
            case 'ArrowDown':
                if (isSubTrigger) return;
                if (!this.dropdown.isVisible()) {
                    event.preventDefault();
                    this._show(true);
                }
                break;
            case 'ArrowUp':
                if (isSubTrigger) return;
                if (!this.dropdown.isVisible()) {
                    event.preventDefault();
                    this.dropdown.show();
                    requestAnimationFrame(() => this._focusLastItem());
                }
                break;
            case 'Enter':
            case ' ':
                if (!this.dropdown.isVisible()) {
                    event.preventDefault();
                    this._show(true);
                }
                break;
            case 'Escape':
                if (this.dropdown.isVisible()) {
                    event.preventDefault();
                    this._hide();
                }
                break;
        }
    }

    _handleContentKeydown(event) {
        // Only handle if the focused element belongs to this menu level
        const items = this._getMenuItems();
        const currentIndex = items.indexOf(document.activeElement);
        if (currentIndex === -1) return;

        // Stop propagation to prevent parent dropdown controllers from intercepting
        event.stopPropagation();

        switch (event.key) {
            case 'ArrowDown':
                event.preventDefault();
                this._focusItem(currentIndex + 1);
                break;
            case 'ArrowUp':
                event.preventDefault();
                this._focusItem(currentIndex - 1);
                break;
            case 'Home':
                event.preventDefault();
                this._focusFirstItem();
                break;
            case 'End':
                event.preventDefault();
                this._focusLastItem();
                break;
            case 'Escape':
                event.preventDefault();
                this._hide();
                break;
            case 'Tab':
                this._hide();
                break;
            case 'ArrowRight':
                // If focused on a SubTrigger, open the submenu
                if (document.activeElement.getAttribute('aria-haspopup') === 'menu') {
                    event.preventDefault();
                    document.activeElement.click();
                    // Focus first item in the submenu after it opens
                    requestAnimationFrame(() => {
                        const subContent = document.activeElement
                            ?.closest('[data-controller="dropdown"]')
                            ?.querySelector('[data-dropdown-target="content"]');
                        if (subContent) {
                            const firstItem = subContent.querySelector('[role="menuitem"]');
                            if (firstItem) firstItem.focus();
                        }
                    });
                }
                break;
            case 'ArrowLeft':
                // If inside a submenu, close it and return focus to the SubTrigger
                this._closeParentSubmenu(event);
                break;
        }
    }

    _closeParentSubmenu(event) {
        // Check if this controller is a submenu (nested inside another dropdown)
        const parentDropdown = this.element.closest('li[role="none"]')?.closest('[data-controller="dropdown"]');
        if (parentDropdown && parentDropdown !== this.element) {
            event.preventDefault();
            this._hide();
            // The _hide() already focuses our trigger, which is the SubTrigger in the parent menu
        }
    }
}
templates/components/Dropdown.html.twig
{# @prop id string Unique identifier for generating internal IDs #}
{# @prop open boolean Whether the dropdown is open on initial render. Defaults to `false` #}
{# @prop placement 'bottom'|'top'|'left'|'right'|'left-start'|'left-end'|'right-start'|'right-end'|'bottom-start'|'bottom-end'|'top-start'|'top-end' The dropdown placement relative to the trigger. Defaults to `bottom` #}
{# @prop triggerType 'click'|'hover' The dropdown trigger type. Defaults to `click` #}
{# @prop int delay The dropdown delay. Defaults to `300` #}
{# @prop int offsetDistance The dropdown content offset. Defaults to `10` #}
{# @block content The dropdown structure (Dropdown:Trigger and Dropdown:Content) #}

{%- props id, open = false, placement = 'bottom', triggerType = 'click', delay = 300, offsetDistance = 10 -%}

{%- set _dropdown_id = 'dropdown-' ~ id -%}
{%- set _dropdown_trigger_id = _dropdown_id ~ '-trigger' -%}

<div {{ attributes.defaults({
        'data-controller': 'dropdown',
        'data-dropdown-placement-value': placement,
        'data-dropdown-trigger-type-value': triggerType,
        'data-dropdown-open-value': open,
        'data-dropdown-delay-value': delay,
        'data-dropdown-offset-distance-value': offsetDistance,
    }) }}>
    {%- block content %}{% endblock -%}
</div>
templates/components/Dropdown/Content.html.twig
{# @block content The dropdown menu content, typically includes `Dropdown:Group`, `Dropdown:Header` #}

<div
    id="{{ _dropdown_id }}"
    data-dropdown-target="content"
    role="menu"
    aria-labelledby="{{ _dropdown_trigger_id }}"
    class="{{ ('z-10 bg-neutral-primary-medium border border-default-medium rounded-base shadow-lg w-fit min-w-44 divide-y divide-default-medium hidden ' ~ attributes.render('class'))|tailwind_merge }}"
    {{ attributes }}
>
    {%- block content %}{% endblock -%}
</div>
templates/components/Dropdown/Group.html.twig
{# @block content The list of `Dropdown:Item` elements #}

<ul
    class="{{ ('p-2 text-sm text-body font-medium ' ~ attributes.render('class'))|tailwind_merge }}"
    role="group"
    {{ attributes }}
>
    {%- block content %}{% endblock -%}
</ul>
templates/components/Dropdown/Header.html.twig
{# @block content The header content (e.g., user name, email) #}

<div
    role="none"
    class="{{ ('m-2 p-2 text-sm text-body bg-neutral-secondary-strong border-0 rounded ' ~ attributes.render('class'))|tailwind_merge }}"
    {{ attributes }}
>
    {%- block content %}{% endblock -%}
</div>
templates/components/Dropdown/Item.html.twig
{# @prop string as The item tag. Defaults to `a` #}
{# @block content The item label #}

{%- props as = 'a' -%}

{% set attributesDefaults = {} %}
{% if as == 'button' %}
    {% set attributesDefaults = {type: 'button'} %}
{% endif %}

<li role="none">
    <{{ as }}
        role="menuitem"
        tabindex="-1"
        class="{{ ('inline-flex items-center w-full p-2 text-sm font-medium text-body hover:bg-neutral-tertiary-medium hover:text-heading rounded ' ~ attributes.render('class'))|tailwind_merge }}"
        {{ attributes.defaults(attributesDefaults) }}
    >
        {%- block content %}{% endblock -%}
    </{{ as }}>
</li>
templates/components/Dropdown/Sub.html.twig
{# @prop id string Unique identifier for generating internal IDs #}
{# @prop placement 'bottom'|'top'|'left'|'right'|'left-start'|'left-end'|'right-start'|'right-end'|'bottom-start'|'bottom-end'|'top-start'|'top-end' The dropdown placement relative to the trigger. Defaults to `right-start` #}
{# @prop triggerType 'click'|'hover' The dropdown trigger type. Defaults to `click` #}
{# @prop int delay The dropdown delay. Defaults to `300` #}
{# @prop int offsetDistance The dropdown content offset. Defaults to `10` #}
{# @block content The dropdown structure (Dropdown:SubTrigger and Dropdown:Content) #}

{% if __context is defined and __context.id is defined %}
    {% set id = null %}
{% endif %}

{%- props id, placement = 'right-start', triggerType = 'click', delay = 300, offsetDistance = 10 -%}

{%- set _dropdown_id = 'dropdown-sub-' ~ id -%}
{%- set _dropdown_trigger_id = _dropdown_id ~ '-trigger' -%}
{%- set _dropdown_content = block('content') -%}

<li role="none">
    <twig:Dropdown :id="id" :placement="placement" :triggerType="triggerType" :delay="delay" :offsetDistance="offsetDistance" {{ ...attributes }}>
        {{- _dropdown_content|raw -}}
    </twig:Dropdown>
</li>
templates/components/Dropdown/SubTrigger.html.twig
{# @block content The trigger label that toggles the sub dropdown when clicked #}

{% set _subtrigger_icons = {
    right: 'flowbite:chevron-right-outline',
    top: 'flowbite:chevron-up-outline',
    bottom: 'flowbite:chevron-down-outline',
} %}
{% set _subtrigger_icon = _subtrigger_icons[placement|split('-')|first] ?? null %}

<button
    data-dropdown-target="trigger"
    id="{{ _dropdown_trigger_id }}"
    type="button"
    role="menuitem"
    tabindex="-1"
    aria-expanded="false"
    aria-haspopup="menu"
    aria-controls="{{ _dropdown_id }}"
    class="{{ ('inline-flex items-center w-full p-2 text-sm font-medium text-body hover:bg-neutral-tertiary-medium hover:text-heading rounded ' ~ attributes.render('class'))|tailwind_merge }}"
    {{ attributes }}
>
    {%- block content %}{% endblock -%}
    {% if _subtrigger_icon %}
        <twig:ux:icon :name="_subtrigger_icon" class="size-4 ms-auto" aria-hidden="true" />
    {% endif %}
</button>
templates/components/Dropdown/Trigger.html.twig
{# @block content The trigger element (e.g., a `Button`) that toggles the dropdown when clicked #}

{%- set dropdown_trigger_attrs = {
    'data-dropdown-target': 'trigger',
    id: _dropdown_trigger_id,
    'aria-expanded': 'false',
    'aria-controls': _dropdown_id,
} -%}

{%- block content %}{% endblock -%}

Happy coding!

Usage

<twig:Dropdown
    id="string"
    open="true | false"
    placement="right | left | top | bottom | right-start | right-end | left-start | left-end | top-start | top-end | bottom-start | bottom-end"
    triggerType="click | hover"
    delay="300"
    offsetDistance="10"
>
    <twig:Dropdown:Trigger>
        <twig:Button {{ ...dropdown_trigger_attrs }}>
            Dropdown
            <twig:ux:icon name="flowbite:chevron-down-outline" class="size-4" aria-hidden="true" />
        </twig:Button>
    </twig:Dropdown:Trigger>

    <twig:Dropdown:Content>
        <twig:Dropdown:Header>
            <span class="block text-sm text-heading font-medium">Bonnie Green</span>
            <span class="block text-sm truncate">name@flowbite.com</span>
        </twig:Dropdown:Header>
        <twig:Dropdown:Group>
            <twig:Dropdown:Item as="a" href="#">Link item</twig:Dropdown:Item>
            <twig:Dropdown:Item as="button">Button item</twig:Dropdown:Item>
            <twig:Dropdown:Sub
                id="string"
                placement="right | left | top | bottom | right-start | right-end | left-start | left-end | top-start | top-end | bottom-start | bottom-end"
                triggerType="click | hover"
                delay="300"
                offsetDistance="10"
            >
                <twig:Dropdown:SubTrigger>
                    Sub menu
                </twig:Dropdown:SubTrigger>

                <twig:Dropdown:Content>
                    <twig:Dropdown:Group>
                        <twig:Dropdown:Item href="#">Sub item</twig:Dropdown:Item>
                        <twig:Dropdown:Item href="#">Sub item</twig:Dropdown:Item>
                    </twig:Dropdown:Group>
                </twig:Dropdown:Content>
            </twig:Dropdown:Sub>
        </twig:Dropdown:Group>
    </twig:Dropdown:Content>
</twig:Dropdown>

Examples

Dropdown hover

Use the ̀triggerType="{hover|click}" prop options to set whether the dropdown should be shown when hovering or clicking on the trigger element (ie. button).

There’s a 300ms default delay when showing or hiding the dropdown due to UI/UX reasons and how it may affect the interaction with other components on the page. Generally, we recommend using the click method.

Loading...
<twig:Dropdown id="hover" triggerType="hover">
    <twig:Dropdown:Trigger>
        <twig:Button {{ ...dropdown_trigger_attrs }}>
            Dropdown button
            <twig:ux:icon name="flowbite:chevron-down-outline" class="size-4" aria-hidden="true" />
        </twig:Button>
    </twig:Dropdown:Trigger>

    <twig:Dropdown:Content>
        <twig:Dropdown:Group>
            <twig:Dropdown:Item href="#">Dashboard</twig:Dropdown:Item>
            <twig:Dropdown:Item href="#">Settings</twig:Dropdown:Item>
            <twig:Dropdown:Item href="#">Earnings</twig:Dropdown:Item>
            <twig:Dropdown:Item href="#">Sign out</twig:Dropdown:Item>
        </twig:Dropdown:Group>
    </twig:Dropdown:Content>
</twig:Dropdown>

Delay duration

You can use the delay={milliseconds} prop options to set the delay on when to show or hide the dropdown menu when using hover. You may want to use this depending on how the users interact with your interface. In this example we add 500 milliseconds instead of the default 300.

Loading...
<twig:Dropdown id="delay" triggerType="hover" delay="500">
    <twig:Dropdown:Trigger>
        <twig:Button {{ ...dropdown_trigger_attrs }}>
            Dropdown button
            <twig:ux:icon name="flowbite:chevron-down-outline" class="size-4" aria-hidden="true" />
        </twig:Button>
    </twig:Dropdown:Trigger>

    <twig:Dropdown:Content>
        <twig:Dropdown:Group>
            <twig:Dropdown:Item href="#">Dashboard</twig:Dropdown:Item>
            <twig:Dropdown:Item href="#">Settings</twig:Dropdown:Item>
            <twig:Dropdown:Item href="#">Earnings</twig:Dropdown:Item>
            <twig:Dropdown:Item href="#">Sign out</twig:Dropdown:Item>
        </twig:Dropdown:Group>
    </twig:Dropdown:Content>
</twig:Dropdown>

Dropdown divider

You can use multiple Dropdown:Group, the Dropdown:Content add a divider between the groups.

Loading...
<twig:Dropdown id="divider">
    <twig:Dropdown:Trigger>
        <twig:Button {{ ...dropdown_trigger_attrs }}>
            Dropdown button
            <twig:ux:icon name="flowbite:chevron-down-outline" class="size-4" aria-hidden="true" />
        </twig:Button>
    </twig:Dropdown:Trigger>

    <twig:Dropdown:Content>
        <twig:Dropdown:Group>
            <twig:Dropdown:Item href="#">Dashboard</twig:Dropdown:Item>
            <twig:Dropdown:Item href="#">Settings</twig:Dropdown:Item>
            <twig:Dropdown:Item href="#">Earnings</twig:Dropdown:Item>
            <twig:Dropdown:Item href="#">Sign out</twig:Dropdown:Item>
        </twig:Dropdown:Group>
        <twig:Dropdown:Group>
            <twig:Dropdown:Item href="#">Separated link</twig:Dropdown:Item>
        </twig:Dropdown:Group>
    </twig:Dropdown:Content>
</twig:Dropdown>

With header

Use this example to show extra information outside of the list of menu items inside the dropdown.

Loading...
<twig:Dropdown id="header">
    <twig:Dropdown:Trigger>
        <twig:Button {{ ...dropdown_trigger_attrs }}>
            Dropdown button
            <twig:ux:icon name="flowbite:chevron-down-outline" class="size-4" aria-hidden="true" />
        </twig:Button>
    </twig:Dropdown:Trigger>

    <twig:Dropdown:Content>
        <twig:Dropdown:Header class="flex items-center space-x-1.5">
            <twig:Avatar>
                <twig:Avatar:Image src="https://flowbite.com/docs/images/people/profile-picture-5.jpg" alt="Bonnie image" />
                <twig:Avatar:Fallback>BG</twig:Avatar:Fallback>
            </twig:Avatar>
            <div class="text-sm">
                <div class="font-medium text-heading">Bonnie Green</div>
                <div class="truncate text-body">name@flowbite.com</div>
            </div>
            <twig:Badge border="bordered">PRO</twig:Badge>
        </twig:Dropdown:Header>
        <twig:Dropdown:Group>
            <twig:Dropdown:Item href="#">
                <twig:ux:icon name="flowbite:user-outline" class="size-4 me-2" aria-hidden="true" />
                Dashboard
            </twig:Dropdown:Item>
            <twig:Dropdown:Item href="#">
                <twig:ux:icon name="flowbite:adjustments-horizontal-outline" class="size-4 me-2" aria-hidden="true" />
                Settings
            </twig:Dropdown:Item>
            <twig:Dropdown:Item href="#">
                <twig:ux:icon name="flowbite:lock-outline" class="size-4 me-2" aria-hidden="true" />
                Privacy
            </twig:Dropdown:Item>
        </twig:Dropdown:Group>
        <twig:Dropdown:Group>
            <twig:Dropdown:Item class="text-fg-danger" href="#">
                <twig:ux:icon name="flowbite:arrow-left-to-bracket-outline" class="size-4 me-2" aria-hidden="true" />
                Sign out
            </twig:Dropdown:Item>
        </twig:Dropdown:Group>
    </twig:Dropdown:Content>
</twig:Dropdown>

Multi-level dropdown

Use this example to enable multi-level dropdown menus by adding stacked elements inside of each other.

Loading...
<twig:Dropdown id="demo" open>
    <twig:Dropdown:Trigger>
        <twig:Button {{ ...dropdown_trigger_attrs }}>
            Dropdown button
            <twig:ux:icon name="flowbite:chevron-down-outline" class="size-4" aria-hidden="true" />
        </twig:Button>
    </twig:Dropdown:Trigger>

    <twig:Dropdown:Content>
        <twig:Dropdown:Group>
            <twig:Dropdown:Item href="#">Dashboard</twig:Dropdown:Item>
            <twig:Dropdown:Sub id="level1">
                <twig:Dropdown:SubTrigger>
                    Settings
                </twig:Dropdown:SubTrigger>

                <twig:Dropdown:Content>
                    <twig:Dropdown:Group>
                        <twig:Dropdown:Item href="#">Submenu item</twig:Dropdown:Item>
                        <twig:Dropdown:Item href="#">Submenu item</twig:Dropdown:Item>
                    </twig:Dropdown:Group>
                </twig:Dropdown:Content>
            </twig:Dropdown:Sub>
            <twig:Dropdown:Item href="#">Earnings</twig:Dropdown:Item>
            <twig:Dropdown:Item href="#">Sign out</twig:Dropdown:Item>
        </twig:Dropdown:Group>
    </twig:Dropdown:Content>
</twig:Dropdown>

With icon

Use the menu icon trigger element on components such as cards as an alternative element to the button.

Loading...
<div class="flex justify-center space-x-4 rtl:space-x-reverse">
    <twig:Dropdown id="icon-vt">
        <twig:Dropdown:Trigger>
            <twig:Button variant="ghost" size="icon" {{ ...dropdown_trigger_attrs }}>
                <twig:ux:icon name="flowbite:dots-vertical-outline" class="size-6" aria-hidden="true"/>
            </twig:Button>
        </twig:Dropdown:Trigger>

        <twig:Dropdown:Content>
            <twig:Dropdown:Group>
                <twig:Dropdown:Item href="#">
                    Dashboard
                </twig:Dropdown:Item>
                <twig:Dropdown:Item href="#">
                    Settings
                </twig:Dropdown:Item>
                <twig:Dropdown:Item href="#">
                    Earnings
                </twig:Dropdown:Item>
                <twig:Dropdown:Item href="#">
                    Sign out
                </twig:Dropdown:Item>
            </twig:Dropdown:Group>
        </twig:Dropdown:Content>
    </twig:Dropdown>

    <twig:Dropdown id="icon-hz">
        <twig:Dropdown:Trigger>
            <twig:Button variant="ghost" size="icon" {{ ...dropdown_trigger_attrs }}>
                <twig:ux:icon name="flowbite:dots-horizontal-outline" class="size-6" aria-hidden="true"/>
            </twig:Button>
        </twig:Dropdown:Trigger>

        <twig:Dropdown:Content>
            <twig:Dropdown:Group>
                <twig:Dropdown:Item href="#">
                    Dashboard
                </twig:Dropdown:Item>
                <twig:Dropdown:Item href="#">
                    Settings
                </twig:Dropdown:Item>
                <twig:Dropdown:Item href="#">
                    Earnings
                </twig:Dropdown:Item>
                <twig:Dropdown:Item href="#">
                    Sign out
                </twig:Dropdown:Item>
            </twig:Dropdown:Group>
        </twig:Dropdown:Content>
    </twig:Dropdown>
</div>

Placement

You can also use the placement={top|right|bottom|left} prop options to choose the placement of the dropdown menu. By default the positioning is set to the bottom side of the button.

Loading...
<div class="flex flex-wrap items-center gap-4">
    <twig:Dropdown id="placement-top" placement="top">
        <twig:Dropdown:Trigger>
            <twig:Button {{ ...dropdown_trigger_attrs }}>
                Dropdown top
                <twig:ux:icon name="flowbite:chevron-up-outline" class="size-6" aria-hidden="true" />
            </twig:Button>
        </twig:Dropdown:Trigger>
        <twig:Dropdown:Content>
            <twig:Dropdown:Group>
                <twig:Dropdown:Item href="#">Dashboard</twig:Dropdown:Item>
                <twig:Dropdown:Item href="#">Settings</twig:Dropdown:Item>
                <twig:Dropdown:Item href="#">Earnings</twig:Dropdown:Item>
            </twig:Dropdown:Group>
        </twig:Dropdown:Content>
    </twig:Dropdown>

    <twig:Dropdown id="placement-right" placement="right">
        <twig:Dropdown:Trigger>
            <twig:Button {{ ...dropdown_trigger_attrs }}>
                Dropdown right
                <twig:ux:icon name="flowbite:chevron-right-outline" class="size-6" aria-hidden="true" />
            </twig:Button>
        </twig:Dropdown:Trigger>
        <twig:Dropdown:Content>
            <twig:Dropdown:Group>
                <twig:Dropdown:Item href="#">Dashboard</twig:Dropdown:Item>
                <twig:Dropdown:Item href="#">Settings</twig:Dropdown:Item>
                <twig:Dropdown:Item href="#">Earnings</twig:Dropdown:Item>
            </twig:Dropdown:Group>
        </twig:Dropdown:Content>
    </twig:Dropdown>

    <twig:Dropdown id="placement-bottom" placement="bottom">
        <twig:Dropdown:Trigger>
            <twig:Button {{ ...dropdown_trigger_attrs }}>
                Dropdown bottom
                <twig:ux:icon name="flowbite:chevron-down-outline" class="size-6" aria-hidden="true" />
            </twig:Button>
        </twig:Dropdown:Trigger>
        <twig:Dropdown:Content>
            <twig:Dropdown:Group>
                <twig:Dropdown:Item href="#">Dashboard</twig:Dropdown:Item>
                <twig:Dropdown:Item href="#">Settings</twig:Dropdown:Item>
                <twig:Dropdown:Item href="#">Earnings</twig:Dropdown:Item>
            </twig:Dropdown:Group>
        </twig:Dropdown:Content>
    </twig:Dropdown>

    <twig:Dropdown id="placement-left" placement="left">
        <twig:Dropdown:Trigger>
            <twig:Button {{ ...dropdown_trigger_attrs }}>
                <twig:ux:icon name="flowbite:chevron-left-outline" class="size-6" aria-hidden="true" />
                Dropdown left
            </twig:Button>
        </twig:Dropdown:Trigger>
        <twig:Dropdown:Content>
            <twig:Dropdown:Group>
                <twig:Dropdown:Item href="#">Dashboard</twig:Dropdown:Item>
                <twig:Dropdown:Item href="#">Settings</twig:Dropdown:Item>
                <twig:Dropdown:Item href="#">Earnings</twig:Dropdown:Item>
            </twig:Dropdown:Group>
        </twig:Dropdown:Content>
    </twig:Dropdown>
</div>

API Reference

Dropdown

Prop Type Description
id string Unique identifier for generating internal IDs
open boolean Whether the dropdown is open on initial render. Defaults to false
placement 'bottom'|'top'|'left'|'right'|'left-start'|'left-end'|'right-start'|'right-end'|'bottom-start'|'bottom-end'|'top-start'|'top-end' The dropdown placement relative to the trigger. Defaults to bottom
triggerType 'click'|'hover' The dropdown trigger type. Defaults to click
int delay The dropdown delay. Defaults to 300
int offsetDistance The dropdown content offset. Defaults to 10
Block Description
content The dropdown structure (Dropdown:Trigger and Dropdown:Content)

Dropdown:Content

Block Description
content The dropdown menu content, typically includes Dropdown:Group, Dropdown:Header

Dropdown:Group

Block Description
content The list of Dropdown:Item elements

Dropdown:Header

Block Description
content The header content (e.g., user name, email)

Dropdown:Item

Prop Type Description
string as The item tag. Defaults to a
Block Description
content The item label

Dropdown:Sub

Prop Type Description
id string Unique identifier for generating internal IDs
placement 'bottom'|'top'|'left'|'right'|'left-start'|'left-end'|'right-start'|'right-end'|'bottom-start'|'bottom-end'|'top-start'|'top-end' The dropdown placement relative to the trigger. Defaults to right-start
triggerType 'click'|'hover' The dropdown trigger type. Defaults to click
int delay The dropdown delay. Defaults to 300
int offsetDistance The dropdown content offset. Defaults to 10
Block Description
content The dropdown structure (Dropdown:SubTrigger and Dropdown:Content)

Dropdown:SubTrigger

Block Description
content The trigger label that toggles the sub dropdown when clicked

Dropdown:Trigger

Block Description
content The trigger element (e.g., a Button) that toggles the dropdown when clicked