Beginner 12 min

Build a counter with state

Store application state in a model, handle a click, and invalidate only the presentation that changed.

State lives in the model

The model is a normal Rust struct. Render receives &mut self and a Context tied to this presentation. There is no hidden global state or hook ordering.

src/counter.rs
#[derive(Default)]
struct Counter {
    count: u32,
}

impl Render for Counter {
    fn render(&mut self, cx: &mut Context<Self>) -> Element {
        let palette = default_theme(cx.environment());
        let theme = palette.resolve(cx.environment().color_scheme);

        Element::column([
            Element::text(format!("Count: {}", self.count)),
            Button::new("increment", "Increment", theme.button())
                .on_click(cx.callback(|app| {
                    app.count = app.count.saturating_add(1);
                }))
                .build(),
        ])
        .gap(12.0)
    }
}

Local callbacks invalidate automatically

Context::callback registers state-only work and invalidates this presentation after it returns. The button owns the activation binding, so pointer, touch, Enter, Space and accessibility click all reach the same callback.

The model stays in control

The handler changes ordinary Rust state; the next render rebuilds the controlled widget from that state. Handler identities remain opaque and closures stay in the runtime rather than inside cloneable Element values.

Reference files

These are the implementation and guide files used for this chapter.

crates/argui-widget-gallery/src/pages/hot_reload.rsdocs/runtime/models.md
Compiled example

A compiled counter running in WebAssembly

The Rust file below is imported verbatim by this page and compiled into the WebAssembly application running underneath it.

Open exact source
app_examples/docs-examples/src/examples/counter.rs
use argui::{
    runtime::{Context, Render},
    text::TextStyle,
    ui::{AlignItems, Element, Sides, percent},
    widgets::{Button, default_theme},
};

#[derive(Default)]
pub struct Example {
    count: u32,
}

impl Render for Example {
    fn render(&mut self, cx: &mut Context<Self>) -> Element {
        let themes = default_theme(cx.environment());
        let theme = themes.resolve(cx.environment().color_scheme);
        Element::column([
            Element::text(format!("Count: {}", self.count)).text_style(TextStyle {
                font_size: 34.0,
                line_height: 42.0,
                color: theme.foreground,
                weight: 700,
                ..TextStyle::default()
            }),
            Button::new("increment", "Increment", theme.button())
                .on_click(cx.callback(|app| app.count = app.count.saturating_add(1)))
                .build(),
        ])
        .width(percent(1.0))
        .height(percent(1.0))
        .align_items(AlignItems::CENTER)
        .padding(Sides::length(28.0))
        .gap(18.0)
        .background(theme.background)
    }
}