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.

KeyContextAction
Enter / SpaceToggle button (closed)Opens menu
Enter / SpaceToggle button (open, no highlight)Closes menu
Enter / SpaceMenu item highlightedActivates item and closes menu
ArrowDownToggle button (closed)Opens menu, highlights first item
ArrowUpToggle button (closed)Opens menu, highlights last item
ArrowDownMenu openHighlights next item
ArrowUpMenu openHighlights previous item
ArrowRightSubmenu trigger highlightedOpens submenu, highlights its first item
ArrowLeftInside submenuCloses submenu, returns to parent trigger
EscapeMenu openCloses all menus and submenus, returns focus to toggle
TabMenu openCloses 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