Simple example of a dropdown in Phoenix Liveview JS and tailwind

tailwind phoenix elixir
Adding the module below will allow you to add the following drop down menu with little effort to your Liveview project.
Here is what we are going to make.

alt text

Lets add a Dropdown Function Component.

Assuming you are using the default tailwind css library just copy this module and you are ready to go. Place this module in your project’s web components folder and replace the YourAppWeb with your projects namespace.

defmodule YourAppWeb.MenuComponents do
  # Gives use our foundation for our function component.
  use Phoenix.Component

  # Will be using this to toggle open and close without server side code
  alias Phoenix.LiveView.JS

  # We want to give our component an id so we can target it with our JS function. 
  attr :id, :string, required: true

  # A way to add your own styles 
  attr :class, :string, default: nil

  # A slot for creating <:item> elements
  slot :item, required: true

  # The content of our button
  slot :inner_block, required: true

  def dropdown(assigns) do
    ~H"""
    <div id={"#{@id}-dropdown"} class={["relative", @class]}>
      <button
        phx-click={show("##{@id}-dropdown-menu") |> JS.set_attribute({"aria-expanded", "true"})}
        phx-click-away={hide("##{@id}-dropdown-menu") |> JS.set_attribute({"aria-expanded", "false"})}
        type="button"
        class="-m-1.5 block p-1.5 text-zinc-400 dark:text-zinc-300 hover:text-zinc-500 hover:bg-zinc-200 dark:hover:bg-zinc-700 rounded-full hover:text-zinc-900 dark:hover:text-zinc-100 transition duration-300 ease-in-out transition-colors"
        id={"#{@id}-dropdown-button"}
        aria-expanded="false"
        aria-haspopup="true"
      >
        <span class="sr-only">Open dropdown</span>
        <%= render_slot(@inner_block) %>
      </button>

      <div
        id={"#{@id}-dropdown-menu"}
        class="hidden absolute right-0 z-10 mt-0.5 w-32 origin-top-right rounded-md bg-white dark:bg-zinc-700 py-2 shadow-lg ring-1 ring-zinc-900/5 dark:ring-zinc-100/5 focus:outline-none"
        role="menu"
        aria-orientation="vertical"
        aria-labelledby="#{@id}-dropdown-menu-button"
        tabindex="-1"
      >
        <div
          :for={item <- @item}
          role="menuitem"
          tabindex="-1"
          class="hover:bg-zinc-200 dark:hover:bg-zinc-800 transition duration-300 ease-in-out transition-colors"
        >
          <%= render_slot(item) %>
        </div>
      </div>
    </div>
    """
  end
 
  # These following functions came for the core components by the phx.new generator. You could easily import these functions instead of defining them here. 
  defp show(js \\ %JS{}, selector) do
    JS.show(js,
      to: selector,
      time: 300,
      transition:
        {"transition-all transform ease-out duration-300",
         "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95",
         "opacity-100 translate-y-0 sm:scale-100"}
    )
  end

  defp hide(js \\ %JS{}, selector) do
    JS.hide(js,
      to: selector,
      time: 200,
      transition:
        {"transition-all transform ease-in duration-200",
         "opacity-100 translate-y-0 sm:scale-100",
         "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"}
    )
  end
end

To use

 <YourAppWeb.MenuComponents.dropdown id={id} class="ml-auto">
  <span>Dropdown menu</span>

  <:item>
    <.link
      class="block px-3 py-1 text-sm/6 text-zinc-900 dark:text-zinc-100"
      patch={~p"/"}
    >
      Somelink
    </.link>
  </:item>
  <:item>
    <.link
      class="block px-3 py-1 text-sm/6 text-zinc-900 dark:text-zinc-100"
      patch={~p"/"}
    >
      Somelink
    </.link>
  </:item>
</YourAppWeb.Web.MenuComponents.dropdown>

Bonus points, import your module so you can just simply use <.dropdown> in your templates.