Group shapes into reusable components with typed inputs, computed outputs, and encapsulated logic. Instances share the definition but each has its own state.
From shapes to building blocks
Select a group of shapes. Click “Create Component.” Now you have a reusable building block with a defined interface.
Button component
A reusable button with label, color, and hover state
Inputs and outputs
Inputs
Parameters that control the component from outside. Set by the parent scene or wired with formulas.
Component "Button"
Inputs:
label: string = "Click me"
color: Color = "#3498DB"
width: number = 200
disabled: boolean = false
Inside the component, formulas reference inputs with $input:
Background.color = if($input.disabled, "#999", $input.color)
Background.width = $input.width
Outputs
Computed values exposed to the parent. Read-only from outside.
Outputs:
isHovered: boolean
computedHeight: number
Parent formulas can read outputs:
StatusText.text = if(Button.isHovered, "Ready", "Waiting")
How it compares
| Aspect | Figma | Flash | Unity | Formo |
|---|---|---|---|---|
| Parameters | Variant props, text overrides | MovieClip vars | Inspector fields | Typed inputs, any type |
| Outputs | None | None | Public fields | Computed outputs |
| Internal logic | None | ActionScript | C# scripts | Reactive formulas |
| Export | Static assets | .swf | Game binary | React components |
| Event handling | None | MovieClip events | UnityEvent | Formula events |
Instances
Place a component — the editor creates an instance. Internal nodes are cloned from the definition. Each instance has independent state.
Change the definition — all instances update. Override an input on one instance — only that instance changes.
Scene
├── Button "Save" → label: "Save", color: "#2ECC71"
├── Button "Cancel" → label: "Cancel", color: "#E74C3C"
└── Button "Delete" → label: "Delete", color: "#E74C3C", disabled: true
Three buttons. One definition. Different inputs.
Non-visual components
Not all components draw to the canvas. Non-visual components are logic-only — like Delphi’s Timer or VB’s Winsock control. They appear in the scene tree with an icon but have no canvas representation.
Timer
Component "Timer" (non-visual)
Inputs: interval: number = 1000, active: boolean = false
Outputs: elapsed: number, tick: event
API Connection
Component "ApiConnection" (non-visual)
Inputs: url: string, method: string = "GET"
Outputs: data: any, loading: boolean, error: string
Actions: fetch()
State
Component "State" (non-visual)
Inputs: initialValue: any
Outputs: value: any
Actions: set(val), reset()
Wire non-visual outputs to visual components:
ApiConnection "api" → loading: boolean
Spinner component → visible = api.loading
DataTable component → data = api.data
Custom editors
Build custom inspector panels for your components. When a user selects an instance, the custom editor appears instead of the default property list.
Editor for "DataChart"
├── "Chart Type" → Dropdown bound to $target.input.chartType
├── "Primary Color" → ColorInput bound to $target.input.primaryColor
├── "Bar Width" → Slider bound to $target.input.barWidth
├── "Show Legend" → Checkbox bound to $target.input.showLegend
└── "Reset" → Button triggers resetDefaults($target)
Custom editors are themselves components — components all the way down. The default properties panel, the timeline, even the toolbar are components. Learn one system, customize everything.
Component slots
Components can define slots — typed inputs that accept nodes from the parent scene. Useful for IK targets, eye tracking, snap points.
Component "Character"
Slots:
leftFootTarget: VNode ← drag a node here
rightFootTarget: VNode
lookAtTarget: VNode
During authoring, each slot creates a placeholder node for testing. At runtime, placeholders are replaced by actual scene nodes.
Formulas reference slots naturally:
EyeRotation = atan2($lookAtTarget.y - this.y, $lookAtTarget.x - this.x)
Nesting
Components contain other component instances. Inputs wire through the hierarchy:
Page component
├── Input: theme.primaryColor
└── Contains: Button instance
└── Input: color = $input.theme.primaryColor
Change the page theme — all buttons update. One formula, unlimited depth.
Export
Components map directly to React components:
// Auto-generated from Component "Button"
function Button({ label = "Click me", color = "#3498DB", disabled = false }) {
return (
<button style={{ backgroundColor: disabled ? "#999" : color }}>
{label}
</button>
);
}
Non-visual components become custom hooks:
function useTimer(interval: number, active: boolean) {
const [elapsed, setElapsed] = useState(0);
useEffect(() => {
if (!active) return;
const id = setInterval(() => setElapsed(e => e + interval), interval);
return () => clearInterval(id);
}, [interval, active]);
return { elapsed };
}