Buttons

Buttons turn user intent into actions. WaterUI’s button helper mirrors the ergonomics of SwiftUI while keeping the full power of Rust’s closures. This chapter explains how to build buttons, capture state, coordinate with the environment, and structure handlers for complex flows.

Anatomy of a Button

button(label) returns a Button view. The label can be any view—string literal, Text, or a fully custom composition. Attach behaviour with .action or .action_with.

use waterui::prelude::*;

fn simple_button() -> impl View {
    button("Click Me").action(|| {
        println!("Button was clicked!");
    })
}

Behind the scenes, WaterUI converts the closure into a HandlerFn. Handlers can access the Environment or receive state via .action_with.

Working with State

Buttons often mutate reactive state. Use action_with to borrow a binding without cloning it manually.

use waterui::prelude::*;
use waterui::reactive::binding;

fn counter_button() -> impl View {
    let count = binding(0);

    vstack((
        text!("Count: {count}"),
        button("Increment").action_with(&count, |binding| binding.increment(1)),
    ))
}

.action_with(&binding, handler) clones the binding for you (bindings are cheap handles). Inside the handler you can call any of the convenience methods exposed by nami (.increment, .toggle, .push, .update, …).

Passing Data into Handlers

Handlers can receive additional state or values from the environment in any order. Compose them with other extractors using tuples:

use waterui::prelude::*;
use waterui::core::extract::{Use, UseEnv};

#[derive(Clone)]
struct Analytics;

fn delete_button(item_id: Binding<Option<u64>>) -> impl View {
    button("Delete")
        .action_with(&item_id, |id, (Use(analytics), UseEnv(env)): (Use<Analytics>, UseEnv<Environment>)| {
            if let Some(id) = id.get() {
                analytics.track_delete(id);
                env.log("Item deleted");
            }
        })
}

Tip: Extractors live in waterui::core::extract. They let you pull services (analytics, database pools, etc.) from the environment at the moment the handler runs.

Custom Labels and Composition

Because labels are just views, you can craft rich buttons with icons, nested stacks, or dynamic content.

use waterui::prelude::*;
use waterui::component::layout::{padding::EdgeInsets, stack::hstack};

fn hero_button() -> impl View {
    button(
        hstack((
            text("🚀"),
            text("Launch")
                .size(18.0)
                .padding_with(EdgeInsets::new(0.0, 0.0, 0.0, 8.0)),
        ))
        .padding()
    )
    .action(|| println!("Initiating launch"))
}

You can nest buttons inside stacks, grids, navigation views, or conditionals—WaterUI treats them like any other view.

Guarding Actions

WaterUI does not currently ship a built-in .disabled modifier. Instead, guard inside the handler or wrap the button in a conditional.

use waterui::widget::condition::when;

fn guarded_submit(can_submit: Computed<bool>) -> impl View {
    when(can_submit.clone(), || {
        button("Submit").action(|| println!("Submitted"))
    })
    .or(|| text("Complete all fields to submit"))
}

For idempotent operations, simply return early:

button("Pay")
    .action_with(&payment_state, |state| {
        if state.is_processing() {
            return;
        }
        state.begin_processing();
    });

Asynchronous Workflows

Handlers run on the UI thread. When you need async work, hand it off to a task:

use waterui::prelude::*;
use waterui::task::task;

fn refresh_button() -> impl View {
    button("Refresh").action(|| {
        task(async {
            let data = fetch_from_api().await;
            update_store(data);
        });
    })
}

task spawns onto the executor configured for your app (see the task chapter). Keep the handler lightweight—schedule work and return.

Best Practices

  • Keep handlers pure – Avoid blocking IO or heavy computation directly in the closure.
  • Prefer action_with – It guarantees the binding lives long enough and stays reactive.
  • Think environment-first – Use extractors when a button needs shared services.
  • Make feedback visible – Toggle UI state with bindings (loading spinners, success banners) so the user sees progress.

Buttons may look small, but they orchestrate the majority of user journeys. Combine them with the layout and state tools covered elsewhere in this book to build polished, responsive workflows.