Pure Rust · Cross-Platform · Open Source

Build beautiful UIs
in Pure Rust

Snow UI is an elegant, declarative UI framework for Rust. One codebase, every platform—from desktop to mobile to web.

View on GitHub See Examples
6
Target Platforms
100%
Pure Rust
Apache 2.0
License

A fresh perspective on UI

"The UI is a projection of a virtual world onto the screen."

Snow UI models your application as a virtual world. Elements are objects in that world—each with properties, behaviors, and the ability to communicate via messages. Rendering is simply projecting that world onto the display.

Write once, run everywhere

Snow UI targets all major platforms from a single Rust codebase—no runtime, no VM, just native performance everywhere.

🪟
Windows
Desktop
🍎
macOS
Desktop
🐧
Linux
Desktop
🤖
Android
Mobile
📱
iOS
Mobile
🌐
Web
Browser (WASM)

Everything you need

Snow UI combines Rust's safety and performance with a modern, declarative API for building rich user interfaces.

🦀
Pure Rust

No JavaScript, no C++ glue, no foreign runtimes. Your UI is 100% Rust, compiled to native code (or WASM for the web).

🎯
Declarative API

Describe your UI with expressive macros like obj! and list!. Clear, readable code that maps directly to the visual structure.

Async-first

Built on Tokio, event handlers and actions are fully async. Network calls, timers, and animations are first-class citizens.

📦
Component Model

Define reusable elements with the #[element] macro. Compose complex UIs from simple, isolated building blocks.

📨
Message Passing

Decouple components with an event bus. Send typed messages between elements without tight coupling or shared mutable state.

🔄
Reactive State

Wrap values in State<T> and the UI automatically re-renders whenever state changes—no boilerplate required.

🌐
Built-in HTTP

ServerApi makes it easy to call REST endpoints. Pair it with Form for complete data-submission workflows.

🧩
Rich Widgets

Board, Card, Row, Text, Button, TextInput, Form, Switch, TextClock and more—a growing library of ready-to-use UI primitives.

⏱️
Timers & Intervals

IntervalTimer fires messages on a schedule. Build clocks, animations, and polling loops with minimal code.

See Snow UI in action

A few real examples from the Snow UI repository.

text_clock.rs
use snow_ui::prelude::*;

fn world() -> World {
    World {
        root: obj!(Board {
            width: VIEWPORT_WIDTH,
            height: VIEWPORT_HEIGHT,
            h_align: HAlign::Center,
            v_align: VAlign::Middle,
            children: list![Card {
                children: list![
                    Row {
                        children: list![Text {
                            text: "Clock Example ⏰",
                        }],
                    },
                    Row {
                        children: list![TextClock {
                            format: "%H:%M:%S",
                        }],
                    },
                ],
            }],
        }),
        ..default()
    }
}

fn main() {
    snow_ui::launch(world);
}
use snow_ui::prelude::*;
use tokio::time::Duration;

#[message]
struct SimpleTextTimerTickEvent {}

#[element]
struct SimpleTextTimer {
    seconds: State<u128>,
    timer: IntervalTimer<SimpleTextTimerTickEvent>,
}

register_handler!(
    impl MessageHandler<SimpleTextTimerTickEvent> for SimpleTextTimer {
        async fn handle(&mut self, _: &SimpleTextTimerTickEvent,
                        _: &mut MessageContext) {
            self.seconds.update(|s| *s += 1);
        }
    }
);

fn simple_text_timer() -> Object {
    obj!(SimpleTextTimer {
        seconds: State::new(0),
        timer: IntervalTimer::from_interval(
            Duration::from_secs(1)
        ),
    })
}

fn main() {
    snow_ui::launch(world);
}
use snow_ui::prelude::*;

#[message]
struct IncreaseButtonClicked {}

#[element]
struct IncreaseButton { button: Button }

impl ClickHandler for IncreaseButton {
    async fn on_click(&mut self) {
        event_bus().send(IncreaseButtonClicked {});
    }
}

#[element]
struct SimpleText { count: State<u128> }

register_handler!(
    impl MessageHandler<IncreaseButtonClicked> for SimpleText {
        async fn handle(&mut self, _: &IncreaseButtonClicked,
                        _: &mut MessageContext) {
            self.count.update(|c| *c += 1);
        }
    }
);

fn world() -> World {
    World {
        root: obj!(Board {
            children: list![Card {
                children: list![
                    Row { children: list![increase_button()], },
                    Row { children: list![simple_text()], },
                ],
            }],
        }),
        ..default()
    }
}

fn main() { snow_ui::launch(world); }
use snow_ui::prelude::*;

#[message]
struct LoginSuccess {}

async fn login(form: &Form) -> anyhow::Result<()> {
    let json = form.to_json()?;
    let api = ServerApi::new("https://api.example.com/login");
    api.post_json(&json).await?;
    event_bus().send(LoginSuccess {});
    Ok(())
}

fn login_board() -> Object {
    obj!(LoginBoard {
        board: Board {
            children: list![Form {
                submit_handler: login,
                submit_button: Button { text: "Login" },
                reset_button:  Button { text: "Reset" },
                children: list![
                    Row {
                        children: list![TextInput {
                            label: "Username",
                            name:  "username",
                            max_len: 20,
                        }],
                    },
                    Row {
                        children: list![TextInput {
                            label:  "Password",
                            name:   "password",
                            r#type: "password",
                            max_len: 20,
                        }],
                    },
                ],
            }],
        }
    })
}

fn main() { snow_ui::launch(world); }

Ready to build with Snow UI?

Star the project on GitHub and follow along as it grows.