Skip to content
Flow-UILive

Theming

Backend colors and fonts for server driven UI, resolved through one pluggable SwiftUI protocol, with light and dark mode handled for you.

Flow-UI never hardcodes a palette. Every color and font in a response resolves through a ThemeProvider, and the default one understands plain hex values with dark variants. Apps with a design system implement the protocol once and backend tokens flow through their own palette.

Colors from the backend#

ColorData accepts three levels of sophistication:

json
"#4F8CFF"
 
{ "hex": "#4F8CFF", "dark_hex": "#FF6B6B" }
 
{ "token": "surface.primary", "alpha": 0.8 }

Resolution order is deliberate: token first when the theme understands it, then hex with dark_hex, then nothing. The 8 digit hex form carries alpha first, so #CC FFFFFF is white at 80 percent.

The default theme turns a hex pair into a dynamic color that switches with the system appearance. Send dark_hex on every surface color and dark mode simply works.

Fonts#

FontData mirrors the same idea:

json
{ "size": 16, "weight": "semibold" }
 
{ "token": "title.large" }

Weights map to the system scale: regular, medium, semibold, bold, heavy, light.

Plugging in a design system#

MyTheme.swiftswift
struct MyTheme: ThemeProvider {
    let defaults = ThemeDefaults()
 
    func color(_ data: ColorData?) -> Color? {
        if let token = data?.token {
            return DesignSystem.color(named: token)
        }
        return DefaultTheme().color(data)
    }
 
    func font(_ data: FontData?) -> Font? {
        if let token = data?.token {
            return DesignSystem.font(named: token)
        }
        return DefaultTheme().font(data)
    }
}

Install it once at the root:

swift
FlowPageView(store: store)
    .flowTheme(MyTheme())

Every widget below that point, built in or custom, resolves through your palette. The backend can now speak in tokens like surface.primary and your design system decides what that means in each appearance.