<!-- LLM_VERSION_INFO
FORMAT: text/markdown
CONTENT_TYPE: article
ORIGINAL_URL: https://fly.io/phoenix-files/pushing-events-with-js-push
ALTERNATE_VERSION: phoenix-files/pushing-events-with-js-push/index.html (text/html)
EXTRACTION_DATE: 2026-04-18T22:12:54.863Z

This is the markdown version with text-only content (images converted to alt-text).
For rich formatting with images, request the HTML version at: phoenix-files/pushing-events-with-js-push/index.html
-->

# LiveView JS Push Functionality

LiveView [DOM element bindings](https://hexdocs.pm/phoenix_live_view/bindings.html) can be used to send events to the server, as well as issue [LiveView JS commands](https://hexdocs.pm/phoenix_live_view/bindings.html#js-commands) on the client.

[In another post](/content/phoenix-files/tabs-with-js-commands/index.html), 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.

```html
<button phx-click="clicked">Don't panic!</button>
```

Here’s how we can write the exact same thing using the `JS.push` API:

```html
<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.

```elixir
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-*`:

```html
<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:

```html
<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.

```elixir
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`.

```html
<%= live_render(@socket, ContentLive, id: "content1", session: %{}})%>
<%= live_render(@socket, ContentLive, id: "content2", session: %{}})%>
```

And the HTML content rendered inside them is the following:

```html
~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`:

```html
<button phx-target=".container_content" phx-click="clicked">
  Panic!
</button>
```

With `JS.push`:

```html
<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:

```html
# 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>
```

## 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.

For the moment, we’re putting aside the loading-state tweaks that `JS.push` enables through its `loading` and `page_loading` options.

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](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.JS.html)!

Let’s try with the `JS.transition` command:

```html
<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.
