React Native Runtime-Generated UI Layouts - From Figma to a Live App

Most applications still ship fixed UI layouts: screens compiled into the app binary, updated only when a new version hits the store. This approach is increasingly misaligned with modern product development. Design changes are frequent, experiments are constant, and coordination costs between designers and developers are high.

Runtime-generated UI layouts, where the UI tree is produced dynamically from JSON or design tokens derived from tools like Figma, offer a fundamentally better model. When done well, they turn layout into data, not code, and unlock a level of velocity that static UIs cannot match.

In this article I focus on what developers can do to implement this approach responsibly and effectively.


From Fixed Screens to Runtime UI Trees

The core idea

Instead of shipping precompiled layouts, the app ships a UI renderer. Layouts are described declaratively and delivered at runtime:

At runtime, the app parses this data and builds a UI tree.

Example (simplified JSON layout):

{
  "type": "Column",
  "padding": "md",
  "children": [
    {
      "type": "Text",
      "token": "heading.lg",
      "value": "Welcome back"
    },
    {
      "type": "Button",
      "variant": "primary",
      "action": "open_profile",
      "label": "View profile"
    }
  ]
}

The app does not know this screen at compile time. It only knows how to render a Column, Text, and Button.

What developers must build

To make this viable, developers need to invest in:

  1. A stable, expressive UI schema
  2. A robust renderer
  3. Clear boundaries between layout and logic

This is not a shortcut, it is infrastructure.


Designers Push Changes Without App Updates

One of the most powerful outcomes of runtime layouts is that design changes no longer require app releases.

Spacing tweaks, copy updates, component rearrangements, and even new screens can be delivered remotely. This shifts the bottleneck from app stores to your backend.

Developer responsibility: enforce safety

Unrestricted runtime UI is dangerous. Developers must:

Example schema validation:

type Node =
  | { type: "Text"; value: string; token: string }
  | { type: "Button"; label: string; action: string }
  | { type: "Column"; children: Node[]; padding?: string };

function validateNode(node: any): node is Node {
  if (typeof node !== "object" || !node.type) return false;

  switch (node.type) {
    case "Text":
      return typeof node.value === "string";
    case "Button":
      return typeof node.action === "string";
    case "Column":
      return Array.isArray(node.children);
    default:
      return false;
  }
}

If validation fails, the renderer should display a fallback, not crash the app.


Feature Flags as Layout, Not Just Logic

Most teams treat feature flags as logic toggles:

if (flags.newProfile) {
  showNewProfile();
} else {
  showOldProfile();
}

Runtime UI flips this model. The layout itself is the flag.

Example server response:

{
  "layoutVersion": "profile_v3",
  "tree": {
    ...
  }
}

Developers no longer branch UI logic. They simply render what the backend provides.

Why this is better

What developers must do differently


Compiling Figma-Like Constraints to Real Layout Engines

Advanced teams go further: they compile design constraints instead of hand-mapping layouts.

Design tools like Figma define relationships:

These can be translated into real layout systems like Flexbox or Skia.

Constraint → Flexbox

Figma constraint:

Button:
  width: hug
  horizontal: fill

Compiled output:

{
  "type": "Button",
  "style": {
    "flexGrow": 1,
    "alignSelf": "stretch"
  }
}

Developer responsibility: build a constraint compiler

This is not a designer problem. Developers must:

This compiler can live:

The key is one source of truth.


Version UI Schemas Independently from App Code

A common failure mode is tying UI schemas to app releases. This defeats the purpose.

Correct approach

Example:

{
  "schema": "ui.v2",
  "minAppVersion": "5.4.0",
  "tree": {
    ...
  }
}

On the client:

if (!supportedSchemas.includes(payload.schema)) {
  renderFallback();
}

Why this matters


Improving Developer Experience (DX)

Runtime UI systems often fail because developers hate working with them. You can fix that.

Generate types from schemas

If your UI schema is JSON, generate types automatically:

json-schema-to-typescript ui.schema.json > ui.ts

This restores autocomplete, refactoring, and safety.

Snapshot testing for layouts

Treat layouts like code:

test("profile layout v3", () => {
  expect(render(layoutJson)).toMatchSnapshot();
});

This catches accidental breaking changes early.

Local preview tooling

Developers should be able to:

Without this, iteration speed collapses.


Performance and Caching Are Your Job

Runtime UI skeptics often complain about performance. In practice, performance issues come from bad implementations, not the model.

Developers should:

Example memoization:

const cache = new Map<string, RenderNode>();

function renderLayout(id: string, json: LayoutJson) {
  if (cache.has(id)) return cache.get(id)!;

  const tree = buildTree(json);
  cache.set(id, tree);
  return tree;
}

Runtime does not mean slow. It means dynamic.


Conclusion: This Is a Structural Upgrade

Runtime-generated UI layouts are not a trend or a nice-to-have. They are a structural upgrade to how apps evolve.

Teams that succeed with this model share one trait: developers take ownership.

If you are serious about speed, experimentation, and long-term maintainability, fixed layouts are technical debt. Runtime UI is the correction, but only if developers do the hard work upfront.