Use Canvas for diagrams, gauges, game-specific maps, and effects that standard widgets cannot express clearly. Prefer Text, ProgressBar, Table, and the form controls when they already provide the required structure or interaction.
A scene is an array of shape records. Shapes are painted in array order from back to front, except that canvas text is painted after non-text shapes.
import { createState } from "smudgy:core"; import { Canvas, createWidget } from "smudgy:widgets"; import type { CanvasShape } from "smudgy:widgets"; const hud = createState<{ scene: CanvasShape[] }>("manual-canvas"); hud.set({ scene: [ { kind: "rect", x: 4, y: 18, width: 92, height: 12, rx: 3, fill: { gradient: { from: [4, 0], to: [96, 0], stops: [[0, "#7a1f1f"], [1, "#d64541"]], }, }, }, { kind: "circle", id: "warning-pulse", cx: 92, cy: 24, r: 4, stroke: { color: "#ffdd55", width: 2 }, animate: { r: { from: 4, to: 10, duration: 800, ease: "in-out", repeat: "infinite" }, }, }, { kind: "text", x: 4, y: 4, text: "Health", size: 11, color: "#ffffff" }, ], }); createWidget("status-canvas", ( <Canvas width="fill" height={160} view_box={[0, 0, 100, 50]} fit="contain" scene={hud.bind("scene")} /> ));
The available records include rectangles, circles, ellipses, lines, polylines, polygons, SVG-style paths, text, and transformed groups. A fill may be a CSS color or a linear gradient. A stroke may specify color, width, and a dash pattern.
Bind scene to shared state when the number, order, or fields of the shapes will change. Writing a new array to that state repaints the existing canvas. This differs from Table, whose row membership is fixed until the widget is mounted again.
Do not mutate an old scene array in place and assume that the UI observed it. Assign a new scene value through the state handle.
An animate block declares the destination, duration, delay, easing, and repeat behavior for supported fields. Smudgy calculates the intermediate frames.
Give every animated shape an id unique within the scene. If a later scene value contains the same ID and animation specification, the animation continues from its current position. Changing the specification restarts it. Without an ID, reordering shapes may restart an animation because position becomes its identity.
Set transient: true for a one-shot effect that should disappear when all its animations finish. Remove that record from the next scene value as well; later writes containing the same transient record do not make it visible again.
Without view_box, scene coordinates are pixels. With view_box, Smudgy maps the declared scene rectangle into the component:
fit=“fill” stretches each axis to the available bounds.fit=“contain” preserves the aspect ratio, centers the scene, and may leave empty margins.Everything is clipped to the canvas bounds, including strokes and expanding animations. Size the component for the largest state it must display.
Add onPointer only when the canvas is interactive. The callback receives down, move, and up events in scene coordinates. Move events are sent while a button is held. A release is reported even when it occurs outside the component.
A display-only canvas without onPointer does not intercept pointer input intended for content behind it. With fit=“contain”, an event in an empty margin can have coordinates outside the view-box rectangle; check bounds before treating it as a hit.
Smudgy validates the whole scene before displaying it. Excessive shape counts or nesting, overlong paths or text, and duplicate animation IDs cause the update to be rejected. The previous valid scene remains visible, and the script error identifies the rejected input. Correct the scene rather than repeatedly submitting the same value.
Reference: Canvas API.