Dropdown

Action menu anchored to a trigger, with submenus and keyboard navigation.

Basic

.heex
<.dropdown id="basic-dropdown" label="Account">
  <.dropdown_button>Profile</.dropdown_button>
  <.dropdown_button>Billing</.dropdown_button>
  <.dropdown_button>Team</.dropdown_button>
  <.dropdown_separator />
  <.dropdown_button>Sign out</.dropdown_button>
</.dropdown>

Links and buttons

dropdown_link renders a Phoenix <.link> for navigation; dropdown_button renders a <button> for actions. Both participate in keyboard navigation.

.heex
<.dropdown label="Navigate">
  <.dropdown_link navigate="/dashboard">Dashboard</.dropdown_link>
  <.dropdown_link patch="/settings">Settings</.dropdown_link>
  <.dropdown_link href="https://fluxonui.com" target="_blank" rel="noopener">
    Documentation
  </.dropdown_link>
</.dropdown>

<.dropdown label="Act">
  <.dropdown_button phx-click="duplicate">Duplicate</.dropdown_button>
  <.dropdown_button phx-click="archive">Archive</.dropdown_button>
  <.dropdown_button phx-click={JS.dispatch("editor:save")}>Save copy</.dropdown_button>
</.dropdown>

With icons

Add class="icon" on icon elements; the menu item layout sizes and aligns them automatically.

.heex
<.dropdown label="Workspace">
  <.dropdown_link navigate="/home">
    <.icon name="hero-home" class="icon" /> Home
  </.dropdown_link>
  <.dropdown_link navigate="/projects">
    <.icon name="hero-folder" class="icon" /> Projects
  </.dropdown_link>
  <.dropdown_link navigate="/team">
    <.icon name="hero-users" class="icon" /> Team
  </.dropdown_link>
  <.dropdown_link navigate="/settings">
    <.icon name="hero-cog-6-tooth" class="icon" /> Settings
  </.dropdown_link>
</.dropdown>

Sections

Group related items with dropdown_header labels and dropdown_separator dividers. Headers and separators are skipped by keyboard navigation.

.heex
<.dropdown label="Settings">
  <.dropdown_header>Account</.dropdown_header>
  <.dropdown_link navigate="/profile">
    <.icon name="hero-user" class="icon" /> Profile
  </.dropdown_link>
  <.dropdown_link navigate="/billing">
    <.icon name="hero-credit-card" class="icon" /> Billing
  </.dropdown_link>

  <.dropdown_header>Workspace</.dropdown_header>
  <.dropdown_link navigate="/team">
    <.icon name="hero-users" class="icon" /> Team
  </.dropdown_link>
  <.dropdown_link navigate="/integrations">
    <.icon name="hero-cog-6-tooth" class="icon" /> Integrations
  </.dropdown_link>
</.dropdown>

Disabled items

Anchors don't accept the native disabled, so disable a dropdown_link with data-disabled. Use disabled on dropdown_button.

.heex
<.dropdown label="Account">
  <.dropdown_button>Available</.dropdown_button>
  <.dropdown_button disabled>Upgrade plan</.dropdown_button>
  <.dropdown_separator />
  <.dropdown_link navigate="/settings">Settings</.dropdown_link>
  <.dropdown_link navigate="/admin" data-disabled>Admin panel</.dropdown_link>
</.dropdown>

Trailing accessories

The menu item layout reserves a third column for content that flows after the label. Drop in shortcuts, counts, or badges.

.heex
<.dropdown label="Quick actions" class="w-64">
  <.dropdown_button phx-click={JS.push("open_search")}>
    <.icon name="hero-magnifying-glass" class="icon" /> Search <kbd class="text-xs text-foreground-softest">Cmd+K</kbd>
  </.dropdown_button>
  <.dropdown_button phx-click="duplicate">
    <.icon name="hero-document-duplicate" class="icon" /> Duplicate
    <kbd class="text-xs text-foreground-softest">Cmd+Shift+D</kbd>
  </.dropdown_button>
  <.dropdown_separator />
  <.dropdown_link navigate="/notifications">
    <.icon name="hero-bell" class="icon" /> Notifications
    <.badge size="xs">12</.badge>
  </.dropdown_link>
  <.dropdown_link navigate="/upgrade">
    <.icon name="hero-sparkles" class="icon" /> Upgrade
    <.badge color="warning" size="xs">PRO</.badge>
  </.dropdown_link>
</.dropdown>

Destructive action

Pair text-danger with phx-confirm for delete-style items. The browser confirms before the click event fires.

.heex
<.dropdown label="Document">
  <.dropdown_button>
    <.icon name="hero-pencil-square" class="icon" /> Rename
  </.dropdown_button>
  <.dropdown_button>
    <.icon name="hero-document-duplicate" class="icon" /> Duplicate
  </.dropdown_button>
  <.dropdown_separator />
  <.dropdown_button
    class="text-danger"
    phx-click="delete"
    phx-value-id="42"
    phx-confirm="Delete this document? This cannot be undone."
  >
    <.icon name="hero-trash" class="icon" /> Delete
  </.dropdown_button>
</.dropdown>

Open on hover

open_on_hover opens the menu when the cursor enters the toggle. Pair with hover_open_delay and hover_close_delay to add intent buffers.

.heex
<.dropdown id="hover-dropdown" label="Products" open_on_hover hover_open_delay={120} hover_close_delay={200}>
  <.dropdown_link navigate="/components/dropdown">Cloud</.dropdown_link>
  <.dropdown_link navigate="/components/dropdown">Self-hosted</.dropdown_link>
  <.dropdown_separator />
  <.dropdown_link navigate="/components/dropdown">Pricing</.dropdown_link>
</.dropdown>

Placement

placement picks the side and alignment relative to the toggle. Floating UI flips and shifts the panel to keep it inside the viewport.

.heex
<.dropdown placement="top" label="Top">...</.dropdown>
<.dropdown placement="right-end" label="Right end">...</.dropdown>
<.dropdown placement="bottom-end" label="Bottom end">...</.dropdown>

Menu height

The menu panel is capped at max-h-[500px] and shrinks to fit the space available, only opening on the other side once that space gets too small. Pass another max-h-* through class to change the cap, or max-h-none to fill the space on whichever side it opens.

.heex
<.dropdown label="Default">...</.dropdown>
<.dropdown label="Shorter cap" class="max-h-64">...</.dropdown>
<.dropdown label="No cap" class="max-h-none">...</.dropdown>

Custom toggle

Pass any markup as the :toggle slot. Workspace switchers, profile chips, or filter pills all map onto this pattern. The wrapper carries the menu's ARIA wiring; the inner element is yours.

.heex
<.dropdown class="w-60">
  <:toggle class="flex items-center gap-x-2 rounded-base border border-base bg-base px-2 py-1.5 hover:highlight aria-expanded:highlight">
    <span class="size-7 inline-flex items-center justify-center rounded-md bg-indigo-500 text-white text-sm font-semibold">
      A
    </span>
    <span class="flex flex-col items-start leading-tight">
      <span class="text-sm font-semibold">Acme Inc.</span>
      <span class="text-xs text-foreground-softer">Workspace</span>
    </span>
    <.icon
      name="hero-chevron-down"
      class="size-4 text-foreground-softer ml-2 in-aria-expanded:rotate-180 transition-transform"
    />
  </:toggle>

  <.dropdown_header>Switch workspace</.dropdown_header>

  <.dropdown_button>
    <span class="size-5 inline-flex items-center justify-center rounded bg-indigo-500 text-white text-[10px] font-semibold -ml-0.5">
      A
    </span>
    Acme Inc. <.icon name="hero-check" class="size-4 text-foreground" />
  </.dropdown_button>
  <.dropdown_button>
    <span class="size-5 inline-flex items-center justify-center rounded bg-blue-500 text-white text-[10px] font-semibold -ml-0.5">
      G
    </span>
    Globex
  </.dropdown_button>
  <.dropdown_button>
    <span class="size-5 inline-flex items-center justify-center rounded bg-emerald-500 text-white text-[10px] font-semibold -ml-0.5">
      I
    </span>
    Initech
  </.dropdown_button>

  <.dropdown_separator />

  <.dropdown_button>
    <.icon name="hero-plus" class="icon" /> Create workspace
  </.dropdown_button>
</.dropdown>

Anchor a primary call-to-action at the bottom of a menu with a final dropdown_custom. Fits permission, sharing, or upgrade CTAs.

Selectable items

Toggle data-active client-side with JS.set_attribute and JS.remove_attribute while you push the change to the server. The check icon flips immediately, so the menu feels optimistic even before the round trip completes.

.heex
<.dropdown label="Visibility" class="w-56">
  <.dropdown_button
    id="vis-private"
    data-vis-option
    data-active
    phx-click={
      JS.remove_attribute("data-active", to: "[data-vis-option]")
      |> JS.set_attribute({"data-active", ""}, to: "#vis-private")
      |> JS.push("set_visibility", value: %{v: "private"})
    }
  >
    <.icon name="hero-lock-closed" class="icon" /> Only me
    <.icon name="hero-check" class="size-4 text-foreground hidden in-data-active:inline-flex" />
  </.dropdown_button>
  <.dropdown_button
    id="vis-team"
    data-vis-option
    phx-click={
      JS.remove_attribute("data-active", to: "[data-vis-option]")
      |> JS.set_attribute({"data-active", ""}, to: "#vis-team")
      |> JS.push("set_visibility", value: %{v: "team"})
    }
  >
    <.icon name="hero-users" class="icon" /> Team
    <.icon name="hero-check" class="size-4 text-foreground hidden in-data-active:inline-flex" />
  </.dropdown_button>
  <.dropdown_button
    id="vis-link"
    data-vis-option
    phx-click={
      JS.remove_attribute("data-active", to: "[data-vis-option]")
      |> JS.set_attribute({"data-active", ""}, to: "#vis-link")
      |> JS.push("set_visibility", value: %{v: "link"})
    }
  >
    <.icon name="hero-link" class="icon" /> Anyone with link
    <.icon name="hero-check" class="size-4 text-foreground hidden in-data-active:inline-flex" />
  </.dropdown_button>
  <.dropdown_button
    id="vis-public"
    data-vis-option
    phx-click={
      JS.remove_attribute("data-active", to: "[data-vis-option]")
      |> JS.set_attribute({"data-active", ""}, to: "#vis-public")
      |> JS.push("set_visibility", value: %{v: "public"})
    }
  >
    <.icon name="hero-globe-alt" class="icon" /> Public
    <.icon name="hero-check" class="size-4 text-foreground hidden in-data-active:inline-flex" />
  </.dropdown_button>
</.dropdown>

Inline toggles

Use dropdown_custom to embed switches alongside menu items. Custom rows are skipped by arrow-key navigation but their controls remain reachable through Tab.

.heex
<.dropdown label="View options" class="w-64">
  <.dropdown_custom class="grid grid-cols-[auto_1fr_auto] items-center gap-x-2.5 px-3 py-2.5 sm:px-2.5 sm:py-1.5 text-sm font-medium text-foreground-soft">
    <.icon name="hero-map-pin" class="size-4 -ml-0.5" />
    <span>Pin to top</span>
    <.switch name="pin" />
  </.dropdown_custom>
  <.dropdown_custom class="grid grid-cols-[auto_1fr_auto] items-center gap-x-2.5 px-3 py-2.5 sm:px-2.5 sm:py-1.5 text-sm font-medium text-foreground-soft">
    <.icon name="hero-lock-closed" class="size-4 -ml-0.5" />
    <span>Private view</span>
    <.switch name="private" checked />
  </.dropdown_custom>
  <.dropdown_custom class="grid grid-cols-[auto_1fr_auto] items-center gap-x-2.5 px-3 py-2.5 sm:px-2.5 sm:py-1.5 text-sm font-medium text-foreground-soft">
    <.icon name="hero-moon" class="size-4 -ml-0.5" />
    <span>Dark mode</span>
    <.switch name="dark_mode" />
  </.dropdown_custom>
</.dropdown>

Filter menu

Multi-select with checkbox and single-select with radio, grouped by dropdown_header. Each option is a <label> wrapped in dropdown_custom so the full row toggles the input.

.heex
<.dropdown class="w-56">
  <:toggle class="gap-x-1.5 h-9 px-3.5 text-sm font-medium border border-base rounded-base hover:highlight">
    <.icon name="hero-funnel" class="icon" /> Filter
    <.icon
      name="hero-chevron-down"
      class="size-4 in-aria-expanded:rotate-180 transition-transform"
    />
  </:toggle>

  <.dropdown_header>Owner</.dropdown_header>
  <.dropdown_custom class="px-1 sm:px-1 py-0">
    <label
      for="filter-owner-me"
      class="flex items-center gap-x-2.5 px-2 py-1.5 rounded-base hover:highlight cursor-pointer text-sm font-medium text-foreground-soft"
    >
      <.checkbox id="filter-owner-me" name="owner[me]" checked /> Me
    </label>
    <label
      for="filter-owner-shared"
      class="flex items-center gap-x-2.5 px-2 py-1.5 rounded-base hover:highlight cursor-pointer text-sm font-medium text-foreground-soft"
    >
      <.checkbox id="filter-owner-shared" name="owner[shared]" /> Shared with me
    </label>
  </.dropdown_custom>

  <.dropdown_separator />

  <.dropdown_header>Date modified</.dropdown_header>
  <.dropdown_custom class="px-1 sm:px-1 py-0">
    <label
      :for={{value, text} <- @date_options}
      class="flex items-center gap-x-2.5 px-2 py-1.5 rounded-base hover:highlight cursor-pointer text-sm font-medium text-foreground-soft"
    >
      <input type="radio" name="date_modified" value={value} checked={value == "anytime"} />
      {text}
    </label>
  </.dropdown_custom>
</.dropdown>

Pair dropdown_submenu_trigger with a sibling :submenu slot whose id matches. The submenu panel renders as a sibling of the root, so it cannot be clipped by parent overflow.

.heex
<.dropdown label="More" class="w-48">
  <.dropdown_link navigate="/profile">Profile</.dropdown_link>
  <.dropdown_link navigate="/settings">Settings</.dropdown_link>
  <.dropdown_separator />

  <.dropdown_submenu_trigger submenu="more-options">More options</.dropdown_submenu_trigger>

  <:submenu id="more-options" class="w-48">
    <.dropdown_link navigate="/preferences">Preferences</.dropdown_link>
    <.dropdown_link navigate="/integrations">Integrations</.dropdown_link>
    <.dropdown_link navigate="/notifications">Notifications</.dropdown_link>
  </:submenu>
</.dropdown>

Nested submenus

Submenus can contain their own dropdown_submenu_trigger items, opening yet another panel. Each level closes independently with ArrowLeft.

.heex
<.dropdown label="File" class="w-48">
  <.dropdown_button>
    <.icon name="hero-document" class="icon" /> New
  </.dropdown_button>
  <.dropdown_button>
    <.icon name="hero-archive-box-arrow-down" class="icon" /> Save
  </.dropdown_button>
  <.dropdown_separator />

  <.dropdown_submenu_trigger submenu="export">
    <.icon name="hero-arrow-up-tray" class="icon" /> Export as
  </.dropdown_submenu_trigger>

  <:submenu id="export" class="w-44">
    <.dropdown_button>
      <.icon name="hero-document-text" class="icon" /> PDF
    </.dropdown_button>
    <.dropdown_button>CSV</.dropdown_button>
    <.dropdown_submenu_trigger submenu="export-image">
      <.icon name="hero-photo" class="icon" /> Image
    </.dropdown_submenu_trigger>
  </:submenu>

  <:submenu id="export-image" class="w-32">
    <.dropdown_button>PNG</.dropdown_button>
    <.dropdown_button>JPG</.dropdown_button>
    <.dropdown_button>SVG</.dropdown_button>
  </:submenu>
</.dropdown>

Account menu

Compose a custom toggle, a dropdown_custom user card, sectioned links, and a destructive sign-out into one menu.

.heex
<.dropdown class="w-64" placement="bottom-start">
  <:toggle class="flex items-center gap-x-2 rounded-full p-1 hover:highlight aria-expanded:highlight">
    <img src={@user.avatar} alt="" class="size-8 rounded-full" />
    <.icon
      name="hero-chevron-down"
      class="size-4 text-foreground-softer in-aria-expanded:rotate-180 transition-transform"
    />
  </:toggle>

  <.dropdown_custom class="flex items-center gap-3 px-3 py-2 sm:px-2.5">
    <img src={@user.avatar} alt="" class="size-9 rounded-full" />
    <div class="flex flex-col">
      <span class="text-sm font-medium leading-snug">{@user.name}</span>
      <span class="text-xs text-foreground-softer leading-snug">{@user.email}</span>
    </div>
    <.badge color="success" size="sm" class="ml-auto font-bold">PRO</.badge>
  </.dropdown_custom>

  <.dropdown_separator />

  <.dropdown_header>Account</.dropdown_header>
  <.dropdown_link navigate={~p"/profile"}>
    <.icon name="hero-user" class="icon" /> Profile
  </.dropdown_link>
  <.dropdown_link navigate={~p"/security"}>
    <.icon name="hero-lock-closed" class="icon" /> Security
  </.dropdown_link>
  <.dropdown_link navigate={~p"/billing"}>
    <.icon name="hero-credit-card" class="icon" /> Billing
  </.dropdown_link>

  <.dropdown_separator />

  <.dropdown_link navigate={~p"/help"}>
    <.icon name="hero-question-mark-circle" class="icon" /> Help center
  </.dropdown_link>
  <.dropdown_link navigate={~p"/sign-out"} class="text-danger">
    <.icon name="hero-arrow-right-on-rectangle" class="icon" /> Sign out
  </.dropdown_link>
</.dropdown>

Filter with submenus

Submenus composed with a custom header, leading icons, count badges, and a Reset/Apply footer for a layered filter pattern.

.heex
<.dropdown class="w-60">
  <:toggle class="gap-x-1.5 h-9 px-3.5 text-sm font-medium border border-base rounded-base hover:highlight">
    <.icon name="hero-funnel" class="icon" /> Filter
    <.icon
      name="hero-chevron-down"
      class="size-4 in-aria-expanded:rotate-180 transition-transform"
    />
  </:toggle>

  <.dropdown_custom class="flex items-center justify-between px-2 py-1.5 border-b border-base">
    <span class="text-sm font-semibold">Filter</span>
    <.button size="icon-xs" variant="ghost" aria-label="Close">
      <.icon name="hero-x-mark" class="icon" />
    </.button>
  </.dropdown_custom>

  <.dropdown_submenu_trigger submenu="filter-status">
    <.icon name="hero-flag-solid" class="icon" /> Status
    <.badge size="xs" class="ml-auto">1</.badge>
  </.dropdown_submenu_trigger>

  <.dropdown_submenu_trigger submenu="filter-type">
    <.icon name="hero-inbox-solid" class="icon" /> Type
    <.badge size="xs" class="ml-auto">5</.badge>
  </.dropdown_submenu_trigger>

  <.dropdown_separator />

  <.dropdown_custom class="flex gap-2 px-1.5 pt-1 pb-0.5 sm:px-1">
    <.button size="sm" variant="outline" class="flex-1">Reset</.button>
    <.button size="sm" variant="solid" color="primary" class="flex-1">Apply</.button>
  </.dropdown_custom>

  <:submenu id="filter-status" class="w-52">
    <.dropdown_button>To-do</.dropdown_button>
    <.dropdown_button>In progress</.dropdown_button>
    <.dropdown_button>Completed</.dropdown_button>
  </:submenu>

  <:submenu id="filter-type" class="w-52">
    <.dropdown_button>Bug</.dropdown_button>
    <.dropdown_button>Feature</.dropdown_button>
    <.dropdown_button>Improvement</.dropdown_button>
  </:submenu>
</.dropdown>

Row action menu

Compact ellipsis trigger inside a list row, placement="bottom-end" so the panel aligns with the row's edge. The standard pattern for table-cell actions.

INV-2026-0142
Acme Co. - $1,240.00
INV-2026-0141
Globex - $480.00
.heex
<.dropdown placement="bottom-end">
  <:toggle class="size-8 justify-center rounded-base hover:highlight">
    <.icon name="hero-ellipsis-horizontal" class="icon" />
    <span class="sr-only">Actions</span>
  </:toggle>

  <.dropdown_button>
    <.icon name="hero-eye" class="icon" /> View
  </.dropdown_button>
  <.dropdown_button>
    <.icon name="hero-envelope" class="icon" /> Resend email
  </.dropdown_button>
  <.dropdown_separator />
  <.dropdown_button class="text-danger">
    <.icon name="hero-trash" class="icon" /> Void
  </.dropdown_button>
</.dropdown>