water-rs/waterui

Bring your app to all platform, learning once and applying anywhere

Rust

523

7,387 commits

updated Oct 19, 2026

See the code

README

WaterUI logo

WaterUI

Bring your app to all platform, learning once and applying anywhere.

crates.io version docs.rs documentation MIT or Apache 2.0 license code coverage

WaterUI is a cross-platform UI framework for Rust. You write views once, and each backend maps them onto whatever the platform actually uses: UIKit and AppKit on Apple, GTK4 on Linux. Where no native toolkit fits, Hydrolysis is the self-drawn renderer: it draws through Android's hardware-accelerated renderer (HWUI) on Android and on the GPU through Cherenkov elsewhere.

State is plain values. Put mutable state in a Binding, derive from it with Computed, and hand those to views. When a value changes, the views that read it update. There is no virtual tree to diff, and changing one string never rebuilds the subtree around it.

Getting started

Install the water CLI. The installer downloads a prebuilt binary; nothing is compiled:

# macOS and Linux
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/water-rs/cli/releases/latest/download/waterui-cli-installer.sh | sh
# Windows
powershell -ExecutionPolicy Bypass -c "irm https://github.com/water-rs/cli/releases/latest/download/waterui-cli-installer.ps1 | iex"

Then create and run a project:

water create counter
cd counter
water run

water update upgrades the CLI in place, and water update --check reports the newest release without installing it.

A WaterUI project is a plain Rust crate; the CLI keeps native projects out of your source tree and manages them on demand. src/lib.rs looks like this:

use waterui::app::App;
use waterui::prelude::*;
use waterui::preview;

#[preview]
fn main() -> impl View {
    let count = Binding::i32(0);

    vstack((
        text("Hello, WaterUI!").size(28),
        text!("Count: {count}"),
        stepper("Count", &count),
    ))
    .padding()
}

pub fn app(env: Environment) -> App {
    App::new(main, env)
}

That's the whole entry point. The app crate depends on waterui and nothing else; FFI glue and backend projects are generated and owned by the CLI.

water run targets the current host by default. To run somewhere else:

water run --platform ios
water run --platform android
water run --platform linux

water doctor tells you what's missing from a toolchain, and water devices lists simulators and connected devices.

Previews

Any function marked #[preview] can be rendered to an image without launching the app:

water preview main --output preview.png
water preview main --frame 800x600 --output preview.png

The same surface drives water preview test for semantic interaction tests and water preview perf for GPU measurements.

Shipping a real app

The same project you iterate on is the one that ships: the CLI generates and manages the platform projects, and builds, signs and packages the app from the declarations in Water.toml:

water create my-app
cd my-app
water run --platform ios

Water.toml holds package metadata, permissions, and theming. The backend is chosen per command — Android builds use Hydrolysis, which you can select explicitly with --backend hydrolysis.

To give the app an icon, drop a square Icon.svg or Icon.png into assets/. The CLI renders every platform format from that one file: full-bleed squares for iOS, the rounded-rect shape for macOS, and adaptive icon layers for Android, so the artwork survives each platform's mask. New projects start with the WaterUI logo there until you replace it.

State

Binding<T> is mutable state, Computed<T> is derived state. Signal-aware APIs take either, and only their readers update:

use waterui::prelude::slider::slider;
use waterui::prelude::*;

fn progress_editor() -> impl View {
    let value = Binding::f64(0.25);
    let percent = value.map(|value| value * 100.0);

    vstack((
        slider("Progress", &value).range(0.0..=1.0),
        text!("Progress: {percent:.0}%"),
    ))
}

Collections work the same way. Give List or ForEach a reactive collection of Identifiable items and membership changes are diffed by id:

use waterui::prelude::*;
use waterui::Identifiable;

#[derive(Clone, Identifiable)]
struct Contact {
    #[id]
    id: u64,
    name: &'static str,
}

fn contacts() -> impl View {
    let contacts = [
        Contact { id: 1, name: "Alice Chen" },
        Contact { id: 2, name: "Bob Smith" },
    ];

    List::for_each(contacts, |contact| ListItem::new(text(contact.name)))
}

One thing to know early: watch replaces the subtree it wraps, losing any state inside it. It exists for intentional structural swaps. For routine updates, pass signals into components and let the framework do the precise thing.

Backends

TargetBackendRenders through
iOS and macOSAppleUIKit / AppKit
AndroidHydrolysisSelf-drawn, HWUI RenderNode display lists
LinuxGTK4GTK4 widgets
macOS, Linux, Windows, webHydrolysisSelf-drawn, GPU (Cherenkov)

Status

Pre-1.0. The API still moves, and we break it on purpose when a better shape is found. The Apple backend is the most complete; Hydrolysis, which also serves Android, is close behind; GTK4 is younger. If you hit a wall, an issue with a small reproduction is genuinely useful.

Examples

  • Gallery — broad component showcase
  • Form — derived forms and reactive field projection
  • Navigation — stacks, tabs, and navigation state
  • Flow Markdown — streaming Markdown rendering
  • Map — map component across platform realizations
  • Video player — playback state and controls
  • Filter — GPU image filters

To run one from a checkout:

cargo install --locked --path cli
cd examples/gallery
water run --platform macos

Repository layout

  • core/ — View, Environment, layout contracts, reactive integration
  • components/ — layouts, text, controls, forms, navigation, media, charts, and friends
  • backends/ — the mainline backends: Apple (UIKit/AppKit) and Hydrolysis (self-drawn, and the Android backend)
  • cli/ — waterui-cli, the water command and project generators (a workspace member)
  • testing/ — semantic UI testing over the accessibility tree
  • examples/ — runnable applications

Documentation

Contributing

Target the dev branch; main is for releases. For anything substantial, open an issue first so the design can be discussed before you sink time into it. AI-assisted contributions are welcome as long as a human understands and reviews the result; fully autonomous pull requests are not accepted.

License

MIT or Apache 2.0, at your option. See LICENSE-MIT and LICENSE-APACHE.

water-rs/waterui

Bring your app to all platform, learning once and applying anywhere

Rust

523

7,387 commits

updated Oct 19, 2026

See the code

README

WaterUI logo

WaterUI

Bring your app to all platform, learning once and applying anywhere.

crates.io version docs.rs documentation MIT or Apache 2.0 license code coverage

WaterUI is a cross-platform UI framework for Rust. You write views once, and each backend maps them onto whatever the platform actually uses: UIKit and AppKit on Apple, GTK4 on Linux. Where no native toolkit fits, Hydrolysis is the self-drawn renderer: it draws through Android's hardware-accelerated renderer (HWUI) on Android and on the GPU through Cherenkov elsewhere.

State is plain values. Put mutable state in a Binding, derive from it with Computed, and hand those to views. When a value changes, the views that read it update. There is no virtual tree to diff, and changing one string never rebuilds the subtree around it.

Getting started

Install the water CLI. The installer downloads a prebuilt binary; nothing is compiled:

# macOS and Linux
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/water-rs/cli/releases/latest/download/waterui-cli-installer.sh | sh
# Windows
powershell -ExecutionPolicy Bypass -c "irm https://github.com/water-rs/cli/releases/latest/download/waterui-cli-installer.ps1 | iex"

Then create and run a project:

water create counter
cd counter
water run

water update upgrades the CLI in place, and water update --check reports the newest release without installing it.

A WaterUI project is a plain Rust crate; the CLI keeps native projects out of your source tree and manages them on demand. src/lib.rs looks like this:

use waterui::app::App;
use waterui::prelude::*;
use waterui::preview;

#[preview]
fn main() -> impl View {
    let count = Binding::i32(0);

    vstack((
        text("Hello, WaterUI!").size(28),
        text!("Count: {count}"),
        stepper("Count", &count),
    ))
    .padding()
}

pub fn app(env: Environment) -> App {
    App::new(main, env)
}

That's the whole entry point. The app crate depends on waterui and nothing else; FFI glue and backend projects are generated and owned by the CLI.

water run targets the current host by default. To run somewhere else:

water run --platform ios
water run --platform android
water run --platform linux

water doctor tells you what's missing from a toolchain, and water devices lists simulators and connected devices.

Previews

Any function marked #[preview] can be rendered to an image without launching the app:

water preview main --output preview.png
water preview main --frame 800x600 --output preview.png

The same surface drives water preview test for semantic interaction tests and water preview perf for GPU measurements.

Shipping a real app

The same project you iterate on is the one that ships: the CLI generates and manages the platform projects, and builds, signs and packages the app from the declarations in Water.toml:

water create my-app
cd my-app
water run --platform ios

Water.toml holds package metadata, permissions, and theming. The backend is chosen per command — Android builds use Hydrolysis, which you can select explicitly with --backend hydrolysis.

To give the app an icon, drop a square Icon.svg or Icon.png into assets/. The CLI renders every platform format from that one file: full-bleed squares for iOS, the rounded-rect shape for macOS, and adaptive icon layers for Android, so the artwork survives each platform's mask. New projects start with the WaterUI logo there until you replace it.

State

Binding<T> is mutable state, Computed<T> is derived state. Signal-aware APIs take either, and only their readers update:

use waterui::prelude::slider::slider;
use waterui::prelude::*;

fn progress_editor() -> impl View {
    let value = Binding::f64(0.25);
    let percent = value.map(|value| value * 100.0);

    vstack((
        slider("Progress", &value).range(0.0..=1.0),
        text!("Progress: {percent:.0}%"),
    ))
}

Collections work the same way. Give List or ForEach a reactive collection of Identifiable items and membership changes are diffed by id:

use waterui::prelude::*;
use waterui::Identifiable;

#[derive(Clone, Identifiable)]
struct Contact {
    #[id]
    id: u64,
    name: &'static str,
}

fn contacts() -> impl View {
    let contacts = [
        Contact { id: 1, name: "Alice Chen" },
        Contact { id: 2, name: "Bob Smith" },
    ];

    List::for_each(contacts, |contact| ListItem::new(text(contact.name)))
}

One thing to know early: watch replaces the subtree it wraps, losing any state inside it. It exists for intentional structural swaps. For routine updates, pass signals into components and let the framework do the precise thing.

Backends

TargetBackendRenders through
iOS and macOSAppleUIKit / AppKit
AndroidHydrolysisSelf-drawn, HWUI RenderNode display lists
LinuxGTK4GTK4 widgets
macOS, Linux, Windows, webHydrolysisSelf-drawn, GPU (Cherenkov)

Status

Pre-1.0. The API still moves, and we break it on purpose when a better shape is found. The Apple backend is the most complete; Hydrolysis, which also serves Android, is close behind; GTK4 is younger. If you hit a wall, an issue with a small reproduction is genuinely useful.

Examples

  • Gallery — broad component showcase
  • Form — derived forms and reactive field projection
  • Navigation — stacks, tabs, and navigation state
  • Flow Markdown — streaming Markdown rendering
  • Map — map component across platform realizations
  • Video player — playback state and controls
  • Filter — GPU image filters

To run one from a checkout:

cargo install --locked --path cli
cd examples/gallery
water run --platform macos

Repository layout

  • core/ — View, Environment, layout contracts, reactive integration
  • components/ — layouts, text, controls, forms, navigation, media, charts, and friends
  • backends/ — the mainline backends: Apple (UIKit/AppKit) and Hydrolysis (self-drawn, and the Android backend)
  • cli/ — waterui-cli, the water command and project generators (a workspace member)
  • testing/ — semantic UI testing over the accessibility tree
  • examples/ — runnable applications

Documentation

Contributing

Target the dev branch; main is for releases. For anything substantial, open an issue first so the design can be discussed before you sink time into it. AI-assisted contributions are welcome as long as a human understands and reviews the result; fully autonomous pull requests are not accepted.

License

MIT or Apache 2.0, at your option. See LICENSE-MIT and LICENSE-APACHE.

Project health

crates.io

Latest release

0.5.2 · Sep 22, 2026

Releases this year

8

Significant stargazers

Alec Larson

957 followers · starred Aug 2026

Bartosz Kaszubowski

469 followers · starred Sep 2026

Fangdun Tsai

1,063 followers · starred Dec 2025

Alex

1,976 followers · starred Dec 2025