A single, stackable modal container for Phoenix LiveView. One stack per page — only one modal is visible at a time — built on a struct in assigns + a function component + a LiveView hook (no live_component, no hidden process).

Installation

def deps do
  [{:modal_stack, "~> 0.1"}]
end

Tailwind

The default chrome ships Tailwind utility classes, so your app must include the package in its Tailwind content scan. For Tailwind v4, add to your app.css:

@source "../../deps/modal_stack/lib";

(Adjust the relative path to point at deps/modal_stack/lib from your CSS file.)

Usage

def mount(_params, _session, socket) do
  {:ok, ModalStack.attach(socket)}
end

def handle_event("delete", _p, socket), do: {:noreply, ModalStack.push(socket, :confirm_delete)}
<ModalStack.modal_stack stack={@modal_stack}>
  <:modal name={:confirm_delete} on_cancel={JS.push("delete_cancelled")}>
    <p>Are you sure?</p>
    <.button phx-click="really_delete">Delete</.button>
  </:modal>
</ModalStack.modal_stack>

The close button, Escape, and click-away pop the stack automatically — no close handler needed. on_cancel is an optional extra side-effect.

API

  • ModalStack.attach(socket) — seed state + hook (call in mount/3)
  • ModalStack.push(socket, name) — open a modal (atom name; dedups)
  • ModalStack.pop(socket) — close the topmost
  • ModalStack.clear(socket) — close all