MVU in Other Ecosystems

MVU in Other Ecosystems

The Elm Architecture (Model–View–Update) outgrew its original language and now lives across nearly every major ecosystem under various names. This note catalogues the major implementations so you can spot the pattern wherever you encounter it.

The shape is always the same: a Model representing state, a Msg/Action/Event type representing what happened, a pure Update function that produces new state, a View function that renders state, and an explicit way to handle side effects (Cmd, Effect, Task, Thunk).

Bubbletea (Go)

Bubbletea by Charm. The most popular TUI framework in Go. Explicit Elm Architecture.

type Model struct {
    count int
}

type tickMsg time.Time

func (m Model) Init() tea.Cmd { return nil }

func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    switch msg := msg.(type) {
    case tea.KeyMsg:
        switch msg.String() {
        case "+": m.count++
        case "-": m.count--
        case "q": return m, tea.Quit
        }
    case tickMsg:
        m.count++
    }
    return m, nil
}

func (m Model) View() string {
    return fmt.Sprintf("count: %d", m.count)
}

func main() {
    p := tea.NewProgram(Model{})
    p.Run()
}

Init returns the initial Cmd. Update is the reducer. View renders. tea.Cmd is the side-effect type. Identical to Elm in shape.

Used by gh (the GitHub CLI's TUI mode), glow, soft-serve, dozens of small CLIs.

tui-realm (Rust)

tui-realm by Christian Visintin. TEA on top of ratatui (formerly tui-rs). More structured than rolling your own.

Key concepts: Components (each with its own Model/Update/View), subscriptions for cross-component messages, properties for parent-to-child config.

A bit heavier than Bubbletea. Trade-off: more structure for free, less control. For lazydap, Lazydap chose plain ratatui + hand-rolled Elm Architecture per Lazydap D012 — wanted to learn the pattern by writing it.

Iocraft (Rust)

Iocraft. React-flavoured Rust TUI: components defined via element! macro, hooks (use_state, use_effect), declarative composition.

Closer to React than to Elm-strict, but fundamentally the same shape under the hood.

Compose Multiplatform (Kotlin)

JetBrains' Compose, originally for Android, now multiplatform (desktop, iOS, web). Declarative UI framework with similar shape:

@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }
    Button(onClick = { count++ }) {
        Text("Count: $count")
    }
}

mutableStateOf is React's useState-equivalent. @Composable functions are pure renders of state. Effects via LaunchedEffect. The pattern's Elm-flavoured even though Kotlin syntax doesn't make it explicit.

For more architectural rigor, Compose users adopt MVI (Model-View-Intent) — explicit reducers and intents, identical to Elm's Update/Msg.

SwiftUI (Apple)

struct Counter: View {
    @State var count = 0
    var body: some View {
        Button("Count: \(count)") { count += 1 }
    }
}

@State is React's useState. body is the view as a function of state. SwiftUI is reactive in the Elm sense — state changes trigger view recomputation. Less explicit reducers in idiomatic SwiftUI, but the pattern is there. The Composable Architecture (TCA) by Point-Free pushes SwiftUI toward strict Elm/MVU.

Flutter (Dart)

Flutter's setState model is React-class-style. The community-built BLoC (Business Logic Component) and Riverpod patterns push Flutter toward Elm-shaped reducers. Redux for Flutter ports Redux directly.

Idiomatic Flutter is more imperative than Elm; the patterns exist for those who want them.

.NET — Elmish, F#'s Fable

Elmish is the Elm Architecture for F#, originally targeting Fable (F#-to-JS). Direct port:

type Model = { Count: int }

type Msg =
    | Increment
    | Decrement

let init () = { Count = 0 }, Cmd.none

let update msg model =
    match msg with
    | Increment -> { model with Count = model.Count + 1 }, Cmd.none
    | Decrement -> { model with Count = model.Count - 1 }, Cmd.none

let view model dispatch =
    div [] [
        button [ OnClick (fun _ -> dispatch Decrement) ] [ str "-" ]
        str (string model.Count)
        button [ OnClick (fun _ -> dispatch Increment) ] [ str "+" ]
    ]

Used in Fable React apps, Avalonia desktop apps, MAUI mobile. F# is one of the few mainstream languages where Elmish feels native (functional first, sum types, pattern matching).

Vue (JavaScript)

Vue 3's Composition API is reactive but not strictly TEA-shaped. Vuex (the official state management library) has reducers; Pinia (its successor) is simpler.

The community is moving toward more reducer-based patterns; "stores" in Vue 3 + Pinia look like Redux slices.

Other notable cases

What this teaches

The pattern doesn't depend on the language. It doesn't depend on syntax. It doesn't depend on a framework — you can hand-roll it (Lazydap does, in 50 lines of Rust). What matters is the discipline:

Wherever those three hold, you have an Elm-flavoured app. The language and framework are details.

Why this is worth knowing

If you're picking a framework for a new reactive app, the choice is less about TEA-vs-not (almost everything modern is TEA-flavoured) and more about:

Don't expect "but it doesn't have proper TEA" to be a real differentiator in 2026. The frameworks have all converged. The discipline is portable.

See also