LocalLiveView brings local-first interactivity to Phoenix applications by running your Elixir code directly in the browser — using the same Phoenix LiveView API you already know.
What is LocalLiveView?
In a standard Phoenix LiveView app, UI state lives on the server. Every interaction travels over the network: user clicks button → server processes event → server sends diff → browser updates DOM. This works great, but it means latency is always in the loop.
LocalLiveView moves that state into the browser itself. Your Elixir modules are compiled to WebAssembly and executed via Popcorn, which runs AtomVM — a tiny Erlang virtual machine — directly in the browser. Events are handled locally, renders happen instantly, and the server is only involved when you explicitly want it to be.
The result: zero-latency UI, offline capability, and a familiar Elixir/LiveView programming model.
How it works
Server Browser
┌───────────────────────────────┐ ┌──────────────────────────────┐
│ Phoenix LiveView (host) │ │ Popcorn (AtomVM WASM) │
│ │ │ │
│ render/1 │ │ MyLocal │
│ <.local_live_view │ │ mount/3 │
│ view="MyLocal" │─── assigns ─▶│ render/1 │
│ items={@items} /> │ │ update/2 │
│ │ │ │
│ │ │ handle_event/3 │
│ handle_event/3 │◀─── event ───│ push_server_event/3 │
│ │ │ │
│ Mirror.MyLocal.handle_sync/3 │◀─── sync ────│ mirror_sync/2 │
│ (optional) │ │ │
└───────────────────────────────┘ └──────────────────────────────┘- Your
local/project is compiled to a.avmWASM bundle at build time. - A server-side LiveView renders the mount point with
<.local_live_view view="MyLocal" />. Every attribute other thanviewis passed down as assigns and delivered to the local view'supdate/2callback — so the host stays in control of the data, exactly as withlive_component/1. - When the page loads, Popcorn starts the WASM runtime and mounts your LocalLiveViews.
- User interactions are handled in the browser — no round-trip to the server.
- A local view can push events back to its host with
push_server_event/3, which arrives at the host LiveView'shandle_event/3. Handling the event locally first and pushing afterwards gives you optimistic updates, with the server's authoritative state arriving as the nextupdate/2. - Optionally, you can sync selected assigns to a server-side mirror module via
mirror_sync/2, letting other users' LiveViews react to local state changes.
Key concepts
LocalLiveView module — An Elixir module that uses use LocalLiveView and implements mount/3, render/1, and optionally handle_event/3. Lives in the local/ project (compiled to WASM).
local/ project — A separate Mix project inside your Phoenix app, built via mix llv.build. The local/ project depends on :local_live_view and contains all your client-side Elixir code.
<.local_live_view> — A Phoenix component that renders the mount point for a LocalLiveView. Generated by mix llv.install and available as import LocalLiveView.Component in your web module.
Mirror — An optional server-side module (Mirror.MyLocal) that receives synced assigns from a LocalLiveView. Useful for broadcasting state changes to other users or persisting data.
Relationship to Phoenix LiveView
If you know Phoenix LiveView, you already know LocalLiveView. The callbacks (mount/3, render/1 with ~H, handle_event/3), the assign functions (assign/2, update/3) and the template bindings (phx-click and friends) are all the same — LocalLiveView intercepts Phoenix LiveView's standard JavaScript layer, so event attributes work exactly the same way.
The main differences between LLV and standard LiveView are that LLV uses use LocalLiveView instead of use Phoenix.LiveView, relies on the update/3 callback to update its assigns, and runs directly in the browser's Popcorn runtime rather than on the server.
LLV can also render LiveComponents and function components natively. These will similarly execute as part of the browser's Popcorn runtime.