Don’t use links for table rows, html tables are finicky so it’s best to use on table elements. Instead build a menu with the options you want. Then add a reference to the menu as a contextmenu to what ever you wish to right click. Respond to the contextmenu event.
Edit:this method currently has poor browser support which is a shame. I came across this pattern many years ago and assumed the support would be better now. everything above is supported. The menu’s related <menuitem> element has been removed from the html spec Sorry
No, neither the <table>, <tbody>, or <tr> elements accept nested anchors. Note <tr> permitted parents section, it can’t be the child of an anchor either.
This won’t make the whole row clickable just the first cell. There’s already an in-spec way to achieve what you want and you should use that since it’s web standard. If you want a hack however put an anchor in the first cell and apply an absolutely positioned pseudo element to it so that it takes up the whole row. But then you lose the ability to copy/paste or apply any other effects to the row because the first cell anchor now spans the entire row
tl;dr, Updated my core_components <.table> to include an optional row_link attr. So I can either pass in a row_link function that will wrap the slot in a <.link> (which does what people mentioned above) or use existing row_click for normal functionality. See code below (ignore custom styling), and further explanation below that:
attr :row_link, :any,
default: nil,
doc: "function that given a row returns a URL string"
attr :row_click, :any, default: nil, doc: "the function for handling phx-click on each row"
...
def table(assigns) do
assigns =
with %{rows: %Phoenix.LiveView.LiveStream{}} <- assigns do
assign(assigns, row_id: assigns.row_id || fn {id, _item} -> id end)
end
~H"""
<div class="overflow-y-auto overflow-visible border-y rounded-none bg-white 2xl:border 2xl:rounded-2xl -mx-6 2xl:mx-0">
<table class="w-[40rem] sm:w-full">
<thead class="text-sm text-left text-gray-500">
<tr>
<th :for={col <- @col} class="px-6 py-3 2xl:py-4 font-semibold">
<span :if={is_nil(col[:order_by])} class="relative">
{col[:label]}
</span>
</th>
<th :if={@action != []} class="relative p-0">
<span class="sr-only">{gettext("Actions")}</span>
</th>
</tr>
</thead>
<tbody
id={@id}
phx-update={match?(%Phoenix.LiveView.LiveStream{}, @rows) && "stream"}
class="relative divide-y divide-zinc-200 border-t border-zinc-200 text-sm leading-6 text-zinc-700"
>
<tr
:for={row <- @rows}
id={@row_id && @row_id.(row)}
class={["group hover:bg-zinc-50", @row_class && @row_class.(row)]}
>
<td
:for={{col, i} <- Enum.with_index(@col)}
class={["relative px-6 py-4", @row_click && "hover:cursor-pointer"]}
>
<%= if @row_link do %>
<.link
navigate={@row_link.(row)}
class="block h-full w-full"
aria-label={"Go to #{inspect row}"}
>
<span class={["relative", i == 0 && "font-semibold text-zinc-900"]}>
{render_slot(col, @row_item.(row))}
</span>
</.link>
<% else %>
<div class="block" phx-click={@row_click && @row_click.(row)}>
<span class={["relative", i == 0 && "font-semibold text-zinc-900"]}>
{render_slot(col, @row_item.(row))}
</span>
</div>
<% end %>
</td>
...
Under the hood, <.link> renders a real <a href="…">, so the browser gives you right-click/open-in-new-tab/copy-link for free. By contrast, phx-click attaches a JS listener that listens for click events and then pushes a LiveView message over the WebSocket—there’s no <a href> for the browser to recognize as a link. The result: you get your LiveView handler, but none of the built-in link behaviors (right-click menu, open-in-new-tab, URL preview, etc.) because to the browser it’s just a <div> with a JS listener.