Properties
Every value on a node has a type. The type determines how it serializes, interpolates, and displays.
Try it
title: "Property types in action"
description: "A shape with number, color, angle, and ratio properties"
nodes:
- type: ELLIPSE, name: "Demo", x: 200, y: 200, w: 120, h: 120, fill: "#3498DB"
formulas:
- node: "Demo", prop: "rotation", value: "time * 30"
- node: "Demo", prop: "opacity", value: "0.5 + Math.sin(time) * 0.5"
tools_visible: [select, play]
ui_visible: [properties, formulas]The 12 built-in types
| Type | Examples | Widget | Serialization |
|---|---|---|---|
number | x, y, width, height | NumberInput with drag-scrub | JSON number |
integer | point count, vertex count | NumberInput (integer step) | JSON integer |
angle | rotation | NumberInput with degree symbol | JSON number (degrees) |
ratio | opacity, friction | NumberInput 0-1 range | JSON number |
color | individual colors | ColorPicker | {r, g, b, a} |
point | origin, velocity | X/Y number pair | {x, y} |
paint | single fill or stroke | Paint editor popup | {type, color, gradient, ...} |
paint[] | fills, strokes | Multi-paint stack editor | Array of paint objects |
enum | blend mode, body type | Dropdown | JSON string |
blendMode | blendMode | Dropdown | JSON string |
boolean | fixedRotation, visible | Checkbox / Toggle | JSON boolean |
string | name, tag | Text input | JSON string |
Type operations
Every type implements these operations:
| Operation | What it does | Used by |
|---|---|---|
serialize(value) | Convert to JSON-safe format | Save/load, clipboard |
deserialize(data) | Convert from JSON | Load, paste |
equals(a, b) | Deep equality check | Undo change detection |
clone(value) | Deep copy | Undo snapshots, multi-select |
lerp(a, b, t) | Interpolate between values | Animation, transitions |
applyDelta(value, delta) | Apply relative change | Multi-select drag-scrub |
Property descriptors
Each property on a node is defined by a PropertyDescriptor:
{
key: "rotation", // internal property key
label: "Rotation", // display label in panel
type: "angle", // type from the registry
group: "transform", // which panel section
constraints: {
min: -360,
max: 360,
step: 0.1
},
afterSet: (node, value) => {
node.recomputeTransform()
},
gizmo: "rotationGizmo" // linked on-canvas gizmo
}Constraints
| Constraint | Description |
|---|---|
min | Minimum value |
max | Maximum value |
step | Increment step for drag-scrub |
enumValues | Allowed values for enum type |
afterSet hooks
When a property changes, its afterSet hook runs. Typical uses:
recomputeTransform()after x/y/rotation/width/height changesinitGeometry()after shape parameters change (star points, ellipse arc)rebuild()physics collider after geometry changes
Property groups
Properties are organized into collapsible groups in the panel:
| Group | Properties |
|---|---|
| Transform | x, y, width, height, rotation |
| Appearance | opacity, blendMode, fills, strokes |
| Shape | (type-specific: cornerRadius, points, innerRadius, arc params) |
| Physics | bodyType, density, friction, restitution, fixedRotation, fixX, fixY |
| Animation | clip, speed, loop |
Formula binding
Any property that accepts a number, angle, or ratio can be bound to a formula.
In the properties panel:
- Click the number field
- Type
=followed by an expression - Press Enter
The field shows a green fx badge. The formula evaluates reactively.
// Formula-bound propertiesrotation = time * 45 // spinsx = mouse.x // follows cursoropacity = Tag.enemy.length > 0 ? 1 : 0.3 // dims when no enemies
Paint type deep dive
Paint is the most complex built-in type.
Paint object structure
{
type: "solid" | "linearGradient" | "radialGradient" | "angularGradient" | "diamondGradient",
color: { r: 0-255, g: 0-255, b: 0-255, a: 0-1 }, // solid only
gradient: {
stops: [{ offset: 0-1, color: {r, g, b, a} }, ...],
transform: [a, b, c, d, e, f] // 2x3 affine matrix
}
}Formula-bindable paint sub-properties
Access any sub-property of a paint in formulas using bracket notation:
this["fills[0].color.r"] = mouse.x // red channel follows cursorthis["fills[0].color.h"] = time * 360 // hue rotates (HSV)this["fills[0].gradient.stops[0].offset"] = mouse.x / 400 // gradient stop position
Supported color spaces: RGB, HSV, LAB, OKLCH.
Multi-select behavior
When multiple nodes are selected:
- Same value → shown normally
- Mixed values → shown with mixed indicator
- Drag-scrub → applies relative delta to all selected nodes
- Paint editing → deep clones per node to prevent shared-reference bugs