Drag & resize
Pointer handling, live previews, and snap constraints out of the box — with hooks to lock axes or quantize to a grid.
Phoenix LiveView · headless canvas runtime
Draggable panels, graph connectors, freehand drawing, and live collaboration — driven by your Elixir server and styled entirely by you. No client-side framework, no second source of truth.
$ mix livepanels.install
$ mix livepanels.new canvas --yes
$ mix phx.server # → /canvas
# a draggable, resizable note — that's the whole component
<.canvas_runtime {canvas_runtime_attrs(assigns)}>
<:item :let={ctx} id="note"
at={{80, 80}} size={{420, 280}} resize>
<article class="note">
<header {drag_handle_attrs()}>Sticky note</header>
<textarea>Move me. The server remembers.</textarea>
<.resize_handles :if={ctx.view.resizable?} />
</article>
</:item>
</.canvas_runtime>
Server-authoritative
Item positions, layout, and history live next to the data they belong to, on the server you already trust. There's no client store to reconcile, no optimistic state to untangle, and no extra API to keep in sync. When the socket reconnects, the canvas is exactly where it was.
Batteries included
Every spatial app needs the same plumbing — drag, undo, selection, sync. LivePanels ships it, so you spend your time on the product, not the pointer math.
Pointer handling, live previews, and snap constraints out of the box — with hooks to lock axes or quantize to a grid.
Every move, resize, add, and remove is a command on a real history stack. Wire a button to undo_attrs() and you're done.
Duplicate selections, paste across the canvas, and keep cross-tab clipboard state consistent without touching localStorage.
Click, shift-click, and drag-to-select across many items, with focus promotion you can drive from product rules.
Smooth camera controls and a focusable viewport, plus a drop-in minimap for canvases that grow past the screen.
Thirteen arrangement strategies — grid, masonry, cascade, BSP, treemap — and the layout persists between sessions.
Ports, connectors, automatic routing, reconnect, and hit-testing for node-and-wire editors of any shape.
Commit freehand strokes and shapes as first-class vector items you can select, move, restyle, and edit point by point.
Headless by design
LivePanels ships no theme, no titlebars, no buttons you'd have to fight later. You write HEEx and your own CSS; the runtime just makes it move.
# the runtime owns behavior — the look is 100% yours
<:item :let={ctx}>
<section class="rounded-xl shadow ring-1 ring-black/5">
<header {drag_handle_attrs(class: "px-3 py-2 bg-teal-700")}>
{ctx.item.label}
</header>
<div class="p-4">{render_slot(@inner_block)}</div>
<.resize_handles :if={ctx.view.resizable?} />
</section>
</:item>
Graph toolkit
Ports, connectors, automatic routing, reconnect, marquee, and graph navigation come with the toolkit. You render the node cards and style the ports — the math underneath is handled.
Read the graph guideDrawing toolkit
Freehand and shapes commit straight into canvas state as vector items — selectable, movable, restyleable, and editable point by point. The same undo, clipboard, and shared sync apply.
Read the drawing guideDesigned by you
There's no theme to override and no component library to escape. Drop behavior onto your own elements with small attr helpers, then style it however your app already does — Tailwind, vanilla CSS, DaisyUI, anything.
# your toolbar button creates a real item
<button {add_item_attrs("note",
id: "note-{System.unique_integer()}",
x: 160, y: 120, w: 320, h: 220,
props: %{body: "New idea"})}>
New note
</button>
Toolkits
Each toolkit is a use preset over the same canvas core. Import the one your route renders; compose more as the product grows.
Toolkit.Canvas
Toolkit.Graph
Toolkit.Drawing