Beginner 12 min

Events and interaction

Handle pointer, keyboard, text, focus, and accessibility actions through one typed event system.

Start with a direct widget callback

Ordinary buttons do not require bubbling, target-key comparisons or event-kind matching. Attach Context::callback to Button::on_click; typed widgets similarly expose on_input, on_change, on_select and on_open_change with useful payloads.

src/view.rs
let save = Button::new("save", "Save", theme.button())
    .on_click(cx.callback(|model| {
        model.saved = true;
    }))
    .build();

Use listeners for deliberate delegation

Context::listener and Element::on remain the advanced layer for capture, bubbling, application-wide shortcuts and routers that intentionally handle many descendants. The runnable Events example demonstrates that pattern rather than presenting it as the basic button API.

Preserve platform defaults deliberately

Use prevent_default only when the application replaces the normal behavior. stop_propagation ends bubbling; stop_immediate_propagation also stops later listeners on the current node. Passive listeners cannot prevent defaults.

Reference files

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

docs/ui/interaction.mdcrates/argui/examples/interaction.rscrates/argui-ui/src/event.rs
Compiled example

Pointer, keyboard, and accessible activation

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/events.rs
use argui::{
    runtime::{Context, Render},
    text::TextStyle,
    ui::{Element, EventType, FlexWrap, Sides, UiEventKind, percent},
    widgets::{Button, default_theme},
};

#[derive(Default)]
pub struct Example {
    last_action: Option<String>,
}

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);
        let status = self.last_action.as_deref().unwrap_or("Choose an action");
        Element::column([
            Element::row([
                Button::new("save", "Save", theme.button()).build(),
                Button::new("preview", "Preview", theme.outline_button()).build(),
            ])
            .gap(10.0)
            .flex_wrap(FlexWrap::Wrap),
            Element::text(status).text_style(TextStyle {
                color: theme.foreground,
                weight: 600,
                ..TextStyle::default()
            }),
        ])
        .width(percent(1.0))
        .height(percent(1.0))
        .padding(Sides::length(28.0))
        .gap(20.0)
        .background(theme.background)
        .on(cx.listener(EventType::Click, |app, event, cx| {
            if matches!(event.kind, UiEventKind::Click(_)) {
                app.last_action = event
                    .target_key()
                    .map(|key| format!("Received Click({key})"));
                cx.notify();
            }
        }))
    }
}