This guide builds a note board in a Phoenix LiveView app.
It covers the first-use path only:
- install the Elixir package and browser runtime;
- define one item-content LiveView;
- author one canvas item in HEEx;
- render the canvas runtime;
- add drag, resize, add, focus, and remove behavior.
Graph connectors, drawing tools, shared canvases, persistence, history, and custom render loops come later.
Install LivePanels
Add the package:
# mix.exs
{:livepanels, "~> 0.1.0"}Fetch dependencies:
mix deps.get
Install the browser package into your Phoenix assets:
mix livepanels.install
cd assets && npm install
Import the browser hooks and runtime CSS beside your existing LiveView setup:
import { Socket } from "phoenix";
import { LiveSocket } from "phoenix_live_view";
import { LivePanelsHooks } from "livepanels";
import "livepanels/runtime.css";
const hooks = {
...LivePanelsHooks
};
const liveSocket = new LiveSocket("/live", Socket, { hooks });If your Phoenix app imports CSS from assets/css/app.css instead of from
JavaScript, put the CSS import there:
@import "livepanels/runtime.css";Generate Or Write The First Canvas
The fastest route is the generator:
mix livepanels.new canvas --yes
That creates a working route LiveView at
lib/<app>_web/live/canvas_live.ex. It is a good first file to inspect because
it contains the same pieces this guide explains manually.
If you are wiring by hand, start with a normal Phoenix route:
scope "/", MyAppWeb do
pipe_through :browser
live "/board", BoardLive
endDefine The Canvas LiveView
The canvas LiveView is an ordinary LiveView with LivePanels.Canvas attached.
Declare initial panels directly inside canvas_runtime/1:
defmodule MyAppWeb.BoardLive do
use MyAppWeb, :live_view
use LivePanels.Canvas,
pubsub: MyApp.PubSub
@impl true
def mount(_params, _session, socket) do
{:ok, socket}
end
@impl true
def render(assigns) do
~H"""
<main class="board-page">
<.canvas_runtime {canvas_runtime_attrs(assigns, class: "board-canvas")}>
<:item :let={context} id="welcome-note" at={{80, 80}} size={{640, 420}} resize>
<section class={"board-item #{if context.selected?, do: "is-selected"}"}>
<header {drag_handle_attrs(class: "board-item__title")}>
<span>Welcome note</span>
</header>
<div class="board-item__body">
<article class="note-content">
<p>This content lives directly inside the item declaration.</p>
</article>
</div>
<.resize_handles :if={context.view.resizable?} />
</section>
</:item>
</.canvas_runtime>
</main>
"""
end
endThe important parts are:
use LivePanels.Canvasattaches the canvas runtime to the LiveView;<:item>declares an item from markup;id,at, andsizedeclare the item identity and initial frame;canvas_runtime_attrs(assigns, ...)passes runtime assigns to the component;canvas_runtime/1renders the canvas root, layers, and item loop;drag_handle_attrs/1andresize_handles/1connect your markup to the runtime protocol.
Add Enough CSS To See It
LivePanels ships structural runtime CSS, not your app's item design. Add application styles that fit your app. A starter board can begin with:
.board-page {
block-size: 100vh;
display: grid;
grid-template-rows: auto 1fr;
}
.board-toolbar {
display: flex;
gap: 0.5rem;
padding: 0.75rem;
border-bottom: 1px solid #d7dde7;
}
.board-canvas {
min-block-size: 0;
background: #f7f9fc;
}
.board-item {
height: 100%;
overflow: hidden;
border: 1px solid #c7d2e0;
border-radius: 6px;
background: white;
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.08);
}
.board-item__title {
display: flex;
justify-content: space-between;
align-items: center;
gap: 0.75rem;
padding: 0.5rem 0.75rem;
border-bottom: 1px solid #e2e8f0;
cursor: grab;
}
.board-item__body {
padding: 0.75rem;
}Try The First Interactions
Start the Phoenix server and visit the route:
mix phx.server
You should be able to:
- click
New noteto add an item; - drag the titlebar to move the item;
- resize from the handles;
- click the item to focus it;
- remove it with the app-rendered close button.
If those interactions do not work, check these in order:
- the LivePanels browser hooks are included in
LiveSocket; runtime.cssis imported;- the page renders
canvas_runtime_attrs(assigns, ...); - controls use LivePanels attrs instead of hand-written
phx-clickpayloads; mix livepanels.checkpasses.
What To Read Next
Use Find The API You Need when you know the thing you want to do and need the
right module. Use Add Items when you want to understand item declarations and
placement. Use Render Item Content With LiveViews when item content needs to
communicate with the canvas.