Item content is what appears inside a canvas item frame.
The item owns runtime behavior: identity, placement, visibility, resize rules, selection, ports, persistence, and shared coordination. The content owns the UI inside the frame.
For many apps, item content is another LiveView.
Define A Content LiveView
Use LivePanels.Item.Content.LiveView for a LiveView that will run inside an item:
defmodule MyAppWeb.NoteLive do
use MyAppWeb, :live_view
use LivePanels.Item.Content.LiveView,
pubsub: MyApp.PubSub
@impl true
def mount_item(socket, props) do
{:ok,
socket
|> assign(:body, LivePanels.Item.prop(props, :body, ""))
|> assign(:saved?, true)}
end
@impl true
def render(assigns) do
~H"""
<form phx-submit="save">
<textarea name="body" phx-change="edit">{@body}</textarea>
<button type="submit">Save</button>
</form>
"""
end
@impl true
def handle_event("edit", %{"body" => body}, socket) do
{:noreply, assign(socket, body: body, saved?: false)}
end
endmount_item/2 receives the item props from the canvas declaration or add
command. Use it to initialize content assigns.
Use The LiveView Content Adapter
Source-owned items declare the content LiveView directly on the item:
<:item
id="note-1"
type="note"
at={{120, 80}}
size={{520, 320}}
live_view={MyAppWeb.NoteLive}
props={%{body: "Start writing..."}}
>
</:item>Runtime-created items use the same content adapter in the command payload. The
application renders their frame and places the supervised content with
context.content:
<.canvas_runtime {canvas_runtime_attrs(assigns)}>
<:item :let={context}>
<article class="note-frame">
<header {drag_handle_attrs()}>{context.item.label}</header>
<section>{context.content}</section>
<.resize_handles :if={context.view.resizable?} />
</article>
</:item>
</.canvas_runtime>The content LiveView should not render the outer draggable frame. Keep the frame in the canvas item renderer so every item uses the same canvas behavior.
Send Commands From Content
Item content can ask the canvas to do item-scoped work through
LivePanels.Item.Content.LiveView.Commands.
For example, a note can remove itself:
def handle_event("remove", _params, socket) do
{:noreply, LivePanels.Item.Content.LiveView.Commands.remove(socket)}
endOr it can update its application item props:
def handle_event("save", %{"body" => body}, socket) do
socket =
socket
|> LivePanels.Item.Content.LiveView.Commands.merge_props(%{body: body})
|> assign(body: body, saved?: true)
{:noreply, socket}
endUse item-content helpers when the content wants to affect its own canvas item. Use canvas application commands when the application is coordinating multiple items or app state.
Message Between App And Content
Item content is a LiveView process. That matters for lifecycle and messaging.
The runtime tracks the child process while the item exists. When an item is removed, the runtime can notify and terminate the content. When a content process exits, the item can remain in the canvas while the content is vector itemed unavailable.
Applications can subscribe to item or canvas runtime topics when they need external coordination:
:ok = LivePanels.subscribe(socket, {:item, "note-1"})
:ok = LivePanels.subscribe(socket, :events)Prefer explicit commands for mutations. Use messages for domain events, status updates, or integration with the rest of your Phoenix app.
Use Non-LiveView Content When It Fits
Not every item needs a child LiveView.
LivePanels item declarations can also represent static rendered content, remote embeds, nested canvases, or custom renderer modules. Use a LiveView when the inside of the item needs LiveView lifecycle, events, assigns, subscriptions, or stateful UI.
Use simpler content when the item is mostly presentation or when the application already owns the state.
Keep The Boundary Clear
Good item content:
- renders the UI inside the item;
- handles its own form events and local state;
- uses item-content helpers for its own item commands;
- leaves placement, drag, resize, focus, selection, and z-order to the canvas.
Good application item frames:
- render the card/window/chrome around content;
- attach
drag_handle_attrs/1, resize handles, remove controls, and graph or drawing attrs; - read
context.viewinstead of reimplementing capability checks; - keep app-specific style and product language outside LivePanels internals.
That boundary is what lets the same item content work in local canvases, shared canvases, graph nodes, nested canvases, and persisted layouts.