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
75214 skills

Claude Code

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

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
@zhanghandong
Ships withmakepad-skills

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

Stars on GitHub

Momentum

stars in total
752
Star history of ZhangHanDong/makepad-skills on GitHub, a chart by star-history.com

Other skills on makepad-skills.

More skills from other plugins.

brainstorming
Auto-invoked

brainstorming

You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…

@obra@obraInstall Skill
ask-matt

ask-matt

Ask which skill or flow fits your situation. A router over the skills in this repo.

skills11d309k
@mattpocock@mattpocockInstall Skill
accessibility
Auto-invoked

accessibility

Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…

ecc13d293k
@affaan-m@affaan-mInstall Skill
compiler-commit

compiler-commit

Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…

react2d252k
@facebook@facebookInstall Skill

Questions about makepad-2.0-app-structure.

Which coding agents can use makepad-2.0-app-structure?
makepad-2.0-app-structure installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.