Visual Designer
Build applications, not mockups. The Delphi model, modernized.
Drag widgets onto the canvas. Wire event handlers. Add layout rules. Preview interactively. Export as a working React app. The distance from design to production is zero.
Widget palette
Pre-built UI components you drag onto the canvas. Each has typed inputs, events, and outputs.
| Widget | Inputs | Events | Outputs |
|---|---|---|---|
| Button | label, color, disabled | click | isPressed, isHovered |
| TextInput | value, placeholder | change, focus, blur | text, isFocused |
| Slider | min, max, value, step | change | value |
| Checkbox | checked, label | change | checked |
| Dropdown | options, selected | change | selectedValue |
| List | items, template | select | selectedItem |
| Panel | title, collapsible | toggle | isOpen |
| Image | src, fit | load | naturalWidth, naturalHeight |
Widgets are components — same system as custom components. The built-in palette is a starting point, not a limit.
Three levels of logic
Level 1: Property toggle
No code. Click a button → toggle a panel's visibility. Wire it in the properties panel.
Level 2: Formula action
One expression. Click a button → set slider.value = 0. Reactive evaluation handles the rest.
Level 3: Script block
Full code. Click a button → fetch data from an API, parse it, update the UI.
const response = await fetch(api.url);
const data = await response.json();
dataTable.items = data.results;
loadingSpinner.visible = false;Start at level 1. Graduate to level 3 when you need it. The system doesn't force a choice.
Layout system
Beyond absolute positioning — responsive containers that adapt to content and screen size.
Flex layout
Container (direction: row, gap: 12, align: center)├── Avatar├── Name└── Badge
Grid layout
Container (columns: "1fr 2fr 1fr", rows: "auto")├── Sidebar├── Content└── Panel
Absolute
Pixel-perfect placement. The default. Switch to flex or grid per container.
Formula-driven dimensions:
ContentPanel.width = $parent.width * 0.7Sidebar.width = $parent.width - ContentPanel.width - 24
Preview mode
A dedicated mode that hides all editor chrome and runs your application interactively.
What happens:
- Handles, selection outlines, gizmos disappear
- Event handlers execute on user interaction
- Reactive formulas evaluate
- Layout rules apply
- Cursor changes on interactive widgets
What doesn't happen:
- No editing — canvas is locked
- No tool switching
- No property panel (unless you built one as a component)
Open in a separate window for side-by-side editing and preview.
The Delphi model, modernized
Visual Basic and Delphi proved that dragging components onto a canvas and wiring properties is the fastest way to build applications. That model died because the web won.
Formo brings it back — for the web.
| Classic RAD | Formo |
|---|---|
| TButton on a Form | Button component on canvas |
| TTimer (non-visual) | Timer non-visual component |
| TDatabase | ApiConnection non-visual component |
| Object Inspector | Properties panel with formulas |
| OnClick event handler | Click event with code editor |
| Compiled .exe | Exported React app |
Same speed. Modern output. Browser-native.
User journey
1. Draw shapes on canvas ← design tool2. Add keyframe animation ← motion tool3. Write formulas: x = mouse.x ← interactive tool4. Group elements into Components ← building blocks5. Wire components with formulas ← application logic6. Export as React app ← production code
Each step is independently useful. Stop at step 1 — you have a design tool. Stop at step 3 — you have an interactive prototype. Go to step 6 — you have working software.
title: "Interactive slider"
description: "A slider widget that controls a shape's rotation"
nodes:
- type: RECT, name: "Track", x: 200, y: 350, w: 200, h: 6, fill: "#BDC3C7", cornerRadius: 3
- type: ELLIPSE, name: "Thumb", x: 200, y: 350, w: 20, h: 20, fill: "#3498DB"
- type: RECT, name: "Box", x: 200, y: 180, w: 80, h: 80, fill: "#E74C3C"
formulas:
- node: "Thumb", prop: "x", value: "clamp(mouse.x, 100, 300)"
- node: "Box", prop: "rotation", value: "(Thumb.x - 100) / 200 * 360"
tools_visible: [select, play]
ui_visible: [properties, formulas]→ Components → Export → Formulas