Events API

Events and Methods covers the everyday workflow — click a button, respond to a collision. This is the reference layer underneath: the complete event catalog, exactly what each event object carries, and how bubbling and compilation actually work.

The full event catalog

Every node gets the same base set of attachable events — not just the ones you'd expect from a mouse:

EventFires when
`click`Pointer click on the node
`dblclick`Double-click
`mousedown` / `mouseup`Pointer button pressed / released on the node
`mouseenter` / `mouseleave`Pointer enters / leaves the node's bounds
`pointerdown` / `pointerup`Same as mousedown/up, under the pointer-event family
`pointerenter` / `pointerleave`Same as mouseenter/leave, under the pointer-event family
`tick`Fires every frame during play — the event-driven counterpart to reading time/frame/dt reactively in a formula

collide is added to this list specifically when a node has the Physics module attached — it isn't offered on a node that doesn't collide with anything. Some node types add their own: a List node, for instance, contributes an itemClick event on top of the common set. A component definition can also declare its own custom events, which then show up in the "Add Event" dropdown for every instance of that component, the same way a built-in event would.

One nuance worth being precise about: mousemove and pointermove genuinely exist and bubble through the node hierarchy internally, but they aren't in the catalog that populates the panel's "Add Event" dropdown — so while the mechanism handles them, they're not something you attach your own handler to from the properties panel today.

Event objects

Mouse and pointer events carry:

event.target    // VNode — the node that was interacted with
event.x         // number — pointer X in world coordinates
event.y         // number — pointer Y in world coordinates
event.offsetX   // number — pointer X relative to the node
event.offsetY   // number — pointer Y relative to the node
event.button    // number — 0 = left, 1 = middle, 2 = right
event.ctrlKey   // boolean
event.shiftKey  // boolean

offsetX/offsetY are lazy — they're only computed if your handler code actually reads them, since deriving a node-relative offset requires inverting that node's transform matrix, work that's skipped entirely when you don't need it.

The collide event carries a different, physics-specific shape:

event.target          // VNode — the other node in the collision
event.targetId        // string — name of the other node
event.collider        // string — name of this node's collider
event.targetCollider   // string — name of the other node's collider
event.normal           // {x, y} — contact surface normal
event.point             // {x, y} — contact point in world coordinates

Autocomplete is context-aware: type event. inside a handler, and the suggestions shown depend on which event type that handler is actually attached to — a click handler sees the mouse-event shape, a collide handler sees the collision shape, not a generic merged list of every possible field.

Writing a handler

In the properties panel: select a node, open its Events section, click Add Event, choose the event type, and write the handler body directly in the code editor. Inside that body, this refers to the node the handler is attached to, and event carries whatever that event type provides.

// Simple: rotate on click
this.rotation += 15

// Conditional: react only to a specific kind of collision
if (event.target.is("bullet")) {
  this.destroy()
}

// Stateful: track clicks across calls
this.clickCount = (this.clickCount || 0) + 1
if (this.clickCount >= 3) {
  this.destroy()
}

Calling methods from events

A method is a named function defined on a node — like an event handler, but callable on demand rather than tied to a trigger. Define one the same way (switch the panel's action dropdown from an event type to Methods, name it, write the body), then call it from any event handler:

this.reset()      // call a method on the node itself
Ball.launch()     // call a method defined on a different node

Methods can technically be called from a formula too, but that's a side effect inside something meant to be a pure value — events are where method calls belong.

Bubbling

Mouse and pointer events bubble through the node hierarchy the same way DOM events do — click on a child inside a frame, and the frame's own click handler fires too, walking from the deepest node up through each parent frame to the root. Call event.stopPropagation() inside a handler to stop that walk at the current node.

// On a child node — absorb the click, never let it reach the parent frame
event.stopPropagation()
this.opacity = 0.5

Compilation

Event handlers compile through the same pipeline as formulas, in the event compile mode: parsed as a full statement block, node references rewritten to ctx.resolve(), method calls rewritten to ctx.callMethod(), and the same inline VLQ source maps attached so a handler is debuggable in DevTools exactly like a formula is. A compile error logs to the console with line and column information, and — importantly — if a handler fails to compile, whatever handler was previously working stays in place. Broken code never silently replaces working code.

Code editor shortcuts

ShortcutAction
Tab / Shift+TabIndent / dedent (multi-line aware)
Ctrl+DDuplicate line
Ctrl+/Toggle comment
Ctrl+Shift+KDelete line
Ctrl+EnterCommit and close
Shift+EnterApply without closing
Ctrl+FSearch

For the day-to-day story - why you'd reach for an event vs. a formula, and worked examples - see Events and methods. For the compiler internals these handlers share with formulas, see Formula compiler and runtime Internals.

On this page

Formo

The browser studio where drawings become animations, toys, and games. Your files, open HTML export, nothing to install.