Skip to main content

Overview

Provides commands for executing JavaScript utility operations on the client. JS commands support a variety of utility operations for common client-side needs, such as adding or removing CSS classes, setting attributes, showing or hiding content, and transitioning animations. JS commands are DOM-patch aware, so operations applied by the JS APIs will stick to elements across patches from the server.

Creating JS Commands

All functions accept an optional %JS{} struct as the first argument for chaining:
JS.push("save") |> JS.hide(to: "#modal")

Client Utility Commands

show/1

Shows elements with optional transitions.
JS.show(to: "#item")
JS.show(to: "#item", transition: "fade-in-scale")
opts
keyword
default:"[]"
Options:
  • :to - DOM selector to show (default: interacted element)
  • :transition - CSS classes or 3-tuple {transition, start, end}
  • :time - Transition duration in ms (default: 200)
  • :display - Display value when showing (default: "block")
  • :blocking - Block UI during transition (default: true)
return
JS.t()
Updated JS command struct.
Events dispatched:
  • phx:show-start - When show begins
  • phx:show-end - After transition completes

hide/1

Hides elements with optional transitions.
JS.hide(to: "#item")
JS.hide(to: "#item", transition: "fade-out-scale")
opts
keyword
default:"[]"
Options:
  • :to - DOM selector to hide (default: interacted element)
  • :transition - CSS classes or 3-tuple
  • :time - Transition duration in ms (default: 200)
  • :blocking - Block UI during transition (default: true)
Events dispatched:
  • phx:hide-start - When hide begins
  • phx:hide-end - After transition completes

toggle/1

Toggles element visibility based on current state.
JS.toggle(to: "#item")
JS.toggle(to: "#item", in: "fade-in", out: "fade-out")
opts
keyword
default:"[]"
Options:
  • :to - DOM selector (default: interacted element)
  • :in - CSS classes or 3-tuple for showing
  • :out - CSS classes or 3-tuple for hiding
  • :time - Transition duration in ms (default: 200)
  • :display - Display value when showing (default: "block")
  • :blocking - Block UI during transition (default: true)

add_class/1

Adds CSS classes to elements.
JS.add_class("highlight")
JS.add_class("highlight underline", to: "#item")
names
string
Space-separated class names to add.
opts
keyword
default:"[]"
Options:
  • :to - DOM selector (default: interacted element)
  • :transition - CSS classes or 3-tuple
  • :time - Transition duration in ms (default: 200)
  • :blocking - Block UI during transition (default: true)

remove_class/1

Removes CSS classes from elements.
JS.remove_class("highlight")
JS.remove_class("highlight underline", to: "#item")
names
string
Space-separated class names to remove.
opts
keyword
default:"[]"
Same options as add_class/1.

toggle_class/1

Toggles CSS classes based on presence.
JS.toggle_class("active")
JS.toggle_class("active", to: "#item")
names
string
Space-separated class names to toggle.
opts
keyword
default:"[]"
Same options as add_class/1.

transition/1

Applies temporary transition to elements for animations.
JS.transition("shake", to: "#item")
JS.transition({"ease-out duration-300", "opacity-0", "opacity-100"}, time: 300)
transition
string | tuple
CSS classes or 3-tuple for transition.
opts
keyword
default:"[]"
Options:
  • :to - DOM selector (default: interacted element)
  • :time - Transition duration in ms (default: 200)
  • :blocking - Block UI during transition (default: true)

set_attribute/1

Sets an attribute on elements.
JS.set_attribute({"aria-expanded", "true"}, to: "#dropdown")
attr_value
{string, string}
Tuple containing attribute name and value.
opts
keyword
default:"[]"
Options:
  • :to - DOM selector (default: interacted element)
set_attribute/1 cannot be used to set DOM properties like input value. Use dispatch/2 with a custom event listener instead.

remove_attribute/1

Removes an attribute from elements.
JS.remove_attribute("aria-expanded", to: "#dropdown")
attr
string
The attribute name to remove.
opts
keyword
default:"[]"
Options:
  • :to - DOM selector (default: interacted element)

toggle_attribute/1

Toggles an attribute based on presence.
JS.toggle_attribute({"open", "true"}, to: "#dialog")
JS.toggle_attribute({"aria-expanded", "true", "false"}, to: "#dropdown")
attr
{string, string} | {string, string, string}
2-tuple to set/remove, or 3-tuple to toggle between two values.
opts
keyword
default:"[]"
Options:
  • :to - DOM selector (default: interacted element)

ignore_attributes/1

Marks attributes as ignored, skipping them when patching the DOM.
JS.ignore_attributes("open", to: "#my-dialog")
JS.ignore_attributes(["open", "data-*"], to: "#my-dialog")
attrs
string | list
Attribute name(s) to ignore. Use * as wildcard.
opts
keyword
default:"[]"
Options:
  • :to - DOM selector (default: interacted element)
Must be used with phx-mounted binding. Does not affect initial render.

dispatch/2

Dispatches a DOM event to elements.
JS.dispatch("click", to: ".nav")
JS.dispatch("my_app:clipcopy", to: "#element", detail: %{foo: "bar"})
event
string
The event name to dispatch.
opts
keyword
default:"[]"
Options:
  • :to - DOM selector (default: interacted element)
  • :detail - Detail map for the event
  • :bubbles - Whether event bubbles (default: true)
  • :blocking - Block UI until event.detail.done() is called
All events are CustomEvent except "click" which is MouseEvent.

Focus Commands

focus/1

Sends focus to a selector.
JS.focus(to: "main")
opts
keyword
default:"[]"
Options:
  • :to - DOM selector (default: interacted element)

focus_first/1

Sends focus to the first focusable child.
JS.focus_first(to: "#modal")
opts
keyword
default:"[]"
Options:
  • :to - DOM selector (default: interacted element)

push_focus/1

Pushes focus from the source element to be later popped.
JS.push_focus()
JS.push_focus(to: "#my-button")
opts
keyword
default:"[]"
Options:
  • :to - DOM selector (default: interacted element)

pop_focus/0

Focuses the last pushed element.
JS.pop_focus()

push/1

Pushes an event to the server.
JS.push("clicked")
JS.push("save", value: %{id: 123}, target: @myself)
event
string
The event name to push.
opts
keyword
default:"[]"
Options:
  • :target - Selector or component ID (overrides phx-target)
  • :loading - Selector to apply loading classes
  • :page_loading - Trigger page loading events (default: false)
  • :value - Map of values to send (merged with phx-value-*)
Sends a navigation event to the server and updates browser history.
JS.navigate("/my-path")
JS.navigate("/my-path", replace: true)
href
string
The path to navigate to.
opts
keyword
default:"[]"
Options:
  • :replace - Replace browser history (default: false)

patch/1

Sends a patch event to the server and updates browser history.
JS.patch("/my-path")
JS.patch("/my-path", replace: true)
href
string
The path to patch to.
opts
keyword
default:"[]"
Options:
  • :replace - Replace browser history (default: false)

Utility Commands

exec/1

Executes JS commands located in an element’s attribute.
JS.exec("phx-remove", to: "#modal")
attr
string
The attribute containing the JS command.
opts
keyword
default:"[]"
Options:
  • :to - DOM selector (default: interacted element)
Example:
<div id="modal" phx-remove={JS.hide("#modal")}>...</div>
<button phx-click={JS.exec("phx-remove", to: "#modal")}>close</button>

concat/2

Combines two JS commands, appending the second to the first.
JS.concat(JS.push("save"), JS.hide(to: "#modal"))
first
JS.t()
First JS command.
second
JS.t()
Second JS command.
return
JS.t()
Combined JS command.

DOM Selectors

All commands accept a :to option with these formats:

String Selectors

JS.show(to: "#modal")
JS.show(to: ".notification")
JS.show(to: "body a:nth-child(2)")

Scoped Selectors

JS.show(to: {:inner, ".menu"})
JS.show(to: {:closest, ".container"})
scope
atom
Scope type:
  • :inner - Target element within the interacted element
  • :closest - Target closest element upwards from interacted element
  • :document - Document-level selector (equivalent to string)
Example:
<div phx-click={JS.show(to: {:inner, ".menu"})}>
  <div>Open me</div>
  <div class="menu hidden">
    I'm in the dropdown menu
  </div>
</div>

Examples

def hide_modal(js \\ %JS{}) do
  js
  |> JS.hide(transition: "fade-out", to: "#modal")
  |> JS.hide(transition: "fade-out-scale", to: "#modal-content")
end

def modal(assigns) do
  ~H"""
  <div id="modal" phx-remove={hide_modal()}>
    <div id="modal-content" phx-click-away={hide_modal()}>
      <button phx-click={hide_modal()}></button>
      {@text}
    </div>
  </div>
  """
end

Custom Clipboard Event

// In app.js
window.addEventListener("my_app:clipcopy", (event) => {
  if ("clipboard" in navigator) {
    const text = event.target.textContent;
    navigator.clipboard.writeText(text);
  }
});
<button phx-click={JS.dispatch("my_app:clipcopy", to: "#code")}>
  Copy content
</button>

Loading States

<button phx-click={JS.push("save", loading: ".form")}>
  Save
</button>

Build docs developers (and LLMs) love