First, start with defining a new LiveView component, for example in your lib/my_app/core_components.ex file. The component looks like this:

defmodule MyAppWeb.CoreComponents do

  # Standard code should be here

  attr :value, :string, required: true

  def copy_to_clipboard(assigns) do
    assigns = assigns |> assign(:uuid, Ecto.UUID.generate())

    ~H"""
    <textarea class="hidden" id={@uuid}>{@value}</textarea>

    <a phx-click={JS.dispatch("phx:copy", to: "##{@uuid}")} class="cursor-pointer">
      📋 copy to clipboard
    </a>
    """
  end
end

The component takes one single attribute which is the value to copy to the clipboard.

The component itself results in two separate HTML items.

A textarea element which contains the value to copy to the clipboard. It's hidden as the end user doesn't need to see it. I've used a textarea element as that can hold a lot of data and will preserve newlines. We also assign a unique id to the textarea as potentially, we can have multiple copies of this element on the page, and each one should know extactly what to copy to the clipboard.

We also have a a element which uses the JS.dispatch/2 function to dispatch the phx:copy event, passing along a parameter called to which specifies the element ID where the data can be found. As this is a query selector, we prepend it with a # to indicate that we query on the id of the element. As the link doesn't have a href attribute, we also add the Tailwind CSS class cursor-pointer to show the proper cursor when hovering over the item.

So far, so good, now it's time to implement the actual copy functionality. This means we need to handle the phx:copy event somewhere. Head over to the assets/js/app.js file and add the following code:

window.addEventListener("phx:copy", (event) => {
  let text = event.target.value;
  navigator.clipboard.writeText(text).then(() => {
    console.log("All done!"); // Or a nice tooltip or something.
  });
});

This takes value of the target of the event (the text to copy) and uses the navigator.clipboard.writeText function to write it to the clipboard.

To use the component, it's as simple as:

<.copy_to_clipboard value={@value} />

I got the inspiration for doing this from here.