Write once, render native.
A write-once UI language for native iOS & Android. Edit .flux source and watch it
hot-reload on-device in milliseconds as binary patches over WebSocket β then compile the exact
same code to idiomatic SwiftUI and Jetpack Compose for release.
No webview. No JS bridge. No interpreter in production.
Note
Flux is under active development. The Rust workspace β 16 crates, ~63k lines β is end-to-end functional: parse β typecheck β lower β diff β patch β VM, plus native codegen for both platforms. On Android, dev renders through the same declarative components the release codegen emits; the iOS dev tier is still imperative (UIKit) and convergence is gated on measurement (ADR-0048).
- Why Flux
- Features
- Architecture
- Getting Started
- Usage
- Language Tour
- Development
- Testing
- Continuous Integration
- Documentation
- Acknowledgements
| Traditional cross-platform | Flux | |
|---|---|---|
| Render target | WebView / canvas | Real UIView / Compose View |
| Hot reload | Full rebuild or JS eval | Binary IR patch over WebSocket |
| Release build | Interpreter shipped in prod | SwiftUI / Compose codegen |
| State | Framework-locked | SolidJS-style signal graph over one shared IR |
The dev loop and the release artifact come from the same Reactive Tree IR. You iterate
against a fast bytecode VM host during development and ship platform-native code in release β
no fork in the road, no "dev looks different from prod." A parity harness
(flux-parity) continuously proves that dev-VM execution and release codegen stay equivalent,
so the two paths are verified, not assumed.
compocomponents with typed props,recorddata types, localstatesignals, closures (|| { ... },fn(v) { ... }), andForEachwith key functions.- SolidJS-style reactivity β assignments to state signals propagate through a fine-grained
dependency graph;
$bindingtwo-way bindings for text inputs. - Navigation built in:
Router/Screen/RouterNavwith named routes. - 28-component standard library β
Text,Button,Column,Row,Stack,Grid,ScrollView,TextField,TextInput,Toggle,Switch,Slider,Picker,DatePicker,Image,Modal,Sheet,Dialog,SafeArea,Spacer, and more β plusprelude,traits, and capability modules. - Platform capabilities for storage, network, and device services through a versioned capability contract, resolved identically on server and both hosts.
- Hand-written lexer + recursive-descent parser with error recovery; every error carries a
source
Span. - Type checker (
flux-types) over the parse tree. - Reactive Tree IR (
flux-ir) β arena-allocated, stableu32node IDs, aClosureIRbytecode table, and anInstanceRegistrythat lets hosts preserve component state across hot swaps. - Structural differ (
flux-differ) β minimal edit scripts, serialized as MessagePack binary patches (flux-ir-serde) with blake3 content-addressed interning. - Register-based bytecode VM β the
flux-vm-refcrate is the dependency-light behavioral oracle for the ISA; the shipping Swift (FluxBytecodeVM) and Kotlin VMs are validated against 92 golden ISA vectors undertests/isa-vectors/. - Capability calls (
CALL_CAP) return result-cell signals (Ready/Pending/Error); async capabilities settle via an injectedAsyncResolver(ADR-0044/0045). - Native codegen β a data-driven shared emitter (
flux-codegen-corewith aBackendtrait and primitive registry) drives the SwiftUI and Jetpack Compose backends, with a node-ID bridge so generated views keep the exact identity the VM used in dev.
fluxCLI β scaffold, dev server, native builds, formatter, diagnostics emitter, stdlib schema, environment doctor.- Hot-reload dev server (
flux-devserver) β file watching, lower + diff on every save, WebSocket patch channel on:7331, HTTP asset server on:7332, optional LAN pairing tokens. - Language Server (
flux-lsp, built onasync-lsp) plus a VS Code extension with syntax highlighting, diagnostics, hot-reload status, and "Run on device". - DevTools desktop app (
flux-devtools-ui) β a gpui-based GUI rendering live telemetry over the same wire protocol (ADR-0041). - Parity harness (
flux-parity) β trace capture, reduction, and equivalence checking between dev-VM execution and release codegen output. - Perf harness (
flux-perf-harness) β render-performance measurement against the project's latency budgets.
main.flux
β
βΌ
βββββββββββββββ βββββββββββββββ ββββββββββββββββββββββββ
β flux-parser ββββΆβ flux-types ββββΆβ flux-ir β
β (lex/parse) β β (typecheck) β β (Reactive Tree IR) β
βββββββββββββββ βββββββββββββββ ββββββββββββ¬ββββββββββββ
β
ββββββββββββββββββββββββββββ΄βββββββββββββββ
βΌ dev βΌ release
ββββββββββββββββ βββββββββββββββββ βββββββββββββββββββββββββββ
β flux-differ ββββΆβ flux-ir-serde β β flux-codegen-core β
β (tree diff) β β (MsgPack patchβ β (data-driven emitter) β
ββββββββββββββββ βββββββββ¬ββββββββ βββββββββββββ¬ββββββββββββββ
β WebSocket :7331 β
βΌ βββΆ flux-codegen-swift β SwiftUI
ββββββββββββββββββββ βββΆ flux-codegen-kotlin β Compose
β Host app VM + β
β signal graph + β βββ validated against
β shadow tree β 92 golden ISA vectors
β (Swift / Kotlin) β (tests/isa-vectors/)
ββββββββββββββββββββ
Node IDs are stable, content-derived FNV-1a-32 hashes β never sequential. Two tag families keep expression and declaration IDs disjoint, and the codegen backends thread the exact same family tags through their node-ID bridges. IDs are stable across edits where structure doesn't change, which is what lets a hot swap preserve component state.
Prop indices are FNV-1a-32 of the prop name masked to u16, derived identically by the host
kits (PropsIndex.propIndexForName on Android). Indices are never hardcoded β a hardcoded
index desyncs from the server and renders silently blank UI.
- The shadow tree is not a tree of raw native views. Each
ShadowNodeholds its materialized props in a platform observable: on Android, props live in a ComposeMutableState(injected viapropsStateFactory, because a plainvaris invisible to snapshot tracking); on iOS the tree is observed natively. - Keyed reconciliation matches children by
nodeIdβ an existing node is reordered, never recreated β preserving scroll position, text field contents, and screen state across diffs and router push/pop. - Three contracts are normative and versioned: the wire protocol (Appendix D), the VM
ISA (Appendix E), and the adapter contracts (Appendix F) in
docs/spec/mlp-appendices.md.
- Rust β the toolchain is pinned to an exact nightly (
nightly-2026-08-28) viarust-toolchain.toml;rustupwill provision it automatically on your first build. A pinned nightly is required because the gpui-based DevTools crate uses unstable std features; the declaredrust-version = "1.86"is the edition-2024 language floor. - cargo-nextest β the project's test runner.
- Xcode (iOS 16+ deployment target) and/or the Android SDK β only for the host apps.
Tip
Everything on the Rust side β dev server, parser, differ, VM oracle, codegen, parity harness, benchmarks β builds and runs without Xcode or the Android SDK.
git clone https://github.com/elcoosp/flux
cd flux
cargo build --release # the CLI lands in target/release/flux
# Format + lint must be clean
cargo fmt -- --check
cargo clippy -- -D warnings
# Full test suite (nextest, never plain `cargo test`)
cargo nextest run
cargo test --doc # doctests (nextest does not run these)cargo build -p flux-cli
flux dev --root ./examples/todoOpen runtimes/ios in Xcode or build the Android host with
./gradlew :runtimes:android:build, point it at ws://<host>:7331, and edit
examples/todo/main.flux β patches land on-device in milliseconds.
| Command | Purpose |
|---|---|
flux init <name> |
Scaffold a new Flux project at <name>/. |
flux dev [--root] [--ws-host] [--ws-port] [--http-port] [--token] |
Start the hot-reload dev server (WS :7331, HTTP :7332 by default). |
flux build ios|android [--root] |
Codegen the project to platforms/<platform>/Generated/, then invoke the native toolchain when present. |
flux fmt [<files>...] [--check] |
Format .flux sources to canonical style; --check exits non-zero on drift (CI gating). |
flux lsp <file> [--types] |
Emit parse + type-check diagnostics as JSON. |
flux doc |
Emit a JSON schema of the stdlib API to stdout. |
flux doctor [--strict] |
Environment health check: toolchain, stdlib parse, wire protocol version, dependency drift; --strict exits non-zero on findings. |
flux dev --root ./my-app # serve a project (defaults to cwd)
flux dev --ws-host 0.0.0.0 --token <secret> # expose on the LAN, require pairingHosts authenticate with the token read from FLUX_DEV_TOKEN or [dev] token in flux.toml
during the Hello handshake. Without --token, localhost keeps an open dev loop.
[project]
name = "todo"
entry = "main.flux"
[dev]
ws_port = 7331
http_port = 7332A real component from examples/todo/main.flux:
record Task { label: String, done: Bool, }
compo TaskRow(task: Task, tasks: List[Task])
Row gap: 8.0
Text text: task.label
Button text: "Remove", onPress: || { tasks.remove(task) }
compo TodoApp
state tasks: List[Task] = [
Task(label: "Buy milk", done: false),
Task(label: "Walk dog", done: false),
]
state newTask: String = ""
Router initialRouteName: "tasks"
Screen route: "tasks"
Column gap: 12.0
Text text: "Flux To-Do"
Row gap: 8.0
TextInput text: $newTask, onChangeText: fn(v) { newTask = v }
Button text: "Add", onPress: || {
tasks.append(Task(label: newTask, done: false))
newTask = ""
}
Column gap: 8.0
ForEach(tasks, key: fn(t) { t.label }) { item => TaskRow(task: item, tasks: tasks) }
Screen route: "about"
Column gap: 12.0
Button text: "Back", onPress: || { RouterNav.navigate("tasks") }
More examples live in examples/: counter (minimal state + patch flow) and
router (multi-screen navigation).
flux/
βββ crates/ # Rust workspace (16 crates)
β βββ flux-syntax/ # Spans, NodeId, FNV-1a node-id hashing, value/patch vocab
β βββ flux-parser/ # Hand-written lexer + recursive-descent parser
β βββ flux-types/ # Type checker
β βββ flux-ir/ # Reactive Tree IR: arena, ClosureIR, InstanceRegistry
β βββ flux-ir-serde/ # Binary patch (de)serialization (MessagePack, blake3)
β βββ flux-differ/ # Structural tree differ
β βββ flux-vm-ref/ # Reference register VM (test oracle, not the shipping VM)
β βββ flux-devserver/ # Hot-reload pipeline + WebSocket + HTTP asset server
β βββ flux-codegen-core/ # Shared data-driven emitter (Backend trait, primitives)
β βββ flux-codegen-swift/ # SwiftUI codegen (node-ID bridge)
β βββ flux-codegen-kotlin/ # Jetpack Compose codegen (node-ID bridge)
β βββ flux-cli/ # The `flux` binary
β βββ flux-parity/ # Dev VM == release codegen parity harness
β βββ flux-lsp/ # Language server (async-lsp)
β βββ flux-devtools-ui/ # gpui DevTools desktop app
β βββ flux-perf-harness/ # Render-perf benchmark harness
βββ runtimes/ # Host apps: ios/ (Swift 6, XcodeGen) Β· android/ (Gradle)
βββ adapters/ # Platform adapter implementations (contract version 1)
βββ stdlib/ # 28 .flux stdlib components + prelude/traits/capabilities
βββ examples/ # counter Β· router Β· todo
βββ editors/vscode/ # VS Code extension
βββ tests/isa-vectors/ # 92 golden ISA vectors shared by Rust + Swift + Kotlin VMs
βββ docs/spec/ # mlp-spec.md + mlp-appendices.md (AβG)
βββ docs/adr/ # 39 Architecture Decision Records (MADR)
βββ website/ # Astro documentation site
#![forbid(unsafe_code)]in every library crate; nounwrap/expect/panic!in production code; every error carries a sourceSpan.cargo fmtandcargo clippy -- -D warningsmust be clean before commit.- Dependencies are vetted through ADRs β
flux doctorreports unapproved drift. - Release profile ships with
lto = "thin"andcodegen-units = 1.
Every public function has a test. The suite spans five layers, run by cargo nextest:
| Layer | Tool | What it proves |
|---|---|---|
| Unit | cargo nextest run |
Every function, edge cases, error paths |
| Property | proptest |
Node-ID stability, diff minimality, round-trips |
| Snapshot | insta |
Swift & Kotlin codegen output |
| Benchmark | criterion |
Per-stage latency budgets |
| Parity | tests/ |
Dev VM execution == release codegen |
Enforced by cargo bench β the headline metric is the Save β pixels budget, the
wall-clock a developer feels during hot reload, measured end-to-end by the LANE-H harness
(a real DevServer plus a headless loopback WebSocket client, β₯ 50 edits per tree size,
p50/p99):
| Stage | Budget |
|---|---|
| Parse a 500-line file | < 5 ms |
| Diff a 50-node tree | < 1 ms |
| Serialize a 50-node patch | < 1 ms |
| VM eval per frame | < 2 ms |
19 GitHub Actions workflows guard main:
| Workflow | Purpose |
|---|---|
rust-check.yml |
cargo fmt / clippy / nextest |
ios-check.yml |
Swift build + test of the iOS host |
android-check.yml |
Kotlin build + test of the Android host |
parity-check.yml |
Dev VM == release codegen equivalence |
codegen-compile.yml |
Generated Swift/Kotlin compiles |
stdlib-contract.yml |
Stdlib component contract checks |
wire-fuzz.yml |
Fuzzes the wire-protocol (de)serialization |
compat-matrix.yml |
Cross-version compatibility matrix |
mutation-testing.yml |
Mutation testing |
benchmarks.yml |
Criterion benchmark suite |
perf-harness.yml |
Render-perf suite |
size-gate.yml |
Artifact size budgets |
release-gate.yml |
Pre-release gate |
artifact-publish.yml |
Publishes release artifacts |
devtools-nightly-canary.yml |
Probes new nightlies against the pinned gpui |
vscode-check.yml |
Builds/checks the VS Code extension |
website-check.yml |
Builds/checks the docs site |
adr-numbering.yml |
Enforces ADR numbering discipline |
manifest-steward.yml |
Keeps the workspace manifest consistent |
- Specification:
docs/spec/mlp-spec.mdanddocs/spec/mlp-appendices.mdβ grammar, IR schema, wire protocol (Appendix D), VM ISA (Appendix E), adapter contracts (Appendix F), glossary. - Architecture decisions:
docs/adr/β 39 MADR records (through ADR-0058). - Contributor manual:
AGENTS.mdβ workflow, quality bar, and house rules. - Docs site:
website/β Astro source (pnpm install && pnpm dev). - Benchmarks:
benches/andcrates/flux-perf-harness/.
Flux builds on excellent open-source foundations:
- Zed's gpui and gpui-component β the DevTools desktop UI.
- Tokio, axum, and tokio-tungstenite β async runtime, HTTP, and WebSocket layers.
- clap β CLI surface.
- notify β file watching.
- blake3 and rmp-serde β content addressing and MessagePack.
- insta, proptest, and criterion β snapshot, property, and benchmark tooling.
- async-lsp and lsp-types β language server foundation.
Flux β write once, render native.