Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Your First WaterUI App

Now that your development environment is set up, let's build your first interactive WaterUI application! We'll create a counter app that demonstrates the core concepts of views, state management, and user interaction.

What We'll Build

Our counter app will feature:

  • A display showing the current count
  • Buttons to increment and decrement the counter
  • A reset button
  • Dynamic styling based on the counter value

By the end of this chapter, you'll understand:

  • How to create interactive views
  • How to manage reactive state
  • How to handle user events
  • How to compose views together

Setting Up the Project

If you completed the setup chapter you already have a CLI-generated workspace. Otherwise scaffold one now:

water create --name "Counter App" \
  --directory counter-app \
  --bundle-identifier com.example.counterapp \
  --backend web \
  --yes --dev
cd counter-app

We will edit src/lib.rs so the shared code can run on any backend the CLI installed.

Building the Counter Step by Step

Let's build our counter app incrementally, learning WaterUI concepts along the way.

Step 1: Basic Structure

Start with a simple view structure. Since our initial view doesn't need state, we can use a function:

Filename: src/lib.rs

#![allow(unused)]
fn main() {
use waterui::prelude::*;

pub fn counter() -> impl View {
    "Counter App"
}
}

Run this to make sure everything works:

water run --platform web --project counter-app

You should see a window with "Counter App" displayed.

Step 2: Adding Layout

Now let's add some layout structure using stacks:

#![allow(unused)]
fn main() {
use waterui::prelude::*;

pub fn counter() -> impl View {
    vstack((
        "Counter App",
        "Count: 0",
    ))
}
}

Note: vstack creates a vertical stack of views. We'll learn about hstack (horizontal) and zstack (overlay) later.

Step 3: Adding Reactive State

Now comes the exciting part - let's add reactive state! We'll use the re-exported binding helper together with Binding's convenience methods and the text! macro for reactive text:

#![allow(unused)]
fn main() {
use waterui::prelude::*;
use waterui::reactive::binding;
use waterui::Binding;

pub fn counter() -> impl View {
    let count: Binding<i32> = binding(0);
    vstack((
        "Counter App",
        text!("Count: {count}"),
        hstack((
            button("- Decrement").action_with(&count, |state: Binding<i32>| {
                state.set(state.get() - 1);
            }),
            button("+ Increment").action_with(&count, |state: Binding<i32>| {
                state.set(state.get() + 1);
            }),
        )),
    ))
}
}

water run --platform web --project counter-app will hot reload changes—save the file and keep the terminal open to see updates instantly.

Understanding the Code

Let's break down the key concepts introduced:

Reactive State with binding

#![allow(unused)]
fn main() {
use waterui::reactive::binding;
use waterui::Binding;
pub fn make_counter() -> Binding<i32> {
    binding(0)
}
}

This creates a reactive binding with an initial value of 0. When this value changes, any UI elements that depend on it will automatically update.

Reactive Text Display

#![allow(unused)]
fn main() {
use waterui::prelude::*;
use waterui::reactive::binding;
use waterui::Binding;
pub fn reactive_label() -> impl View {
    let count: Binding<i32> = binding(0);
    text!("Count: {count}")
}
}
  • The text! macro automatically handles reactivity
  • The text will update whenever count changes

Event Handling

#![allow(unused)]
fn main() {
use waterui::prelude::*;
use waterui::reactive::binding;
use waterui::Binding;
pub fn decrement_button() -> impl View {
    let count: Binding<i32> = binding(0);
    button("- Decrement").action_with(&count, |count: Binding<i32>| {
        count.set(count.get() - 1);
    })
}
}
  • .action_with() attaches an event handler with captured state
  • Binding<i32>::decrement and Binding<i32>::increment provide ergonomic arithmetic updates without manual closures

Layout with Stacks

#![allow(unused)]
fn main() {
use waterui::prelude::*;
pub fn stack_examples() -> impl View {
    vstack((
        text("First"),
        hstack((text("Left"), text("Right"))),
    ))
}
}

Stacks are the primary layout tools in WaterUI, allowing you to arrange views vertically or horizontally.