Phoenix LiveView · headless canvas runtime

Give your LiveView app an infinite canvas.

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
board_live.ex
# 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

The canvas is just your LiveView.

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.

Renata Jules

Batteries included

The hard parts of a canvas, already solved.

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.

Drag & resize

Pointer handling, live previews, and snap constraints out of the box — with hooks to lock axes or quantize to a grid.

drag_handle_attrs · resize_handles

Undo & redo

Every move, resize, add, and remove is a command on a real history stack. Wire a button to undo_attrs() and you're done.

Canvas.History · undo_attrs

Copy, paste & clipboard

Duplicate selections, paste across the canvas, and keep cross-tab clipboard state consistent without touching localStorage.

Canvas.Clipboard

Selection & marquee

Click, shift-click, and drag-to-select across many items, with focus promotion you can drive from product rules.

Canvas.Selection

Zoom, pan & focus

Smooth camera controls and a focusable viewport, plus a drop-in minimap for canvases that grow past the screen.

mountLivePanelsMinimaps

Auto-layout

Thirteen arrangement strategies — grid, masonry, cascade, BSP, treemap — and the layout persists between sessions.

Canvas.Arrange.layout/3

Graph connectors

Ports, connectors, automatic routing, reconnect, and hit-testing for node-and-wire editors of any shape.

Toolkit.Graph

Freehand & shapes

Commit freehand strokes and shapes as first-class vector items you can select, move, restyle, and edit point by point.

Toolkit.Drawing

Headless by design

It runs the canvas. You own every pixel.

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.

  • Style with Tailwind, vanilla CSS, CSS modules, or DaisyUI — your call.
  • Your markup stays your markup. Behavior lives on small attr helpers.
  • Items can host full LiveViews — even a canvas inside a canvas.
  • Gate any mutation with a server-side policy before it ever lands.
your own styling
# 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

Wire nodes together without writing a routing engine.

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 guide

Drawing toolkit

Sketch and annotate, with strokes that become real items.

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 guide

Designed by you

The runtime moves it. The markup is yours.

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.

  • drag_handle_attrs/1 makes any element a drag handle
  • add_item_attrs/2 spawns runtime items from a button
  • items can host full LiveViews — even nested canvases
# 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

Start with the preset that matches the build.

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

Canvas

Use when you need item frames and canvas controls.
  • runtime root
  • drag + resize handles
  • selection
  • history + clipboard
  • layout
  • keyboard scope

Toolkit.Graph

Graph

Use when nodes have ports and connectors.
  • everything in Canvas
  • connectors
  • routing
  • port hit-testing
  • marquee
  • graph navigation

Toolkit.Drawing

Drawing

Use when you commit freehand or shape vectors.
  • everything in Canvas
  • draw tools
  • vector items
  • stroke + shape styles
  • point editing
  • committed to state

Shared mode

Single-player today, multiplayer with one option.

Set mode: :shared and the exact same commands fan out to everyone in the room — presence, recovery, and layout sync included. Start local; graduate to a clustered adapter when you outgrow a single node.

AMJSRK+6