Concepts / Architecture

A retained UI tree, described declaratively.

LumaFlow does not introduce a second renderer or layout engine. It turns immutable widget descriptions into retained Unity UI Toolkit elements and reconciles compatible changes in place.

Each layer has a distinct job.

Widget

Immutable C# configuration: text, layout, callbacks and child descriptions.

WidgetNode

Internal mounted boundary that owns reconciliation, dependencies and lifecycle.

VisualElement

The native Unity UI Toolkit element that receives layout, input and rendering.

The application describes the first layer. LumaFlow manages the second. Unity owns rendering and layout at the third.

Compatible updates preserve the native element.

When a reactive value changes, the framework rebuilds the branch that read it. If the next widget is compatible with the mounted one, its existing node and native element are updated instead of replaced. An incompatible type or configuration creates a new mounted branch.

This is why widgets stay immutable.

A widget is a current description, not a mutable view object. The retained identity belongs to the mounted node and native UI Toolkit element.

Position is enough until a sibling can move.

Ordinary children reconcile by position. When stateful siblings can be inserted, removed or reordered, give them a stable WidgetKey so LumaFlow can follow their identity.

ProjectRows.cs
new Column(
  projects.Select(project =>
    new ProjectRow(project)
      .WithKey(new WidgetKey(project.Id))));

Keys are local to one sibling collection. They are not application-wide identifiers, and duplicate keys in the same collection are rejected.

State belongs to an owner, not to a build pass.

Keep application data in explicitly owned State<T> values, controllers or domain services. A StatefulWidget owns mount-local UI state. Do not create either as a side effect of a builder that can run repeatedly.

Use MountHandle.Rebuild() to re-evaluate declarative builder boundaries while retaining compatible mounted state. Use Restart(newRoot) when the root needs fresh mount-local state.

Use Native at the integration edge.

Native embeds a detached UI Toolkit element when LumaFlow does not yet wrap the capability you need. Prefer a factory when every mount requires its own element. Do not reparent that element externally while it is mounted.

Interop.cs
new Native(() => new Label("Native UI Toolkit content"));
Continue with the guides