Layout first
Start with column, row, or grid. Add scrollArea, overlay, or
absolute only when the content model requires them.
Flow-Like A2UI surfaces are normalized component trees. Each component has a
stable ID, a lower-camel-case type, typed properties, optional styles, and
references to its children. This is the same contract used by the visual
builders, FlowPilot, Pages, and Widgets. For visual authoring, start with
Create Pages or Create Widgets.
A generated or imported surface uses one root ID and a flat component array:
{ "rootComponentId": "root", "canvasSettings": { "padding": "24px" }, "components": [ { "id": "root", "style": { "className": "min-h-full bg-background text-foreground" }, "component": { "type": "column", "gap": { "literalString": "16px" }, "children": { "explicitList": ["title", "open-dashboard"] } } }, { "id": "title", "component": { "type": "text", "content": { "literalString": "Operations dashboard" }, "variant": { "literalString": "heading" }, "size": { "literalString": "2xl" }, "weight": { "literalString": "bold" } } }, { "id": "open-dashboard", "component": { "type": "button", "label": { "literalString": "Open dashboard" }, "variant": { "literalString": "default" }, "actions": [ { "name": "navigate_page", "context": { "route": "/dashboard" } } ] } } ], "dataModel": []}The visual builders maintain this structure for you. Use the JSON contract directly when generating a complete surface with FlowPilot or another coding agent.
Most visible component properties use BoundValue, even when their value is
currently static.
| Value | JSON shape |
|---|---|
| String | { "literalString": "Hello" } |
| Number | { "literalNumber": 42 } |
| Boolean | { "literalBool": true } |
| Select options | { "literalOptions": [{ "value": "open", "label": "Open" }] } |
| Arbitrary JSON | { "literalJson": "{\"key\":\"value\"}" } |
| Data binding | { "path": "$.data.customer.name", "defaultValue": "Customer" } |
Do not substitute raw strings or numbers where a component property expects a
BoundValue.
Use an explicit list for a known component tree:
{ "children": { "explicitList": ["heading", "description", "actions"] }}Template children are available for data-driven repetition:
{ "children": { "template": { "dataPath": "$.data.items", "itemIdPath": "$.id", "templateComponentId": "item-template" } }}For a reusable card, row, or repeated interaction pattern, prefer a
widgetInstance. A Widget owns its own component tree, exposed properties,
and named actions.
Actions are an array inside the interactive component:
{ "type": "button", "label": { "literalString": "Approve" }, "actions": [ { "name": "workflow_event", "context": { "nodeId": "board-event-node-id" } } ]}Use one board Event for each distinct purpose. Page actions use the fixed
workflow_event name and identify the selected Event with context.nodeId.
The Event handler reads live input state with nodes such as Get Element
Value or Get File Input Files; typed values should not be copied into the
action context.
The current action contracts are:
| Action | Context |
|---|---|
workflow_event | nodeId; the builder also includes the owning appId and boardId |
widget_event | actionId; resolved through the Widget instance’s action binding |
navigate_page | route plus optional queryParams |
external_link | url |
For Widgets, declare the action ID in the Widget definition and bind that ID
when the Widget is instantiated. There are no onClick, onChange,
updateData, openModal, or closeModal action objects in the current
component contract. A host may receive an otherwise unknown action name as a
generic userAction, but that fallback does not execute a workflow by itself.
The current runtime contains 71 component types.
| Type | Purpose | Common properties |
|---|---|---|
row | Horizontal flex layout | gap, align, justify, wrap, reverse, children |
column | Vertical flex layout | gap, align, justify, wrap, reverse, children |
stack | Layer children on the z-axis | align, width, height, children |
grid | CSS grid layout | columns, rows, gap, columnGap, rowGap, autoFlow, children |
scrollArea | Scrollable content region | direction, children |
aspectRatio | Preserve a content ratio | ratio, children |
overlay | Anchor components over a base component | baseComponentId, overlays |
absolute | Free-positioned canvas | width, height, children |
box | Generic semantic container | as, children |
center | Center one or more children | inline, children |
spacer | Fixed or flexible space | size, flex |
widgetInstance | Instance of a reusable Widget definition | widgetId, instanceId, inlineWidgetDef, exposedPropValues |
| Type | Purpose | Common properties |
|---|---|---|
text | Plain typography | content, variant, size, weight, color, align, truncate, maxLines |
image | Image with loading and fallback behavior | src, alt, fit, fallback, loading, aspectRatio |
icon | Lucide icon | name, size, color, strokeWidth |
video | Video player | src, poster, autoplay, loop, muted, controls |
lottie | Lottie animation | src, autoplay, loop, speed, width, height |
markdown | Rendered Markdown content | content, allowHtml |
divider | Horizontal or vertical separator | orientation, thickness, color |
badge | Compact status or category label | content, variant, color |
avatar | Avatar image with fallback | src, fallback, size |
userProfile | User lookup and profile presentation | value, variant, avatarSize, visibility flags |
progress | Progress indicator | value, max, showLabel, variant, color |
spinner | Indeterminate loading state | size, color |
skeleton | Loading placeholder | width, height, rounded |
table | Data table | columns, data, sorting, search, pagination, and selection flags |
tableRow | Explicit table row | cells, selected, disabled |
tableCell | Explicit table cell | content, isHeader, spans, align |
filePreview | Preview a PDF, image, media, code, or text file | src, filename, mimeType, fileType, showControls |
diffView | Compare text, code, Markdown, JSON, or documents | original, modified, mode, kind, language |
boundingBoxOverlay | Display boxes over an image | image source, boxes, labels, and display options |
| Type | Purpose | Common properties |
|---|---|---|
button | Primary user action | label, variant, size, disabled, loading, icon, actions |
feedback | Positive/negative, rating, and comment feedback | mode, labels, ratings, comment settings, context flags |
appLink | Link to an app surface | target, label, appId, eventId, variant, disabled |
textField | Single- or multi-line text input | value, placeholder, label, inputType, multiline, required |
select | Single or multiple selection | value, options, placeholder, multiple, searchable |
slider | Numeric range input | value, min, max, step, showValue, label |
checkbox | Boolean or indeterminate input | checked, label, disabled, indeterminate |
switch | Boolean switch | checked, label, disabled |
radioGroup | Single selection from visible options | value, options, orientation, label |
dateTimeInput | Date, time, or combined input | value, mode, min, max, label |
fileInput | File upload | value, accept, multiple, directory, maxSize, maxFiles, error |
imageInput | Image upload with preview | file-input properties plus aspectRatio and showPreview |
voiceInput | Record audio or speech-to-text input | value, variant, size, mode, invoke, resultMode |
cameraView | User-started camera with recent audio capture and annotations | value, facingMode, deviceId, intervalMs, maxWidth, quality, audioEnabled, audioBufferSeconds, overlays, effects |
link | Internal route or external link | href, label, route, queryParams, target, variant |
imageLabeler | Draw and edit labeled image regions | image, labels, boxes, and editing options |
imageHotspot | Interactive image hotspots | image, hotspots, selection, and action options |
| Type | Purpose | Common properties |
|---|---|---|
card | Framed content group | title, description, footer, variant, padding, children |
modal | Modal dialog | open, title, description, close behavior, size, children |
tabs | Tabbed content | value, tabs, orientation, variant, tab styles |
accordion | Collapsible sections | items, selection and collapse behavior |
drawer | Edge-mounted panel | open, side, title, close behavior, children |
tooltip | Hover or focus explanation | content, side, delay, and trigger component |
popover | Anchored interactive panel | open state, placement, trigger, and content component |
| Type | Purpose |
|---|---|
plotlyChart | Plotly series, axes, layout, and advanced raw configuration |
nivoChart | Nivo chart types including bar, line, pie, radar, heatmap, Sankey, and more |
geoMap | Geographic map with markers and view settings |
graph | Node and edge network graph with legend, search, and inspectors |
ontologyGraph | Live explorer for one of the project’s ontologies |
calendar | Month, week, day, or agenda planning view |
gantt | Interactive task timeline with dependencies |
iframe | Sandboxed external URL or srcdoc embed |
When workflow data drives a chart or table, use Push Data to Chart, Push
CSV to Table, or Update Table. Literal data properties are best kept
for static design-time content.
| Type | Purpose |
|---|---|
canvas2d | Root 2D canvas |
sprite | Positioned 2D image sprite |
shape | Rectangle, circle, polygon, line, or other 2D shape |
scene3d | Root 3D scene |
model3d | GLB or GLTF model |
dialogue | Visual-novel-style dialogue |
characterPortrait | Character image and expression |
choiceMenu | Player decision menu |
inventoryGrid | Inventory slots and items |
healthBar | Health or resource meter |
miniMap | Compact map with markers |
style belongs beside component in each component record:
{ "id": "summary-card", "style": { "className": "rounded-xl border border-border bg-card p-6 text-card-foreground", "shadow": { "x": "0", "y": "8px", "blur": "24px", "color": "rgb(0 0 0 / 0.12)" } }, "component": { "type": "card", "title": { "literalString": "Summary" } }}Prefer semantic theme classes such as bg-background, bg-card,
text-foreground, text-muted-foreground, and border-border. They adapt to
light and dark mode without duplicating color rules.
Structured style fields cover background, border, shadow, spacing, sizing, position, transform, overflow, typography, visibility, and responsive overrides.
Layout first
Start with column, row, or grid. Add scrollArea, overlay, or
absolute only when the content model requires them.
Bind live values
Use BoundValue paths for shared data, and element setter nodes when a
workflow owns the displayed value.
Reuse interactions
Extract repeated interactive cards or rows into Widgets and declare their actions at the Widget level.
Respect both themes
Prefer semantic theme tokens and verify the surface in both light and dark mode.
Enable showLocate on a geoMap to show Find my location. The button reads
one foreground location fix using the native provider on supported Apple devices
or the browser location provider. It does not start tracking when the map renders.
The editing canvas disables location acquisition. Closing the Page, hiding it,
or moving the app into the background cancels the request and discards late fixes.
Bind Location found in the map’s Actions tab to receive these fields:
coordinate: the existing {latitude, longitude} object.geometry: a Geometry Point, {"type":"Point","coordinates":[longitude,latitude]}.location: the full fix, including geometry, latitude, longitude,
accuracy, timestamp, altitude, altitudeAccuracy, speed, and heading.Accuracy and altitude are measured in meters, speed in meters per second, and
heading in degrees. Timestamp is Unix time in milliseconds. Unavailable altitude,
altitude accuracy, speed, or heading is null. Geometry remains two-dimensional;
altitude belongs in the fix metadata. Existing map Event bindings that read
coordinate keep their current shape.
Location error carries code and message, also available under error.
The map shows the error beside its controls. This Event runs only when explicitly
bound; older wildcard or legacy actions do not inherit it.
Use Get Current Location (geo.getCurrentLocation, under Web / Geo /
Location) to read location from an Event without a map. Its High Accuracy
input defaults to false, Maximum Age defaults to zero seconds and accepts up
to 300 seconds, and Timeout defaults to 30 seconds with a range of 1 to 120.
Success provides a typed Point on geometry, the full fix on location, and the
legacy object on coordinate. The Error branch provides a structured code and
message. An Event running remotely reads from its invoking device and requests
permission to share that fix with the app.
Connect geometry directly to Geometry Point inputs, Point Distance (Meters),
or map update nodes. Point to Legacy Coordinate adapts it for existing H3,
routing, and geocoding nodes. Use geodesic distance in meters for geographic
radius checks; planar distance operates in coordinate degrees.
Use cameraView when a Page needs to capture a photo, translate visible text,
combine a scene with recent speech, or show detection results over a live preview.
The user starts the camera with its
Start camera button. Loading a Page, editing its layout, or calling a node
cannot grant camera access or start the camera.
{ "id": "inspection-camera", "component": { "type": "cameraView", "label": { "literalString": "Inspection camera" }, "facingMode": { "literalString": "environment" }, "intervalMs": { "literalNumber": 2000 }, "maxWidth": { "literalNumber": 1280 }, "quality": { "literalNumber": 0.85 }, "value": { "path": "/camera/frame" }, "overlays": { "path": "/camera/annotations" } }}Bind the component’s Image captured or Interval frame event in the
builder’s Actions tab. These events use the Page’s existing Event authorization
and Widget action bindings. Interval capture requires an explicit frame
handler. Setting an interval without one never transfers frames. Zero disables
sampling; positive values have a minimum of 250 milliseconds. A tick is skipped
while the previous capture or Event is still running.
After Start, the value binding and Camera ready event expose surfaceId,
componentId, sessionId, status, audioEnabled, audioActive,
audioBufferSeconds, and audioAvailableMs. audioActive indicates whether the
microphone is recording; audioAvailableMs is the recent history available for a
clip. A separate button can read this bound
session and pass its IDs to Capture Camera Frame. Every camera command
requires the current session ID, so an earlier Event cannot capture from a camera
that the user has restarted.
The capture payload contains surfaceId, componentId, sessionId, frameId,
capturedAt, width, height, and an image temporary-storage reference.
It is also available as frame and value in the action context. The value
binding then stores this reference, without image bytes. Read Camera Image turns
the frame into the image handle used by OCR and detection nodes. Capture Camera
Frame requests a new frame from a camera on the invoking screen, including
when the requesting Event runs remotely.
Set audioEnabled to { "literalBool": true } to offer microphone capture.
The user must press Start camera and microphone and grant microphone permission.
The default is false, so existing Pages capture video only. audioBufferSeconds
sets the amount of recent audio retained in memory, in whole seconds from 1 to
300; the default is 60. Older samples are discarded as new samples arrive.
The buffer stays on the device until an Event explicitly requests a clip.
Automatic frame events transfer images only.
Pass the current surfaceId, componentId, and sessionId to Capture Camera
Audio (ui.captureCameraAudio) and choose the recent duration to retrieve.
The Duration input defaults to 10 seconds. Capture Camera Input
(ui.captureCameraInput) takes a new frame and a clip from the same active
session before uploading either, so later upload time does
not shift the captured input. Both nodes accept a duration from 0.1 to 300
seconds and return the available portion if the buffer is shorter. Check the
actual Recorded Duration output. An empty buffer or an inactive microphone
follows the Error branch.
Both nodes provide Clip metadata, Audio URL, Audio Path when available, and the actual Recorded Duration in seconds. The combined node also provides Frame. Connect Audio Path to Speech to Text, or pass Audio URL to an audio service. Connect Frame to Read Camera Image for OCR or vision analysis. Audio is a 16 kHz, mono, PCM16 WAV file in the Event’s configured temporary storage. Workflows receive file references; audio bytes do not travel in component events.
Clip metadata includes surfaceId, componentId, sessionId, clipId,
capturedAt, startedAt, endedAt, durationMs, requestedDurationMs,
sampleRate, channels, and an audio file reference containing name, type,
size, url, and optional flowPath. Requests always use the
current session ID and cannot read history from an earlier camera session.
Update Camera Overlays accepts the original frame and an array of annotations.
Keep that frame attached to the analysis result: the frontend rejects an update
after a newer frame is captured or the camera session ends. Annotation types are
box, text, point, polygon, blur, and dim. Every annotation needs a unique
id. Coordinates are normalized to the upright captured image, with (0, 0) at
the top left. Boxes and effect regions use x, y, width, and height;
polygons use points, an array of [x, y] pairs. Text uses x, y, and text.
[ { "id": "object-1", "type": "box", "x": 0.1, "y": 0.2, "width": 0.3, "height": 0.4, "text": "Package", "confidence": 0.96 }, { "id": "translation-1", "type": "text", "x": 0.15, "y": 0.65, "text": "Keep upright", "color": "#ffffff", "fontSize": 18 }]Annotations expire after five seconds by default, configurable from 100 to
60,000 milliseconds. Sending an empty array clears them. For data-bound updates,
wrap the array as { sessionId, frameId, overlays, ttlMs }. overlayClick carries
the clicked annotation and its frame identity. Mirroring changes only the
preview; the component aligns annotations with both mirrored previews and
contain/cover fitting.
The effects object supports grayscale and sepia from 0 to 1, blur from 0
to 24 pixels, and brightness/contrast from 0 to 3. These effects and annotation
regions affect the preview only. Captured images contain the original pixels,
so a preview blur does not redact an image sent to an Event.
Freeze pauses the displayed frame for inspection, disables microphone tracks, and discards audio history. Resume returns to live video and re-enables the already granted microphone tracks with an empty buffer. It never opens a new microphone permission request. Stop releases the camera and microphone and discards audio history. The Control Camera node exposes the same operations for the session that produced a frame. Camera tracks and interval scheduling stop when the Page unmounts, the document becomes hidden, the component or its parent is hidden, or the native host reports an inactive device. Returning to the screen requires another user start. Late permission replies and analysis results cannot reopen or modify an ended session. Leaving or hiding the screen also discards audio history. Browser capture requires HTTPS or localhost and browser/device permission. Media references use the requesting Event’s configured local or remote temporary storage.
If microphone processing is suspended or interrupted while the screen remains visible, the session stops and its audio history is discarded. Press Start camera and microphone again to begin a new session.
Use Play Sound (ui.playSound, under UI → Audio) to play audio on the
screen that invoked an Event. It works with local and remote Events and does not
require an audio component on the Page. Connect Text to Speech or Capture
Camera Audio to its Audio Path input, or supply an audio download URL through
Audio URL. Supply exactly one of these inputs.
Volume defaults to 1 and accepts values from 0 to 1; 0 mutes playback. If a
browser cannot apply a requested nonzero volume, the node reports
unsupported_volume. Use Volume 1 and the device’s volume buttons in that case.
Timeout defaults to 300 seconds and accepts 1 to 600 seconds
for the frontend to load the sound, obtain a user tap if needed, and finish
playback. If the browser blocks automatic playback, the screen shows a Play
sound action. The workflow waits for that tap within the timeout.
The normal execution output runs after playback finishes. Duration reports the completed sound’s length in seconds. Loading errors, rejected playback, cancellation, and timeout follow the Error branch with a structured code and message. Navigating to another Page, switching accounts, hiding or backgrounding the screen, or cancelling the Event stops playback and cancels pending sound requests. A new sound for the same app stops the previous one. An Event without an available invoking screen cannot play sound through this node.
Audio travels as a URL or file reference. When a FlowPath needs a temporary copy for frontend access, the node uses the Event’s configured temporary storage and limits that copy to 32 MiB. It does not send audio bytes through the device command channel. See the Play Sound node reference for its pins.