
LiveView DOM element bindings can be used to send events to the server, as well as issue LiveView JS commands on the client.
In another post, we used client-side JS commands to show and hide content in a set of tabs, just by manipulating DOM element attributes.
JS.push
is a bit different; it has one foot on the client side and one on the server side. On its own, JS.push
provides a combined API for pushing events to the server, specifying targets and payloads, and customizing loading states. As a LiveView JS command, it's composable with the other JS commands to coordinate more complex, optimistic client-side effects.
In its basic event-pushing functionality, JS.push
provides an alternative syntax for some things you can already do with phx-*
bindings alone.
Let's take a closer look at how we can migrate between pure phx-*
pushes and JS.push
. Then we'll take it one step further and combine a push with an asynchronous transition effect, by composing JS.push
with JS.transition
.
Sending a Simple Click Event to the Server
If all you want to do is send an event called clicked
to the server, you can use the phx-click
binding and call it a day.
<button phx-click="clicked">Don't panic!</button>
Here's how we can write the exact same thing using the JS.push
API:
<button phx-click={JS.push("clicked")}>Don't panic!</button>
This looks a little bit silly. But it will work! Either way, we're sending the clicked
event to the server, where we'd have a handle_event
callback defined that knows what to do with clicked
events.
Here's a super-simple callback that can receive our event and print the string Handling clicked event
to the iex console.
def handle_event("clicked", _values, socket) do
IO.inspect("Handling clicked event")
{:noreply, socket}
end
A Click With a Payload
Say we want to send an event called clicked
, with a payload of parameter values: val1
, val2
, and val3
. We can do it with phx-click
and phx-value-*
:
<button
phx-click="clicked"
phx-value-val1="At"
phx-value-val2="the"
phx-value-val3="disco!">
Panic!
</button>
Or we can use JS.push
, passing a map of values using its value
option:
<button
phx-click={
JS.push("clicked",
value: %{val1: "At", val2: "the", val3: "disco!"})
}
>
Panic!
</button>
Either way, our handle_event
callback receives a map as its values
parameter.
The following example callback assigns values for variables val1
, val2
, and val3
from the values
map, and concatenates them into a string called content
. Finally, it uses this string to update the value of the content
assign and returns the updated socket.
def handle_event("clicked", values, socket) do
%{"val1" => val1, "val2" => val2, "val3" => val3} = values
content = val1 <> " " <> val2 <> " " <> val3
{:noreply, assign(socket, :content, content)}
end
Some part of our LiveComponent would render the contents of the content
assign; for example:
When the button is clicked, content
gets updated and our concatenated string appears in the rectangle.
Pushing the Event to a Specific Target
We can send events to a particular LiveView or LiveComponent, by specifying a DOM selector that matches an element or elements inside it. The owner of each matching DOM element will automatically receive the event.
Imagine we've defined a LiveView named ContentLive
and we render two ContentLive
s with DOM IDs content1
and content2
.
<%= live_render(@socket, ContentLive, id: "content1", session: %{}})%>
<%= live_render(@socket, ContentLive, id: "content2", session: %{}})%>
And the HTML content rendered inside them is the following:
~H"""
<div class="container_content">
<h1><%= @content %></h1>
</div>
"""
Each ContentLive
will render one div with the class container_content
. We can target an event to both divs using the class selector as follows.
With phx-click
and phx-target
:
<button phx-target=".container_content" phx-click="clicked">
Panic!
</button>
With JS.push
:
<button phx-click={JS.push("clicked", target: ".container_content")}>
Panic!
</button>
Either way, both of our ContentLive
LiveViews will receive and handle the clicked
event, because they each contain an element with class container_content
. We didn't need to specify the ContentLive
s by id.
We can target a LiveView or LiveComponent directly by id if we want to:
# pure phx-bindings way
<button phx-target="#content1" phx-click="clicked">
Panic!
</button>
# JS.push way
<button phx-click={JS.push("clicked", target: "#content1")}>
Panic!
</button>
Fly ❤️ Elixir
Fly is an awesome place to run your Elixir apps. It's really easy to get started. You can be running in minutes.

Composing JS.push With Other JS Commands
In all the above scenarios, JS.push
is doing nothing more than what the phx-*
bindings are doing. We haven't seen any compelling reason to migrate to the JS.push
syntax.
But here's a cool trick! If we're using JS.push
to send our event, we can orchestrate client-side transition effects along with our push, simply by composing it with other LiveView JS commands!
Let's try with the JS.transition
command:
<button phx-click={
JS.push("clicked", value: %{val1: "At", val2: "the", val3: "disco!"})
|> JS.transition("shake", to: "#content")
}
>
Panic!
</button>
This applies a transition named shake
to the div with id content
, totally client-side, at the same time the event is being processed and the content
assign is changed.
Even when JS.push
itself is replicating push behavior that can be achieved with phx-*
bindings alone, we've found a use-case for preferring the JS.push
syntax, and one of its raisons d'être as a LiveView JS command.