Skip to content
Flow-UILive

Adding a widget

Teach Flow-UI a new server driven UI component in three steps, entirely from your own app: model the payload, write the SwiftUI view, register it.

Adding a widget never touches framework code. You agree a type string and a data shape with your backend team, then write three things in your app: a payload model, a view, and one registration call.

1. Model the payload#

OrderCardContent.swiftswift
import FlowCore
 
struct OrderCardContent: WidgetContent, Hashable {
    static let widgetType = "order_card"
 
    let orderNumber: TextData
    let status: TagData?
    let items: [TextData]
    let total: TextData?
 
    private enum CodingKeys: String, CodingKey {
        case orderNumber = "order_number"
        case status, items, total
    }
}

Reuse the FlowCore atoms wherever they fit. TextData, TagData and friends buy you fonts, colors, dark mode and theming with no extra work.

2. Write the view#

OrderCardWidget.swiftswift
import SwiftUI
import FlowRender
 
struct OrderCardWidget: WidgetView {
    let content: OrderCardContent
    let context: WidgetContext
 
    var body: some View {
        VStack(alignment: .leading, spacing: 10) {
            HStack {
                FlowText(content.orderNumber)
                if let status = content.status { FlowTag(status) }
            }
            ForEach(Array(content.items.enumerated()), id: \.offset) { _, item in
                FlowText(item)
            }
            if let total = content.total {
                Divider()
                FlowText(total)
            }
        }
    }
}

Do not apply margins, padding, backgrounds or corners yourself. The backend sends them in layout and the renderer applies them around your view. Your view owns only what is inside.

3. Register it#

swift
registry.register(OrderCardWidget.self)

Done. The widget now works everywhere: vertical sections, carousels, grids, headers, footers, bottom sheets, and nested inside containers like the accordion. Envelope actions, layout chrome, diagnostics and the debug console all apply automatically.

Interactions#

Envelope level tap and long_press are dispatched for you. For controls inside your payload, dispatch explicitly:

swift
FlowButton(content.cta) { context.dispatch(content.cta.action) }

Named events fire whatever the backend declared under that name:

swift
context.dispatch(event: "change")

Ephemeral state#

swift
private var expanded: Binding<Bool> {
    context.state.binding(widgetID: context.widgetID, key: "expanded", default: false)
}

See Widget state for the lifecycle rules.

Skeletons#

Conform to WidgetSkeletonProviding and loading states mirror your widget's real shape instead of a generic block:

swift
extension OrderCardWidget: WidgetSkeletonProviding {
    static func skeleton() -> AnyView {
        AnyView(
            VStack(alignment: .leading, spacing: 8) {
                RoundedRectangle(cornerRadius: 4).fill(.gray.opacity(0.3))
                    .frame(width: 120, height: 14)
                RoundedRectangle(cornerRadius: 4).fill(.gray.opacity(0.2))
                    .frame(height: 12)
            }
            .flowShimmer()
        )
    }
}