Skip to content
Elements

Dropdowns

Anchored menus for actions and selections.


Setup

Dropdown styles are included in Manifest CSS or a standalone stylesheet, both referencing theme variables.

Dropdown functionality is included in manifest.js with all core plugins, or it can be selectively loaded.

<!-- Manifest CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/mnfst@latest/lib/manifest.min.css" />

<!-- Manifest JS -->
<script src="https://cdn.jsdelivr.net/npm/mnfst@latest/lib/manifest.min.js"></script>

For OS dropdowns, see selects.


Default

Dropdowns use the <menu> element as a popover. The <button> that opens the dropdown requires the x-dropdown attribute, matching the menu ID.


<button x-dropdown="basic-menu">Open Menu</button>

<menu popover id="basic-menu">
    <li>Item 1</li>
    <li>Item 2</li>
    <li>Item 3</li>
</menu>

Hover

Add the hover modifier to x-dropdown for mouseover dropdowns:


<button x-dropdown.hover="hover-menu">Hover Me</button>

<menu popover id="hover-menu">
    <li>Item 1</li>
    <li>Item 2</li>
    <li>Item 3</li>
</menu>

Hover dropdowns include a small delay to prevent accidental auto-close if the mouse briefly leaves the trigger or menu area.


Context Menu

Add the context modifier to x-dropdown for right-click dropdowns. The menu appears at the cursor position, replacing the browser's native context menu on the trigger element.


<div x-dropdown.context="context-menu">Right-click this area</div>

<menu popover id="context-menu">
    <li>Cut</li>
    <li>Copy</li>
    <li>Paste</li>
</menu>

The trigger element does not need to be a <button> since the menu is opened programmatically on right-click rather than via the popovertarget attribute's click-to-toggle behavior. The menu still uses the Popover API for rendering and stacking.


Auto-Close

Dropdowns stay open when an item is clicked, so menus built around checkboxes, switches and inputs keep working. Add the close attribute to an individual item to dismiss the menu when that item is activated:


<button x-dropdown="autoclose-menu">Actions</button>

<menu popover id="autoclose-menu">
    <li close>Duplicate</li>
    <a href="#" close>Settings</a>
    <li>Stays open</li>
</menu>

Add the close modifier to x-dropdown when every item should dismiss the menu, as in a select-like list of choices:


<button x-dropdown.close="autoclose-all">Select One</button>

<menu popover id="autoclose-all">
    <li>Rename</li>
    <li>Archive</li>
    <li>Delete</li>
    <hr>
    <label><input type="checkbox" /><span>Stays open</span></label>
    <li keep-open>Also stays open</li>
</menu>

Embedded controls are excluded from the close modifier, so a row that is — or contains — an input, textarea, select, switch, combobox, nested button or submenu trigger leaves the menu open. A <button> or <a> used as the row itself counts as an item and closes. Add keep-open to exclude any other row.

Selecting an item inside a nested menu closes its parent menus as well, and keyboard activation returns focus to the trigger.


Nesting

Create multi-level navigation menus with nested dropdowns.


<button x-dropdown="nested-menu">Main Menu</button>

<!-- Main Menu -->
<menu popover id="nested-menu">
    <li>Item 1</li>
    <li>Item 2</li>
    <button x-dropdown="submenu-1"><span>Submenu</span><span x-icon="lucide:chevron-right" class="trailing"></span></button>
    <li>Item 4</li>
    
    <!-- Submenu 1 -->
    <menu popover id="submenu-1">
        <li>Item 1</li>
        <li>Item 2</li>
        <button x-dropdown.hover="submenu-2"><span>Hover Submenu</span><span x-icon="lucide:chevron-right" class="trailing"></span></button>
        
        <!-- Submenu 2 -->
        <menu popover id="submenu-2">
            <li>Item 1</li>
            <li>Item 2</li>
            <li>Item 3</li>
        </menu>

    </menu>
</menu>

Nested dropdowns automatically position themselves to avoid overlapping and maintain proper navigation flow.


Positioning

Menus have utility classes like top and bottom to position them in relation to their trigger buttons. If no class is set, menus default to bottom-start, or end-start if nested.

Examples
<!-- Top -->
<menu popover id="..." class="top">...</menu>

<!-- Bottom with start alignment -->
<menu popover id="..." class="bottom-start">...</menu>

<!-- Start with top alignment -->
<menu popover id="..." class="start-top">...</menu>

<!-- Top start corner (either version works) -->
<menu popover id="..." class="top-start-corner">...</menu>
<menu popover id="..." class="start-top-corner">...</menu>

Regardless of a set class, dropdowns overflowing the viewport will attempt to stay onscreen with default fallback positions.


Templating

HTML IDs must identify single elements on a page, and generating multiple dropdowns in a template loop requires each dropdown be assigned a unique ID. These can be generated with Alpine using template literals like ${i}.


<template x-for="i in 3" :key="i">

    <!-- Multiple elements need to be wrapped in a container, since template tags only recognize their first child. -->
    <div>

        <!-- Button template -->
        <button x-dropdown="`template-menu-${i}`" x-text="`Menu ${i}`"></button>
        
        <!-- Menu template -->
        <menu popover :id="`template-menu-${i}`">
            <li>Item 1</li>
            <li>Item 2</li>
        </menu>

    </div>

</template>

Content

Use <li> elements for generic options in a dropdown, with Alpine's @click directive giving them utility. A variety of other elements support additional dropdown content needs.


<button x-dropdown="content-menu"><span>Dropdown</span><span x-icon="lucide:chevron-down" class="trailing"></span></button>

<menu popover id="content-menu" class="w-60 max-h-160">

    <small>List Items</small>
    <li @click="alert('Hello world')">Do Something</li>
    <li><span x-icon="lucide:house"></span><span>Icon</span></li>
    <li><span>Trailing</span><kbd class="trailing"></kbd><kbd>D</kbd></li>
    <li class="brand">Brand</li>
    <li class="accent">Accent</li>
    <li class="negative">Negative</li>

    <hr>

    <small>Links</small>
    <a href="#"><span x-icon="lucide:home"></span>Home</a>
    <a href="#"><span x-icon="lucide:settings"></span><span>Settings</span><span x-icon="lucide:external-link" class="trailing"></span></a>

    <hr>

    <small>Buttons</small>
    <button><span x-icon="lucide:copy"></span><span>Copy</span></button>
    <button><span x-icon="lucide:edit"></span><span>Edit</span></button>

    <hr>

    <small>Checkboxes</small>
    <label><input type="checkbox" /><span>Lorem ipsum dolor sit amet</span></label>
    <label><input type="checkbox" /><span>Consectetur adipiscing elit</span></label>

    <hr>

    <small>Radios</small>
    <label><input type="radio" id="option-a" name="group-preview" checked /><span>Option A</span></label>
    <label><input type="radio" id="option-b" name="group-preview" /><span>Option B</span></label>
    <label><input type="radio" id="option-c" name="group-preview" /><span>Option C</span></label>

    <hr>

    <small>Switches</small>
    <label for="switch1">Switch 1<input id="switch1" role="switch" type="checkbox" checked /></label>
    <label for="switch2">Switch 2<input id="switch2" role="switch" type="checkbox"/></label>

    <hr>

    <small>Text Inputs</small>
    <input placeholder="Text input" />
    <textarea placeholder="Textarea"></textarea>

</menu>

Use the following elements as direct children of a dropdown <menu>:

  • <small> - Group titles
  • <hr> - Dividers
  • <li> - Generic options
  • <button> - Button options (i.e. triggers for sub-dropdowns)
  • <a> - Links
  • <label> - Wrappers for radios, checkboxes, and switches
  • <input> - Single line text fields
  • <textarea> - Multi-line text fields

And use <span> within applicable elements above for icons, truncating text with ellipsis, and trailing content.


Closed Menus Cost Nothing

A closed menu's contents are not initialized until it opens. Nothing inside it runs while it is closed, so a page can hold hundreds of menus for free.

During idle time, Manifest warms the menus the visitor is likely to open next, so opening is instant. A menu that has not been warmed renders when it opens, and a menu opened once stays ready.

Three optional knobs on the <menu> element:

  • x-defer.priority="1" — warm this menu first (a lower number is warmed earlier). Use on the one or two menus people open most.
  • x-defer.off — keep a menu eager. Only for menus whose contents must exist while closed.
  • x-defer.discard — throw the contents away when the menu closes, for large, rarely reopened menus.
<menu popover id="account-menu" x-defer.priority="1">
    <li>Profile</li>
    <li>Sign out</li>
</menu>

The modifier is part of the attribute name and the number is its value: x-defer.priority="1". Writing x-defer="priority:1" does nothing.


<div x-data="{ countries: /* 300 entries */ }">
    <button x-dropdown="country-menu">Country</button>

    <menu popover id="country-menu">
        <div x-virtual class="h-64">
            <template x-for="country in countries" :key="country.id">
                <li x-text="country.name"></li>
            </template>
        </div>
    </menu>
</div>

Three hundred rows are in the menu, but only the visible ones are rendered, and none of them exist until the menu opens.


Styles

Theme

Default dropdowns use the following theme variables:

Variable Purpose
--color-popover-surface Menu background color
--color-content-stark Menu text color
--color-field-surface Hover background color
--color-content-neutral Section title color
--color-line Divider color
--spacing-popover-offset Offset from trigger element

Tailwind CSS

If using Tailwind, individual menus can be customized with utility classes. Menus taller than a max height will vertically scroll.


<button x-dropdown="menu-wide-preview">Offset & Widen</button>
<menu popover id="menu-wide-preview" class="w-100 !m-6">
    <li>Lorem ipsum dolor sit amet</li>
    <li>Consectetur adipiscing elit</li>
    <li>Sed do eiusmod tempor incididunt</li>
</menu>

Customization

Modify base dropdown styles with custom CSS for the menu[popover] selector.


menu[popover] {
    background-color: #f0f8ff;
    border: 2px solid #3b82f6;
    border-radius: 12px;
    box-shadow: 0 8px 25px rgba(59, 130, 246, 0.3);

    /* Any relevant options */
    & :where(li, a, button, label) {
        color: #1e40af;
        border-radius: 8px;

        &:hover {
            background-color: #dbeafe;
        }
    }
}