Skip to content
Development
Skill

/makepad-2.0-app-structure

CRITICAL: Use for Makepad 2.0 app structure and Rust integration. Triggers on: makepad app, makepad getting started, app_main!, App::run, MatchEvent, AppMain, handle_event, handle_actions, ScriptVm, from_script_mod, makepad boilerplate, makepad new project, makepad cargo,

From plugin
makepad-skills
74514 skills
Install
$ npx -y skills add ZhangHanDong/makepad-skills --skill makepad-2.0-app-structure --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/makepad-2.0-app-structure

Context preview

The summary Claude sees to decide when to auto-load this skill.

CRITICAL: Use for Makepad 2.0 app structure and Rust integration. Triggers on: makepad app, makepad getting started, app_main!, App::run, MatchEvent, AppMain, handle_event, handle_actions, ScriptVm, from_script_mod, makepad boilerplate, makepad new project, makepad cargo,

SKILL.md

makepad-2.0-app-structure.SKILL.md
name: makepad-2.0-app-structure
description: |
  CRITICAL: Use for Makepad 2.0 app structure and Rust integration. Triggers on:
  makepad app, makepad getting started, app_main!, App::run, MatchEvent,
  AppMain, handle_event, handle_actions, ScriptVm, from_script_mod,
  makepad boilerplate, makepad new project, makepad cargo, Cargo.toml setup,
  hot reload, --hot, live reload, wasm deploy, cargo makepad, media plugin,
  audio_output, audio_input, AudioBuffer, cx.audio, makepad audio, 音频,
  应用结构, 入门, 新项目, 脚手架, 启动, 热重载, 部署

Makepad 2.0 App Structure Skill

> **Version:** makepad-widgets (dev branch) | **Last Updated:** 2026-03-03

Overview

A Makepad 2.0 app combines Rust code with Splash scripting. The Rust side handles app lifecycle, event routing, and business logic. The Splash side defines UI structure, templates, and inline interactions.

Documentation

Refer to the local files for detailed documentation:

  • `./references/app-boilerplate.md` - Complete working app template with Cargo.toml
  • `./references/rust-splash-integration.md` - Rust ↔ Splash communication patterns

IMPORTANT: Documentation Completeness Check

**Before answering questions, Claude MUST:** 1. Read the relevant reference file(s) listed above 2. If file read fails, answer based on SKILL.md patterns + built-in knowledge

---

Minimal App Template

Cargo.toml

[package]
name = "my-app"
version = "0.1.0"
edition = "2024"

[dependencies]
makepad-widgets = { path = "../path/to/makepad/widgets" }

src/app.rs (or src/main.rs)

use makepad_widgets::*;

app_main!(App);

script_mod! {
    use mod.prelude.widgets.*

    let state = {
        counter: 0
    }
    mod.state = state

    startup() do #(App::script_component(vm)){
        ui: Root{
            on_startup: ||{
                ui.main_view.render()
            }
            main_window := Window{
                window.inner_size: vec2(420, 300)
                body +: {
                    main_view := View{
                        width: Fill height: Fill
                        flow: Down spacing: 12
                        align: Center
                        on_render: ||{
                            Label{
                                text: "Count: " + state.counter
                                draw_text.text_style.font_size: 24
                            }
                        }
                    }
                    increment_button := Button{
                        text: "Increment"
                    }
                }
            }
        }
    }
}

impl App {
    fn run(vm: &mut ScriptVm) -> Self {
        crate::makepad_widgets::script_mod(vm);
        App::from_script_mod(vm, self::script_mod)
    }
}

#[derive(Script, ScriptHook)]
pub struct App {
    #[live]
    ui: WidgetRef,
}

impl MatchEvent for App {
    fn handle_actions(&mut self, cx: &mut Cx, actions: &Actions) {
        if self.ui.button(cx, ids!(increment_button)).clicked(actions) {
            script_eval!(cx, {
                mod.state.counter += 1
                ui.main_view.render()
            });
        }
    }
}

impl AppMain for App {
    fn handle_event(&mut self, cx: &mut Cx, event: &Event) {
        self.match_event(cx, event);
        self.ui.handle_event(cx, event, &mut Scope::empty());
    }
}

---

Key Components Explained

1. `app_main!(App)`

Registers `App` as the application entry point. MUST be called at module level.

2. `script_mod! { ... }`

Defines the Splash script that runs at startup. Contains:

  • `use mod.prelude.widgets.*` - Import widget definitions
  • State definitions (`let state = {...}`)
  • Functions and templates
  • `startup() do #(App::script_component(vm)){...}` - UI construction

3. `App::run(vm)` - Initialization Order

**CRITICAL: Registration order matters!**

fn run(vm: &mut ScriptVm) -> Self {
    // 1. Register theme (optional, for light/dark theme)
    crate::makepad_widgets::theme_mod(vm);
    script_eval!(vm, { mod.theme = mod.themes.light });

    // 2. Register base widgets
    crate::makepad_widgets::widgets_mod(vm);

    // 3. Register custom widget modules (if any)
    // crate::my_widgets::script_mod(vm);

    // 4. Build app from script module
    App::from_script_mod(vm, self::script_mod)
}

**Simplified (without theme selection):**

fn run(vm: &mut ScriptVm) -> Self {
    crate::makepad_widgets::script_mod(vm);   // Registers both theme + widgets
    App::from_script_mod(vm, self::script_mod)
}

4. `#[derive(Script, ScriptHook)]` on App struct

#[derive(Script, ScriptHook)]
pub struct App {
    #[live]
    ui: WidgetRef,    // The widget tree root
}
  • `Script` - Enables Splash integration (replaces old `Live`)
  • `ScriptHook` - Enables lifecycle hooks (replaces old `LiveHook`)
  • `#[live]` - Field settable from DSL

5. `MatchEvent` for Action Handling

impl MatchEvent for App {
    fn handle_actions(&mut self, cx: &mut Cx, actions: &Actions) {
        // Check button clicks
        if self.ui.button(cx, ids!(my_button)).clicked(actions) {
            // Handle click
        }
        // Check text input changes
        if let Some(text) = self.ui.text_input(cx, ids!(my_input)).changed(actions) {
            log!("Input: {}", text);
        }
    }
}

6. `AppMain` for Event Dispatch

impl AppMain for App {
    fn handle_event(&mut self, cx: &mut Cx, event: &Event) {
        self.match_event(cx, event);  // MUST call for MatchEvent to work
        self.ui.handle_event(cx, event, &mut Scope::empty());
    }
}

---

Rust → Splash Communication

script_eval! - Update state and trigger renders

script_eval!(cx, {
    mod.state.counter += 1
    ui.main_view.render()
});

script_apply_eval! - Patch widget properties

script_apply_eval!(cx, self.ui, {
    title.text: "New Title"
    subtitle.draw_text.color: #f00
});

---

Splash → Rust Communication

In

Read more
Ships withmakepad-skills

Skills for building cross-platform UI applications with Makepad 2.0.

Get the whole plugin

Other skills on makepad-skills.