---
name: Makepad 2.0 Troubleshooting
slug: makepad-2-0-troubleshooting
category: Quality
description: Makepad 2.0 Troubleshooting helps diagnose common Makepad 2.0 and Splash issues like invisible UI, missing text, failed clicks, hot reload problems, and build errors. Use it to find the likely cause and the correct fix.
github: "https://github.com/ZhangHanDong/makepad-skills/tree/main/skills/makepad-2.0-troubleshooting"
stars: 745
forks: 87
install: "npx degit https://github.com/ZhangHanDong/makepad-skills/tree/main/skills/makepad-2.0-troubleshooting ~/.claude/skills/makepad-2.0-troubleshooting"
installs_to: ~/.claude/skills/makepad-2.0-troubleshooting
source_path: skills/makepad-2.0-troubleshooting/SKILL.md
collection_size: 14
category_size: 1354
collection_url: "https://dirskills.com/collections/ZhangHanDong/makepad-skills"
added: 2026-08-23T05:20:00.039Z
last_synced: 2026-08-23T05:20:00.039Z
canonical_url: "https://dirskills.com/skills/makepad-2-0-troubleshooting"
---

# Makepad 2.0 Troubleshooting

Makepad 2.0 Troubleshooting helps diagnose common Makepad 2.0 and Splash issues like invisible UI, missing text, failed clicks, hot reload problems, and build errors. Use it to find the likely cause and the correct fix.

**Install:**

```bash
npx degit https://github.com/ZhangHanDong/makepad-skills/tree/main/skills/makepad-2.0-troubleshooting ~/.claude/skills/makepad-2.0-troubleshooting
```

## README

# Makepad 2.0 Common Pitfalls & Troubleshooting Guide

This skill covers common mistakes when building with Makepad 2.0 and the Splash scripting language. Each pitfall includes:
- What the user sees (symptom)
- Why it happens (root cause)
- How to fix it (correct code)

Reference documents: `AGENTS.md`, `splash.md`

---

## Pitfall #1: Container height is 0px -- UI is invisible

**Symptom:** Your entire UI or a section of it does not appear. The container renders with zero height, making all children invisible.

**Root Cause:** All View-based containers (`View`, `SolidView`, `RoundedView`, etc.) default to `height: Fill`. When a `Fill` container is placed inside a `Fit` parent (or any context where the available height is determined by children), the height resolves to 0px due to circular dependency: the parent asks the child how tall it is, the child says "as tall as my parent", and the result is zero.

**Fix:** Always set `height: Fit` on containers that should shrink-wrap their content.

```
// WRONG -- height defaults to Fill, resolves to 0px in a Fit context
View{
    flow: Down
    Label{text: "Hello"}
}

// CORRECT -- height: Fit makes the container wrap its children
View{
    height: Fit
    flow: Down
    Label{text: "Hello"}
}
```

**Rule of thumb:** Write `height: Fit` immediately after the opening brace of every container unless you have a fixed-height parent or you explicitly want `height: Fill` inside a known fixed-size ancestor.

**Exception:** Inside a fixed-height parent, `height: Fill` is valid:
```
View{
    height: 300
    View{
        height: Fill
        Label{text: "I fill the 300px"}
    }
}
```

---

## Pitfall #2: Text invisible on colored background -- missing new_batch

**Symptom:** You add a `Label` inside a `RoundedView` or `SolidView` with a background color, but the text is invisible. The container appears correctly colored but the text cannot be seen, even though `draw_text.color` is set to a contrasting color.

**Root Cause:** Makepad batches draw calls by shader type for GPU performance. All `Label` widgets using the same text shader get batched into one draw call, and all backgrounds into another. Without `new_batch: true`, the text draw call may execute *before* the background draw call, placing the text geometrically behind the opaque background.

**Fix:** Add `new_batch: true` to any View-based container that has a visible background (`show_bg: true` or pre-styled views like `SolidView`, `RoundedView`) and contains text children.

```
// WRONG -- text is drawn behind the background due to batching
RoundedView{
    height: Fit
    draw_bg.color: #333
    Label{text: "Can't see me"}
}

// CORRECT -- new_batch forces background to draw before children's text
RoundedView{
    height: Fit
    new_batch: true
    draw_bg.color: #333
    Label{text: "Now visible" draw_text.color: #fff}
}
```

**When you MUST use `new_batch: true`:**
- Any container with `show_bg: true` (or pre-styled like `SolidView`, `RoundedView`) that contains text
- Hoverable items with background animator -- text disappears on hover without it
- Parent containers of repeated items that each have their own background

---

## Pitfall #3: Named child override does not work -- used `:` instead of `:=`

**Symptom:** You define a template with `let` and try to override a child property per-instance, but the override is silently ignored. The default text always shows.

**Root Cause:** In Splash, `:` creates a **static** property, while `:=` creates a **named/dynamic** child that is addressable and overridable. If you declare `label: Label{...}` (with `:`), the child has no addressable name and the override path `label.text:` cannot find it.

**Fix:** Use `:=` for any child you want to reference or override later.

```
// WRONG -- static child, override fails silently
let Card = View{
    height: Fit
    title: Label{text: "default"}
}
Card{title.text: "new text"}  // Fails! title is not addressable

// CORRECT -- named child with :=, override works
let Card = View{
    height: Fit
    title := Label{text: "default"}
}
Card{title.text: "new text"}  // Works! title is a named child
```

**Additional rule:** Named children inside anonymous containers are UNREACHABLE. Every container in the path from root to child must also be named:

```
// WRONG -- label is inside an anonymous View, unreachable
let Item = View{
    height: Fit
    View{
        flow: Down
        label := Label{text: "default"}
    }
}
Item{label.text: "new"}  // Fails! No path to label through anonymous View

// CORRECT -- full named path
let Item = View{
    height: Fit
    texts := View{
        flow: Down
        label := Label{text: "default"}
    }
}
Item{texts.label.text: "new"}  // Works! Full dot-path through named containers
```

---

## Pitfall #4: Hex color with letter 'e' renders wrong or causes parse error

**Symptom:** A hex color like `#2ecc71` causes a cryptic parse error such as `expected at least one digit in exponent`, or the color renders incorrectly.

**Root Cause:** The Rust tokenizer inside `script_mod!{}` interprets a digit followed by `e` as the start of a scientific notation number (e.g., `2e` looks like `2 * 10^...`). This breaks parsing of hex colors that contain the letter `e` adjacent to digits.

**Fix:** Use the `#x` prefix for any hex color containing the letter `e` or `E`.

```
// WRONG -- parser reads '2e' as scientific notation exponent
draw_bg.color: #2ecc71
draw_bg.color: #1e1e2e
draw_bg.color: #4466ee

// CORRECT -- #x prefix escapes the hex literal
draw_bg.color: #x2ecc71
draw_bg.color: #x1e1e2e
draw_bg.color: #x4466ee
```

**When is `#x` NOT needed?** Colors without the letter `e` work fine with plain `#`:
```
draw_bg.color: #ff4444    // OK -- no 'e'
draw_bg.color: #44cc44    // OK -- no 'e'
draw_bg.color: #333       // OK -- no 'e'
```

---

## Pitfall #5: border_radius takes wrong type -- must be float, not Inset

**Symptom:** Attempting to set per-corner border radii with `Inset` causes a parse error or silently breaks the layout. The rounded corners do not appear.

**Root Cause:** Border radius is a single `f32` uniform value applied uniformly to all corners. It is NOT an Inset-like struct with per-corner values. Passing an `Inset` or object silently breaks the entire layout.

**CRITICAL:** The property name differs by context:
- **In Canvas Splash (POST /splash):** Use `draw_bg.radius` with trailing-dot float
- **In script_mod! macro:** Use `draw_bg.border_radius`

**Fix:** Use a plain float value with the correct property name.

```
// WRONG -- border_radius is not an Inset
draw_bg.border_radius: Inset{top_left: 10 top_right: 10}

// WRONG -- not an object
draw_bg.border_radius: {top: 10 bottom: 0}

// CORRECT (Canvas Splash context) -- use draw_bg.radius with trailing dot
draw_bg.radius: 10.

// CORRECT (script_mod! context) -- use draw_bg.border_radius
draw_bg.border_radius: 10.0

// For per-corner radii, use RoundedAllView with a vec4
// (top-left, top-right, right-bottom, left-bottom)
RoundedAllView{
    height: Fit
    draw_bg.border_radius: vec4(10.0 10.0 0.0 0.0)
}
```

---

## Pitfall #6: Widget not found from Rust -- registration order wrong

**Symptom:** At runtime, a widget type is not found or a script error occurs saying a widget is not registered. The app may panic or display nothing.

**Root Cause:** In Makepad 2.0, widget modules must be registered via `script_mod(vm)` calls in the correct order. Base widgets must be registered before custom widgets, and custom widgets before the UI that uses them. If the order is wrong, a module tries to use a widget type that has not been registered yet.

**Fix:** Follow the correct registration order in `App::run()`.

```rust
impl App {
    fn run(vm: &mut ScriptVm) -> Self {
        // 1. Register base widget library (theme + all standard widgets)
        crate::makepad_widgets::script_mod(vm);

        // 2. Register your custom widget modules (if any)
        crate::my_custom_widgets::script_mod(vm);

        // 3. Register your app UI module (uses widgets from steps 1 and 2)
        crate::app_ui::script_mod(vm);

        // 4. Create the app from its own script_mod
        App::from_script_mod(vm, self::script_mod)
    }
}
```

**Key rule:** Widget modules must be registered BEFORE UI modules that use them. Always call `lib.rs::script_mod` before `app_ui::script_mod`.

---

## Pitfall #7: Filler clips text -- used alongside width: Fill sibling

**Symptom:** Text in a horizontal layout is cut off halfway. The text label appears to have only half the available width.

**Root Cause:** `Filler{}` is defined as `View{width: Fill height: Fill}`. When placed next to a sibling that also has `width: Fill`, both compete for the remaining horizontal space and split it 50/50. The text label only gets half the width and text is clipped.

**Fix:** Remove `Filler{}` when a sibling already uses `width: Fill`. The `Fill` sibling naturally takes all remaining space, pushing `Fit`-sized siblings to the edge.

```
// WRONG -- Filler splits space with Fill sibling, text is clipped
View{
    flow: Right height: Fit
    Label{width: Fill text: "Long text that gets clipped"}
    Filler{}
    Button{text: "OK"}
}

// CORRECT -- width: Fill on label pushes button to the right edge
View{
    flow: Right height: Fit
    Label{width: Fill text: "Long text now has full space"}
    Button{text: "OK"}
}

// CORRECT use of Filler -- between Fit-sized siblings
View{
    flow: Right height: Fit
    Label{text: "left"}
    Filler{}
    Label{text: "right"}
}
```

---

## Pitfall #8: Text disappears on hover -- animated View without new_batch

**Symptom:** A list item or button has hover effects. When you hover over it, the background color changes but the text vanishes completely. Moving the cursor away brings the text back.

**Root Cause:** The hover animator changes the View's background from transparent (`#0000`) to an opaque or semi-opaque color. Without `new_batch: true`, the background and text are in the same draw batch. When the background becomes opaque, it covers the text that was drawn in the same batch order.

**Fix:** Add `new_batch: true` to any View with `show_bg: true` that has a hover animator and contains text.

```
// WRONG -- text disappears when hover activates the background
View{
    width: Fill height: Fit
    show_bg: true
    draw_bg +: {
        color: uniform(#0000)
        color_hover: uniform(#fff2)
        hover: instance(0.0)
        pixel: fn(){
            return Pal.premul(self.color.mix(self.color_hover, self.hover))
        }
    }
    animator: Animator{
        hover: {
            default: @off
            off: AnimatorState{
                from: {all: Forward {duration: 0.15}}
                apply: {draw_bg: {hover: 0.0}}
            }
            on: AnimatorState{
                from: {all: Forward {duration: 0.15}}
                apply: {draw_bg: {hover: 1.0}}
            }
        }
    }
    Label{text: "Vanishes on hover!" draw_text.color: #fff}
}

// CORRECT -- add new_batch: true
View{
    width: Fill height: Fit
    new_batch: true
    show_bg: true
    draw_bg +: {
        color: uniform(#0000)
        color_hover: uniform(#fff2)
        hover: instance(0.0)
        pixel: fn(){
            return Pal.premul(self.color.mix(self.color_hover, self.hover))
        }
    }
    animator: Animator{
        hover: {
            default: @off
            off: AnimatorState{
                from: {all: Forward {duration: 0.15}}
                apply: {draw_bg: {hover: 0.0}}
            }
            on: AnimatorState{
                from: {all: Forward {duration: 0.15}}
                apply: {draw_bg: {hover: 1.0}}
            }
        }
    }
    Label{text: "Stays visible on hover!" draw_text.color: #fff}
}
```

---

## Pitfall #9: Commas in Splash -- tolerated but not required

**Symptom:** Confusion about whether commas are allowed between properties in `script_mod!` blocks.

**Root Cause:** Splash is primarily whitespace-delimited. However, the Splash tokenizer **treats commas as whitespace** — they are silently consumed and do not cause parse errors. Many Makepad projects (including Robrix) use commas extensively in `script_mod!` blocks, inherited from Makepad 1.x `live_design!` syntax which required commas.

**Guidance:** Both styles are valid. Match the surrounding code style:

```
// Style A: with commas (common in codebases migrated from 1.x)
View{
    flow: Down,
    height: Fit,
    spacing: 10,
    padding: Inset{top: 5, bottom: 5, left: 10, right: 10}
}

// Style B: without commas (pure Splash style)
View{
    flow: Down
    height: Fit
    spacing: 10
    padding: Inset{top: 5 bottom: 5 left: 10 right: 10}
}
```

**Note:** Both compile and run identically. The tokenizer discards commas. Do NOT waste time removing commas from existing code — it creates noisy diffs with no functional change.

---

## Pitfall #10: Using semicolons -- not valid in Splash

**Symptom:** Parse errors or unexpected behavior. The semicolons are treated as part of property values or cause tokenizer failures.

**Root Cause:** Splash does not use semicolons to terminate statements. This is a common mistake for developers coming from CSS, JavaScript, or Rust backgrounds.

**Fix:** Remove all semicolons.

```
// WRONG -- semicolons are not valid
View{
    flow: Down;
    height: Fit;
    Label{text: "Hello";};
}

// CORRECT -- no semicolons needed
View{
    flow: Down
    height: Fit
    Label{text: "Hello"}
}
```

---

## Pitfall #11: Root container without width: Fill -- narrow or broken layout

**Symptom:** The UI appears as a narrow sliver on one side of the window, or the layout is entirely broken. Content does not fill the available window width.

**Root Cause:** Using a fixed pixel width (e.g., `width: 400`) on the outermost container means it does not adapt to the available window space. If the window is wider, the content is a small strip. If narrower, content is clipped.

**Fix:** Always use `width: Fill` on the root container. Fixed pixel widths are fine for inner elements.

```
// WRONG -- fixed width on root, does not adapt to window
RoundedView{
    width: 400
    height: Fit
    flow: Down
    Label{text: "Narrow!"}
}

// CORRECT -- root fills available width
RoundedView{
    width: Fill
    height: Fit
    flow: Down
    Label{text: "Full width!"}
}
```

---

## Pitfall #12: Label does not support Animator -- silently ignored

**Symptom:** You add `animator: Animator{...}` and `cursor: MouseCursor.Hand` to a Label, but nothing happens on hover. No error, no effect.

**Root Cause:** `Label` (and `H1`-`H4`, `P`, `TextBox`, `Image`, `Icon`, `Markdown`, `Html`, `Slider`, `DropDown`, `Splitter`, `Hr`, `Filler`) do NOT have an `animator` field. Adding one is silently ignored.

**Fix:** Wrap the Label in a `View` that does support animator.

```
// WRONG -- animator on Label is silently ignored
Label{
    animator: Animator{
        hover: {
            default: @off
            off: AnimatorState{ from: {all: Forward{duration: 0.15}} apply: {draw_text: {hover: 0.0}} }
            on: AnimatorState{ from: {all: Forward{duration: 0.15}} apply: {draw_text: {hover: 1.0}} }
        }
    }
    text: "Hover me"
}

// CORRECT -- animate the wrapping View
View{
    width: Fill height: Fit
    new_batch: true
    cursor: MouseCursor.Hand
    show_bg: true
    draw_bg +: {
        color: uniform(#0000)
        color_hover: uniform(#fff2)
        hover: instance(0.0)
        pixel: fn(){ return Pal.premul(self.color.mix(self.color_hover, self.hover)) }
    }
    animator: Animator{
        hover: {
            default: @off
            off: AnimatorState{ from: {all: Forward{duration: 0.15}} apply: {draw_bg: {hover: 0.0}} }
            on: AnimatorState{ from: {all: Forward{duration: 0.15}} apply: {draw_bg: {hover: 1.0}} }
        }
    }
    Label{text: "Hover me" draw_text.color: #fff}
}
```

**Widgets that SUPPORT animator:** `View`, `SolidView`, `RoundedView`, `ScrollXView`, `ScrollYView`, `ScrollXYView`, `Button`, `ButtonFlat`, `ButtonFlatter`, `CheckBox`, `Toggle`, `RadioButton`, `LinkLabel`, `TextInput`

---

## Pitfall #13: Default text color is white -- invisible on light backgrounds

**Symptom:** Text is present in the widget tree but invisible. On white or light-colored backgrounds, the text simply cannot be seen. It may appear if you select the text or change the background to a dark color.

**Root Cause:** All text widgets (`Label`, `H1`-`H4`, `P`, `Button` text, etc.) default to white (`#fff`) text color. On a light background, white text is invisible.

**Fix:** Explicitly set `draw_text.color` to a dark color for every text element on light backgrounds.

```
// WRONG -- white text on white background
View{
    height: Fit
    show_bg: true
    draw_bg.color: #fff
    Label{text: "Can't see this"}
}

// CORRECT -- explicit dark text color
View{
    height: Fit
    new_batch: true
    show_bg: true
    draw_bg.color: #fff
    Label{text: "Visible!" draw_text.color: #333}
}
```

---

## Pitfall #14: Shader pixel function missing Pal.premul() -- colors render wrong

**Symptom:** Semi-transparent colors render as bright, fully opaque, or washed out. A subtle tint like `#ffffff08` appears as bright white instead of nearly transparent.

**Root Cause:** Makepad uses premultiplied alpha for GPU rendering. When you return a color from a `pixel: fn()` without premultiplying the alpha, the alpha blending produces incorrect results.

**Fix:** Always wrap your final color return in `Pal.premul()` -- unless returning `sdf.result`, which is already premultiplied by `sdf.fill()` / `sdf.stroke()`.

```
// WRONG -- alpha not premultiplied, renders incorrectly
draw_bg +: {
    pixel: fn() {
        return vec4(1.0 0.0 0.0 0.5)
    }
}

// CORRECT -- premultiply alpha
draw_bg +: {
    pixel: fn() {
        return Pal.premul(vec4(1.0 0.0 0.0 0.5))
    }
}

// ALSO CORRECT -- sdf.result is already premultiplied
draw_bg +: {
    pixel: fn() {
        let sdf = Sdf2d.viewport(self.pos * self.rect_size)
        sdf.box(0.0 0.0 self.rect_size.x self.rect_size.y 4.0)
        sdf.fill(#f00)
        return sdf.result
    }
}

// CORRECT -- color mixing with premultiply
draw_bg +: {
    pixel: fn() {
        return Pal.premul(self.color.mix(self.color_hover, self.hover))
    }
}
```

---

## Pitfall #15: Using CSS property names -- not valid in Splash

**Symptom:** Properties are not recognized, causing parse errors or the property being silently ignored.

**Root Cause:** Splash is not CSS. Property names use Makepad's own naming convention with dot-path access to nested struct fields.

**Fix:** Use Makepad property names.

| CSS Name | Splash Name |
|----------|-------------|
| `background-color` | `draw_bg.color` |
| `color` | `draw_text.color` |
| `font-size` | `draw_text.text_style.font_size` |
| `border-radius` | `draw_bg.border_radius` |
| `border-color` | `draw_bg.border_color` |
| `border-width` | `draw_bg.border_size` |
| `padding` | `padding` (same) |
| `margin` | `margin` (same) |
| `gap` | `spacing` |
| `display: flex` | `flow: Right` or `flow: Down` |
| `flex-direction: column` | `flow: Down` |
| `flex-direction: row` | `flow: Right` |
| `justify-content: center` | `align: Center` |
| `width: 100%` | `width: Fill` |
| `width: auto` | `width: Fit` |

```
// WRONG -- CSS property names
View{
    background-color: #333
    font-size: 14
    border-radius: 8px
    gap: 10
}

// CORRECT -- Splash property names
RoundedView{
    height: Fit
    draw_bg.color: #333
    draw_bg.border_radius: 8.0
    spacing: 10
    Label{text: "Hello" draw_text.text_style.font_size: 14}
}
```

---

## Pitfall #16: Inventing non-existent properties -- silent failures

**Symptom:** A property you wrote has no effect. No error is raised, but the widget does not behave as expected.

**Root Cause:** Splash silently ignores unknown properties. If you guess at a property name (e.g., 
