A declarative, markup-first UI framework for native desktop apps — a Rust ECS core, GPU rendering, and live hot reload of your markup, styles, and script.
Author a .lmn tree, an optional CSS-subset sheet, and an optional .rhai script. Save to reload — state preserved.
<root bg="#0c1c30" padding="32" gap="20" align="center" justify="center"> <label class="display" id="counter" text="0" bind-text="clicks" /> <row gap="14" justify="center"> <button class="primary" id="bump" text="+1" /> <button class="primary" id="reset" text="reset" /> </row> <script src="main.rhai" /> </root>
The things a UI developer actually notices: how soon a click paints, what the app costs while it sits there, and how a save lands.
An input marks the frame dirty and the very next redraw paints it — latency bounded by your display's refresh, nothing added on top.
The event loop parks between events. When nothing changes, nothing repaints — no polling, no background churn, the CPU sleeps.
Hot reload swaps markup, CSS, or script in place. Focus, scroll position, and signal values all survive the swap.
Architecture guarantees, verified in the winit event loop — not benchmark dumps. For throughput on your own hardware, run cargo bench -p benches.
Three files describe your app. Underneath, a two-world ECS core drives layout, styling, and GPU paint — and every save reloads without losing your place.
A fixed tag vocabulary, a familiar CSS-subset styling layer, and a small embedded scripting language for view logic and reactive signals.
Markup, CSS, and script all reload on save — preserving focus, scroll position, and signal values across the swap.
Every frame is composited as retained vector paths — crisp at any DPI, with full glyph shaping and flexbox & grid layout.
Name a signal, bind it with bind-text, and the loop closes: edits flow back, derived values flow out.
An accessibility tree, IME composition, and Unicode BiDi shaping for mixed left-to-right and right-to-left text.
An LSP server and formatter, an MCP server to inspect a running app, and a C ABI — with Rust, C++ & Python SDKs on top — to drive Lumen from other languages.
A signal is a named entry in a host-local store. A <label bind-text> reads it; a click handler writes it; the label re-renders. No view-model to wire by hand.
fn on_start() { on("click", "bump", "handle_bump"); on("click", "reset", "handle_reset"); } fn handle_bump(id) { let n = signal("clicks", 0); n.set(n.get() + 1); } fn handle_reset(id) { signal("clicks", 0).set(0); }
:root { --color-accent: #5fd9e0; --color-bg: #163459; --radius-pill: 24; } .display { text-align: center; font-size: 96; } .primary { bg: var(--color-bg); hover-bg: #1d4477; radius: var(--radius-pill); } .primary:focus { outline: 2 var(--color-accent); }
Lumen is pre-1.0 and honest about its edges. Everything on the left works now; everything on the right is on the roadmap, not yet built.
lumenc)lumenc add@media (prefers-color-scheme)The installer drops the prebuilt lumenc on your machine — no Rust toolchain, no sudo. Scaffold a starter and it opens live.