Fluxon.Components.Dropdown (Fluxon v2.4.0-rc.2)
Provides <.dropdown> component for building accessible menu interfaces with keyboard navigation, submenus, and automatic positioning.
This module renders a dropdown menu system that supports both click and hover interactions, nested
submenus, custom toggle elements, and rich content areas. The menu panel is positioned with
Floating UI, adapting to available viewport space. It includes six sub-components for composing
menu content: dropdown_link/1, dropdown_button/1, dropdown_header/1, dropdown_separator/1,
dropdown_custom/1, and dropdown_submenu_trigger/1.
Basic Usage
Render a dropdown with navigation links:
<.dropdown>
<.dropdown_link navigate={~p"/profile"}>Profile</.dropdown_link>
<.dropdown_link navigate={~p"/settings"}>Settings</.dropdown_link>
<.dropdown_separator />
<.dropdown_link href={~p"/logout"} method="delete">Sign Out</.dropdown_link>
</.dropdown>The default toggle renders a <.button> with the text "Menu" and a chevron icon. Provide a
label attribute to customize the button text, or use the :toggle slot for full control.
Custom Toggle
Replace the default button with any element via the :toggle slot. The slot wrapper receives
the required aria-haspopup, aria-expanded, and aria-controls attributes automatically:
<.dropdown>
<:toggle>
<button class="flex items-center gap-x-2 bg-zinc-200/50 rounded-lg p-2">
<img src={~p"/images/human-avatar-01.png"} alt="User" class="size-6 rounded-lg" />
<div class="text-sm text-gray-800 font-semibold">John Doe</div>
<.icon name="u-chevron-down" class="size-4" />
</button>
</:toggle>
<.dropdown_button>Profile</.dropdown_button>
<.dropdown_button>Billing</.dropdown_button>
<.dropdown_button>Settings</.dropdown_button>
</.dropdown>Apply additional styles to the toggle wrapper with the slot's class attribute:
<:toggle class="my-custom-class">
<!-- custom toggle content -->
</:toggle>Menu Items
Use dropdown_link/1 for navigation and dropdown_button/1 for actions. Both render with
role="menuitem" and participate in keyboard navigation:
<.dropdown>
<.dropdown_link navigate={~p"/dashboard"}>Dashboard</.dropdown_link>
<.dropdown_link patch={~p"/settings"}>Settings</.dropdown_link>
<.dropdown_link href="https://docs.example.com" target="_blank">Docs</.dropdown_link>
<.dropdown_separator />
<.dropdown_button phx-click="sign_out">Sign Out</.dropdown_button>
</.dropdown>Clicking a menu item closes the dropdown automatically. For link items, LiveView navigation
attributes (navigate, patch, href) work as expected.
Disabled Items
Disabled items are visually dimmed, skipped during keyboard navigation, and cannot be activated:
<.dropdown>
<.dropdown_button>Account</.dropdown_button>
<.dropdown_button disabled>Upgrade Plan</.dropdown_button>
<.dropdown_separator />
<.dropdown_link navigate={~p"/settings"}>Settings</.dropdown_link>
<.dropdown_link navigate={~p"/restricted"} data-disabled>Admin Panel</.dropdown_link>
</.dropdown>Disabling Links
For button items, use the standard disabled attribute. For link items, use data-disabled
since HTML anchor elements do not support the native disabled attribute. Both approaches
produce the same visual and behavioral result.
Rich Content
Combine headers, separators, and custom content areas to build complex menus. Headers and custom content areas are non-interactive and skipped during keyboard navigation:
<.dropdown class="w-64">
<.dropdown_custom class="flex items-center p-2">
<img src="https://i.pravatar.cc/150?u=1" alt="Avatar" class="size-9 rounded-full" />
<div class="flex flex-col ml-3">
<span class="text-sm font-medium">Emma Johnson</span>
<span class="text-xs text-zinc-500">emma@acme.com</span>
</div>
</.dropdown_custom>
<.dropdown_separator />
<.dropdown_header>Account</.dropdown_header>
<.dropdown_link navigate={~p"/profile"}>Profile</.dropdown_link>
<.dropdown_link navigate={~p"/billing"}>Billing</.dropdown_link>
<.dropdown_header>Support</.dropdown_header>
<.dropdown_link navigate={~p"/help"}>Documentation</.dropdown_link>
<.dropdown_link navigate={~p"/contact"}>Contact Us</.dropdown_link>
<.dropdown_separator />
<.dropdown_link href={~p"/logout"} method="delete" class="text-red-600">
Sign Out
</.dropdown_link>
</.dropdown>When hovering over headers, separators, or custom content, the active item highlight is cleared and any open child submenus are closed.
Hover Interaction
Enable hover-based opening with open_on_hover. The menu opens when the cursor enters the
toggle button and stays open while the cursor remains over the toggle or menu panel. Moving
from the toggle to the menu cancels the close timer:
<.dropdown open_on_hover hover_open_delay={200} hover_close_delay={300}>
<.dropdown_link navigate={~p"/profile"}>Profile</.dropdown_link>
<.dropdown_link navigate={~p"/settings"}>Settings</.dropdown_link>
</.dropdown>Both delays default to 0 milliseconds. The open delay prevents accidental activation when
the cursor passes over the toggle, while the close delay provides a grace period for moving
the cursor between the toggle and menu.
Positioning
Control the menu placement relative to the toggle with the placement attribute. The menu
automatically flips and shifts to stay within the viewport:
<.dropdown placement="bottom-end">
<.dropdown_link>Bottom End Aligned</.dropdown_link>
</.dropdown>
<.dropdown placement="right-start">
<.dropdown_link>Right Side Menu</.dropdown_link>
</.dropdown>Available placements: top, top-start, top-end, right, right-start, right-end,
bottom, bottom-start (default), bottom-end, left, left-start, left-end.
The positioning system uses Floating UI with flip, shift, and size middleware. The
menu panel has a maximum height of 500px and automatically enables vertical scrolling when
content overflows.
Submenus
Create hierarchical menus with the :submenu slot and dropdown_submenu_trigger/1. Each
trigger references a submenu by id, and the submenu panel renders as a sibling element
(not nested) to avoid overflow clipping:
<.dropdown>
<.dropdown_link navigate={~p"/profile"}>Profile</.dropdown_link>
<.dropdown_link navigate={~p"/settings"}>Settings</.dropdown_link>
<.dropdown_separator />
<.dropdown_submenu_trigger submenu="more">
More Options
</.dropdown_submenu_trigger>
<:submenu id="more">
<.dropdown_link navigate={~p"/preferences"}>Preferences</.dropdown_link>
<.dropdown_link navigate={~p"/integrations"}>Integrations</.dropdown_link>
<.dropdown_link navigate={~p"/notifications"}>Notifications</.dropdown_link>
</:submenu>
</.dropdown>Control submenu placement with the placement attribute on the :submenu slot. The default
is right-start:
<:submenu id="more" placement="left-start">
<!-- Opens to the left instead of right -->
</:submenu>Nested Submenus
Submenus support arbitrary nesting depth. Each level opens and closes independently with
ArrowRight / ArrowLeft:
<.dropdown>
<.dropdown_submenu_trigger submenu="file">File</.dropdown_submenu_trigger>
<:submenu id="file">
<.dropdown_link>New</.dropdown_link>
<.dropdown_submenu_trigger submenu="export">Export</.dropdown_submenu_trigger>
</:submenu>
<:submenu id="export">
<.dropdown_link>PDF</.dropdown_link>
<.dropdown_link>CSV</.dropdown_link>
</:submenu>
</.dropdown>When a submenu opens, the parent trigger remains highlighted. Hovering a sibling menu item or a custom content area closes the open submenu. Mouse hover on a submenu trigger uses an 80ms intent delay before opening, preventing accidental activation when the cursor passes over the trigger.
Keyboard Interactions
The dropdown follows the WAI-ARIA Menu Button pattern. Focus stays on the toggle button at
all times, with aria-activedescendant communicating the highlighted item to assistive
technologies. Arrow keys do not wrap at boundaries.
| Key | Context | Action |
|---|---|---|
Enter / Space | Toggle button (closed) | Opens menu |
Enter / Space | Toggle button (open, no highlight) | Closes menu |
Enter / Space | Menu item highlighted | Activates item and closes menu |
ArrowDown | Toggle button (closed) | Opens menu, highlights first item |
ArrowUp | Toggle button (closed) | Opens menu, highlights last item |
ArrowDown | Menu open | Highlights next item |
ArrowUp | Menu open | Highlights previous item |
ArrowRight | Submenu trigger highlighted | Opens submenu, highlights its first item |
ArrowLeft | Inside submenu | Closes submenu, returns to parent trigger |
Escape | Menu open | Closes all menus and submenus, returns focus to toggle |
Tab | Menu open | Closes menu, moves focus naturally |
Summary
Components
Renders a dropdown menu with toggle button, floating menu panel, and optional submenus.
Renders an action menu item as a <button type="button">.
Renders an arbitrary content area within a dropdown menu.
Renders a non-interactive section header within a dropdown menu.
Renders a navigable menu item as a Phoenix <.link>.
Renders a horizontal divider between groups of dropdown items.
Renders a menu item that opens a linked submenu panel.
Components
Renders a dropdown menu with toggle button, floating menu panel, and optional submenus.
The toggle defaults to a <.button> with the label text and a chevron icon. When
the :toggle slot is provided, it replaces the default button while preserving all
ARIA attributes on the wrapper element. The menu panel is positioned via Floating UI
and supports both click and hover interactions.
Examples
<.dropdown>
<.dropdown_link navigate={~p"/profile"}>Profile</.dropdown_link>
<.dropdown_link navigate={~p"/settings"}>Settings</.dropdown_link>
</.dropdown>
<.dropdown label="Actions" placement="bottom-end" class="w-48">
<.dropdown_button phx-click="edit">Edit</.dropdown_button>
<.dropdown_button phx-click="duplicate">Duplicate</.dropdown_button>
<.dropdown_separator />
<.dropdown_button phx-click="delete" class="text-red-600">Delete</.dropdown_button>
</.dropdown>
<.dropdown open_on_hover hover_open_delay={150} hover_close_delay={200}>
<:toggle>
<span class="text-sm underline cursor-pointer">Hover me</span>
</:toggle>
<.dropdown_link navigate={~p"/option-a"}>Option A</.dropdown_link>
<.dropdown_link navigate={~p"/option-b"}>Option B</.dropdown_link>
</.dropdown>Attributes
id(:string) - The unique identifier for the dropdown component. If not provided, one will be automatically generated.label(:string) - The text label for the default dropdown toggle button. Only used when no custom toggle is provided via the:toggleslot.Defaults to
"Menu".class(:any) - Additional CSS classes for the dropdown menu panel. Useful for controlling width, max-height, and other menu-specific styles.Defaults to
nil.container_class(:string) - Additional CSS classes for the dropdown's outer container. Affects the positioning wrapper element.Defaults to
nil.toggle_class(:string) - Additional CSS classes for the dropdown toggle button. Only applies to the default toggle button, not custom toggles.Defaults to
nil.disabled(:boolean) - When true, disables the dropdown toggle and prevents the menu from opening. Defaults tofalse.placement(:string) - Controls the placement of the dropdown menu relative to its toggle button. Supports different positions with automatic repositioning when needed.The possible values are:
top,top-start,top-end,right,right-start,right-end,bottom,bottom-start,bottom-end,left,left-start,left-endDefaults to
"bottom-start".animation(:string) - Base animation classes applied to the dropdown menu. Controls the transition timing and easing function.Defaults to
"transition ease-in-out duration-150".animation_enter(:string) - Classes applied when the dropdown menu enters. Usually defines the final state of the animation.Defaults to
"opacity-100 scale-100".animation_leave(:string) - Classes applied when the dropdown menu leaves. Usually defines the initial state of the exit animation.Defaults to
"opacity-0 scale-95".open_on_hover(:boolean) - When true, opens the dropdown menu on mouse hover instead of click. Can be combined with hover delays for better user experience.Defaults to
false.hover_open_delay(:integer) - Delay in milliseconds before opening the menu when hovering. Only applies whenopen_on_hoveris true.Defaults to
0.hover_close_delay(:integer) - Delay in milliseconds before closing the menu when mouse leaves. Only applies whenopen_on_hoveris true.Defaults to
0.
Slots
inner_block(required) - The content of the dropdown menu. Usually containsdropdown_link,dropdown_button, or other dropdown components.toggle- Optional custom toggle element. When provided, replaces the default button toggle while maintaining proper accessibility attributes.Accepts attributes:
class(:any) - Additional CSS classes for the wrapper of the custom toggle element.
submenu- Defines a submenu panel that opens when its trigger is activated. Each submenu must have a uniqueidthat matches thesubmenuattribute of the correspondingdropdown_submenu_trigger. Submenus are rendered as siblings to the main menu panel to avoid overflow clipping issues.Accepts attributes:
id(:string) (required) - Unique ID referenced by trigger'ssubmenuattribute.class(:any) - Additional CSS classes for the submenu panel.placement(:string) - Placement relative to trigger: right-start, right-end, left-start, left-end.
Renders an action menu item as a <button type="button">.
Use this for menu items that trigger actions (event handlers, JS commands) rather than
navigation. The item renders with role="menuitem" and participates in keyboard
navigation. Clicking or activating the item via Enter / Space fires the button's
click event and closes the dropdown.
Supports the standard disabled attribute. Disabled buttons are visually dimmed and
skipped during keyboard navigation.
Examples
<.dropdown_button phx-click={JS.push("update-view", value: %{view: "grid"})}>
Grid
</.dropdown_button>
<.dropdown_button phx-click="update-view" phx-value-view="list">
List
</.dropdown_button>
<.dropdown_button phx-click={Fluxon.open_dialog("new-user-dialog")}>
New User
</.dropdown_button>
<.dropdown_button disabled>
Unavailable Option
</.dropdown_button>Attributes
id(:string) - Optional unique identifier for the button element. When not provided, noidattribute will be rendered.Defaults to
nil.class(:any) - Additional CSS classes for the button element. The new classes will be merged with the default styles. Defaults tonil.Global attributes are accepted. Additional HTML attributes to apply to the button element. Useful for adding event handlers or data attributes. Supports all globals plus:
["autofocus", "disabled", "form", "formaction", "formenctype", "formmethod", "formnovalidate", "formtarget", "name", "type", "value"].
Slots
inner_block(required) - The content of the button item. Usually contains text but can include icons or other elements.
Renders an arbitrary content area within a dropdown menu.
Custom content is excluded from keyboard navigation (ArrowUp / ArrowDown skip it
entirely). However, focusable elements inside the container remain accessible via Tab.
Hovering a custom content area clears the current item highlight and closes any open
child submenu, matching the behavior of headers and separators.
Examples
<.dropdown_custom class="flex items-center p-2">
<img src="https://i.pravatar.cc/150?u=1" alt="Avatar" class="size-9 rounded-full" />
<div class="flex flex-col ml-3 mr-10">
<span class="text-sm font-medium leading-snug">Emma Johnson</span>
<span class="text-xs text-zinc-500 leading-snug">emma@acme.com</span>
</div>
<.badge color="red" class="ml-auto">PRO</.badge>
</.dropdown_custom>
<.dropdown_custom class="flex items-center gap-x-10 bg-zinc-100/70 rounded-lg p-2">
<div class="flex flex-col">
<span class="text-sm font-medium">Available Tokens</span>
<span class="text-sm text-zinc-500">Only 75 tokens available</span>
</div>
<.button size="xs" class="ml-auto">Manage</.button>
</.dropdown_custom>Attributes
class(:any) - Additional CSS classes for the custom content container. The new classes will be merged with the default styles. Defaults tonil.- Global attributes are accepted. Additional HTML attributes to apply to the custom content container.
Slots
inner_block(required) - The custom content to be rendered. Can contain any HTML or components for creating complex dropdown items.
Renders a non-interactive section header within a dropdown menu.
Headers label groups of related menu items with smaller, muted text. They are not part of keyboard navigation and cannot be selected. Hovering a header clears the current item highlight and closes any open submenu at the same level.
Examples
<.dropdown_header>Account Settings</.dropdown_header>
<.dropdown_link patch={~p"/profile"}>Edit Profile</.dropdown_link>
<.dropdown_link patch={~p"/settings"}>Preferences</.dropdown_link>
<.dropdown_header>Help & Support</.dropdown_header>
<.dropdown_link navigate={~p"/faq"}>FAQ</.dropdown_link>
<.dropdown_link navigate={~p"/contact"}>Contact Us</.dropdown_link>Attributes
class(:any) - Additional CSS classes for the header element. The new classes will be merged with the default styles. Defaults tonil.- Global attributes are accepted. Additional HTML attributes to apply to the header element.
Slots
inner_block(required) - The content of the header. Usually contains text but can include other elements for complex headers.
Renders a navigable menu item as a Phoenix <.link>.
Supports LiveView navigation (navigate, patch) and standard links (href). The item
renders with role="menuitem" and participates in keyboard navigation. Clicking or
activating the item via Enter / Space triggers navigation and closes the dropdown.
Disable a link item with the data-disabled attribute, since HTML anchors do not support
the native disabled attribute. Disabled links are visually dimmed and skipped during
keyboard navigation.
Examples
<.dropdown_link navigate={~p"/dashboard"}>
Dashboard
</.dropdown_link>
<.dropdown_link patch={~p"/settings"}>
Settings
</.dropdown_link>
<.dropdown_link href="https://example.com" target="_blank">
External Link
</.dropdown_link>
<.dropdown_link navigate={~p"/admin"} data-disabled>
Admin Panel
</.dropdown_link>Attributes
id(:string) - Optional unique identifier for the link element. When not provided, noidattribute will be rendered.Defaults to
nil.class(:any) - Additional CSS classes for the link element. The new classes will be merged with the default styles. Defaults tonil.Global attributes are accepted. Additional HTML attributes for the link element. Supports both standard anchor attributes and LiveView navigation options. Supports all globals plus:
["navigate", "patch", "href", "replace", "method", "csrf_token", "download", "hreflang", "referrerpolicy", "rel", "target", "type"].
Slots
inner_block(required) - The content of the link item. Usually contains text but can include icons or other elements.
Renders a horizontal divider between groups of dropdown items.
The separator renders as a <div> with aria-hidden="true" so it is invisible to
assistive technologies. It is not part of keyboard navigation. Hovering a separator
clears the current item highlight.
Examples
<.dropdown_link patch={~p"/profile"}>Edit Profile</.dropdown_link>
<.dropdown_link patch={~p"/settings"}>Preferences</.dropdown_link>
<.dropdown_separator />
<.dropdown_link href={~p"/sign_out"} method="delete">Sign Out</.dropdown_link>Attributes
class(:any) - Additional CSS classes for the separator element. The new classes will be merged with the default styles. Defaults tonil.- Global attributes are accepted. Additional HTML attributes to apply to the separator element.