Skip to content
Flow-UILive

Pages, sections and widgets

The JSON envelope every Flow-UI response follows, from the page down to a single widget, and how it maps onto native SwiftUI views.

Every response is one shape: a page holds sections, sections hold widgets. Learn this envelope once and every Flow-UI payload becomes readable.

The page#

GET /pages/homejson
{
  "page": {
    "id": "home",
    "nav": { "title": { "text": "Home" } },
    "header": { "sticky": true, "widgets": [] },
    "sections": [],
    "footer": { "sticky": true, "widgets": [] },
    "pagination": { "has_more": true, "postback": { "cursor": "abc" } },
    "refresh": { "pull_to_refresh": true }
  }
}

header and footer are widget strips. When sticky is true they pin outside the scrolling area, which is how persistent filter rails and cart bars work. When false they scroll with the content.

Sections#

A section owns an arrangement and spacing for its widgets:

json
{
  "id": "sec_popular",
  "layout": {
    "arrangement": "carousel",
    "item_spacing": 12,
    "insets": { "left": 16, "right": 16 }
  },
  "header": { "type": "title_block", "data": { "title": "Popular this week" } },
  "widgets": []
}
  • vertical stacks widgets top to bottom.
  • carousel scrolls horizontally with view aligned snapping. A widget's layout.width fraction produces peeking cards.
  • grid lays widgets in columns flexible columns.

A section's optional header widget pins while its section scrolls beneath it, the classic sticky section title.

Unknown arrangement strings fall back to vertical, so backends can invent new arrangements without breaking old clients.

Widgets#

json
{
  "type": "image_text_card",
  "id": "product_1",
  "layout": { "corner_radius": 12, "margin": { "left": 16, "right": 16 } },
  "data": { "title": "AirPods Pro 2" },
  "actions": { "tap": { "type": "toast", "message": "Tapped" } },
  "tracking": { "impression_id": "imp_1" }
}

Five envelope fields are uniform across every widget:

  • type selects the registered SwiftUI view.
  • id gives the widget stable identity for state and mutations. Send one whenever a widget is interactive or replaceable.
  • layout is the backend controlled chrome, covered in Layout.
  • actions maps event names to declarative actions, covered in Actions.
  • tracking is opaque JSON your analytics receives untouched.

The data payload belongs to the widget type. The framework never looks inside it; the registered widget decodes it into its own typed model.

Pagination#

When pagination.has_more is true, Flow-UI renders a sentinel after the last section. When it appears, the store requests the next page and echoes postback exactly as received. New sections append; existing widget state is untouched.