Drawing support lets users create committed canvas vector items: freehand strokes, polylines, arrows, lines, rectangles, ellipses, diamonds, triangles, and text.
This guide is the short integration path. For a full drawing-app track, read
Drawing Apps With LivePanels
after this guide.
The browser owns in-progress drafts so the pointer feels immediate. When the gesture is finished, the browser sends one drawing command. The server validates the payload, creates a canvas item, and projects it like other canvas state.
That gives vector items normal canvas behavior: move, resize, select, remove, persist, undo, redo, copy, paste, and share.
Use The Drawing Toolkit
Import the drawing toolkit on drawing-capable canvases:
defmodule MyAppWeb.SketchLive do
use MyAppWeb, :live_view
use LivePanels.Canvas,
pubsub: MyApp.PubSub,
domains: [:drawing]
use LivePanels.Toolkit.Drawing
endThe drawing preset includes canvas helpers plus drawing controls, style JS helpers, vector item rendering helpers, erase attrs, and point-edit attrs.
Add Drawing Mode Controls
Use set_cursor_mode_js/3 to switch the browser cursor mode:
<button
phx-click={
set_cursor_mode_js(@livepanels.canvas_id, :draw,
tool: :freehand,
style: %{
color: "#1f2937",
width: 3,
fill_color: "none",
stroke_style: "solid",
opacity: 100,
roundness: 0
}
)
}
>
Freehand
</button>
<button
phx-click={
set_cursor_mode_js(@livepanels.canvas_id, :draw,
tool: :rectangle,
style: %{
color: "#1f2937",
width: 2,
fill_color: "#dbeafe",
stroke_style: "solid",
opacity: 100,
roundness: 8
}
)
}
>
Rectangle
</button>
<button phx-click={set_cursor_mode_js(@livepanels.canvas_id, :erase)}>
Erase
</button>If the application mirrors cursor mode in LiveView assigns, pass the mode back into the runtime component:
<.canvas_runtime {canvas_runtime_attrs(assigns, cursor_mode: @cursor_mode)} />The browser still owns the active draft. The application mirror is for visible toolbar state and LiveView patch consistency.
Update Drawing Style
Use set_draw_style_js/3 to change browser-owned draft style without changing
mode:
<button
phx-click={
set_draw_style_js(@livepanels.canvas_id, %{
color: "#dc2626",
width: 4,
fill_color: "none",
stroke_style: "dashed",
opacity: 100,
roundness: 0
})
}
>
Red dashed
</button>For committed vector items, use update_style_attrs/3:
<button
:if={@selected_vector_item}
{update_style_attrs(@selected_vector_item, %{
color: "#2563eb",
width: 3,
fill_color: "none",
stroke_style: "solid",
opacity: 100,
roundness: 0
})}
>
Blue
</button>Draft style and committed vector item style are separate because a draft is not canvas state until it is committed.
Create Vector Items From Server Code
Use LivePanels.Drawing.vector_item/4 for server-authored vector items, imports, tests, and
derived annotations:
socket =
LivePanels.Drawing.vector_item!(
socket,
:line,
%{
space: :world,
anchor: :top_left,
x: 120,
y: 80,
w: 240,
h: 160
},
points: [%{x: 0, y: 0}, %{x: 240, y: 160}],
style: %{
color: "#444444",
width: 3,
fill_color: "none",
stroke_style: "solid",
opacity: 100,
roundness: 0
}
)Text vector items use the same item placement model:
socket =
LivePanels.Drawing.vector_item!(socket, :text, placement,
text: "Review this",
style: %{
color: "#111827",
width: 1,
fill_color: "none",
stroke_style: "solid",
opacity: 100,
roundness: 0
}
)The command creates an item with vector state. It does not append SVG directly to the DOM.
Committed vector items set interaction: %{promote_on_focus: false}. Selecting
or focusing a vector item keeps the canvas stack order intact, so drawing layers
can coexist with graph nodes and LiveView-backed items. Use normal z-order
selection commands when a user chooses to move vector items forward or back.
Render Committed Vector Items
The standard canvas_runtime/1 renders committed vector items in the world
vector layer. The :item slot can stay focused on HTML item frames:
<.canvas_runtime {canvas_runtime_attrs(assigns)}>
<:item :let={context}>
<section class="item-frame">
<header {drag_handle_attrs()}>{context.item.label}</header>
{context.content}
<.resize_handles :if={context.view.resizable?} />
</section>
</:item>
</.canvas_runtime>For manual render loops, use the manual render helpers:
<LivePanels.Toolkit.Integration.Render.svg_vector_item
id={"vector-item-#{context.item.id}"}
item={context.item}
selected={context.selected?}
active={context.focused?}
/>Applications can also render vector items manually from LivePanels.Drawing.vector_state/1
when they need a custom SVG contract.
Edit Points And Remove Vector Items
Point-backed vector items such as freehand, polyline, arrow, and line store points in vector state. Render point handles when selected:
<circle
:for={{point, index} <- Enum.with_index(points)}
cx={point.x}
cy={point.y}
r="5"
{vector_point_handle_attrs(item, index)}
/>Use erase target attrs on the SVG geometry you want erase mode to recognize:
<path
d={path}
{erase_vector_item_target_attrs(item)}
/>Or render a normal removal control:
<button {erase_attrs(item)}>Remove vector item</button>The server verifies that the target is a committed vector item before removing it.
Flatten Dense Vector Item Sets
For dense drawing workflows, flatten committed vector items into one canvas item:
socket = LivePanels.Drawing.flatten!(socket, selected_vector_item_ids)The flattened item renders as one unit. Its vector state keeps each source vector item as a serialized source item record in the flattened item's local coordinate space, so the runtime can act on the flattened item as one object until it is restored:
socket = LivePanels.Drawing.unflatten!(socket, flattened_vector_item)Applications can also run threshold-based flattening after commits or on their own schedule:
socket =
LivePanels.Drawing.flatten_over_limit!(socket, 250,
scope: :viewport
)Supported scopes are :all, :owner, :viewport, and :selection.
Add Content With Normal Items
Drawing tools create vector items. For screenshots, remote references, nested panels, dashboards, or app-specific content, declare a normal item type and use the standard item command path:
<button {add_item_attrs(:reference)}>
Reference
</button>That item can still be selected, moved, resized, connected with ports, or given vector capability state when the application wants it to participate in drawing workflows. The drawing toolkit does not create a separate content-item path.
Persistence, History, Clipboard, And Shared Mode
Committed vector items are canvas items. Because of that:
- moving and resizing vector items uses normal item commands;
- selection can include vector items;
- history can undo vector item creation and edits;
- layouts can restore vector items;
- clipboard can copy and paste vector items;
- shared canvases can coordinate committed vector item changes.
Browser drafts are intentionally not canonical state. In shared canvases the
runtime can broadcast throttled draw intent for live peer previews, while the
accepted vector_item command remains the source of durable canvas state. Pending
draw commits are kept client-side until the command ack resolves, with a
runtime option for app-provided draft persistence.
What LivePanels Owns
LivePanels owns drawing command validation, vector item state, committed vector item creation, draft-to-command protocol, drawing attrs, neutral vector item rendering helpers, persistence/history participation, and shared coordination.
Your application owns the toolbar, palette, selected-vector-item inspector, SVG styling, product labels, and any domain data attached to drawings.