Skip to content
Back to skills

Makepad 2.0 Theme

ASecurity

CRITICAL: Use for Makepad 2.0 theme system. Triggers on: makepad theme, theme variable, theme color, theme font, theme spacing, dark mode, light mode, theme switching, mod.themes, theme_mod, theme.color_, theme.font_, theme.space_, theme.mspace_, 主题, 颜色, 字体, 暗色模式, 亮色模式, 主题切换, 样式

  • 747 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added May 26, 2026
developmentrustgo

Works with

  • cursor
  • cli

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned May 27, 2026

npx -y skills add ZhangHanDong/makepad-skills --skill makepad-2.0-theme --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Makepad 2.0 Theme?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Makepad 2.0 Theme
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/zhanghandong-makepad-2-0-theme/badge)](https://www.skillsdirectory.com/skills/zhanghandong-makepad-2-0-theme)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: makepad-2.0-theme
description: |
  CRITICAL: Use for Makepad 2.0 theme system. Triggers on:
  makepad theme, theme variable, theme color, theme font, theme spacing,
  dark mode, light mode, theme switching, mod.themes, theme_mod,
  theme.color_, theme.font_, theme.space_, theme.mspace_,
  主题, 颜色, 字体, 暗色模式, 亮色模式, 主题切换, 样式
---

# Makepad 2.0 Theme System

## Overview

The Makepad 2.0 theme system provides a comprehensive set of design tokens accessed
through `theme.*` variables in Splash scripts. It delivers consistent styling for
colors, typography, spacing, and widget states across your entire application.

Three built-in themes are available:
- `mod.themes.dark` -- dark desktop theme (default)
- `mod.themes.light` -- light desktop theme
- `mod.themes.skeleton` -- minimal skeleton theme with hardcoded values

**Golden rule**: Always use `theme.*` variables instead of hardcoded values for any
color, font size, or spacing in production UIs. This ensures your app automatically
supports theme switching and maintains visual consistency.

## Theme Setup in App::run

The theme must be loaded **before** widgets are loaded. The standard pattern is:

```rust
impl App {
    fn run(vm: &mut ScriptVm) -> Self {
        // Step 1: Load theme definitions (dark, light, skeleton)
        crate::makepad_widgets::theme_mod(vm);

        // Step 2: Select active theme (MUST come before widgets_mod)
        script_eval!(vm, {
            mod.theme = mod.themes.light  // or mod.themes.dark
        });

        // Step 3: Load widget definitions (they reference mod.theme)
        crate::makepad_widgets::widgets_mod(vm);

        // Step 4: Load your app's script_mod
        App::from_script_mod(vm, self::script_mod)
    }
}
```

If you skip steps 1-2, the default theme is dark (set inside `theme_mod`).
The counter example uses the simplified one-liner `crate::makepad_widgets::script_mod(vm)`
which bundles steps 1-3 with the default dark theme.

### How It Works Internally

`theme_mod()` does the following:
1. Calls `makepad_draw::script_mod(vm)` to load drawing primitives
2. Creates the `mod.themes` module
3. Loads `theme_desktop_dark`, `theme_desktop_light`, and `theme_desktop_skeleton`
4. Sets `mod.theme = mod.themes.dark` as the default

Then `widgets_mod()` creates `mod.prelude.widgets_internal` with `theme: mod.theme`,
making `theme.*` available in all widget scripts that `use mod.prelude.widgets.*`.

## Theme Global Parameters

Each theme defines tunable global parameters that control the overall feel:

| Parameter | Purpose | Default (dark/light) |
|-----------|---------|---------------------|
| `color_contrast` | Controls color palette spread | 1.0 |
| `color_tint` | Tint applied to backgrounds | `#0000ff` |
| `color_tint_amount` | How much tint to apply (0-1) | 0.0 |
| `space_factor` | Base spacing multiplier | 6.0 |
| `corner_radius` | Base corner radius | 2.5 |
| `beveling` | Bevel intensity | 0.75 |
| `font_size_base` | Base font size in px | 10.0 |
| `font_size_contrast` | Font size step between levels | 2.5 |

## Theme Color Variables -- Primary

These are the colors you will use most often in application code:

| Variable | Purpose | Light Appearance | Dark Appearance |
|----------|---------|-----------------|-----------------|
| `theme.color_bg_app` | App background | Light gray (~#DDD) | Dark gray (~#333) |
| `theme.color_fg_app` | Foreground layer | Slightly darker | Slightly lighter |
| `theme.color_bg_container` | Card/container bg | Semi-transparent light | Semi-transparent dark |
| `theme.color_bg_even` | Alternating row (even) | Lighter | Darker |
| `theme.color_bg_odd` | Alternating row (odd) | Darker | Lighter |
| `theme.color_bg_highlight` | Highlight background | White-ish `#FFFFFF22` | White-ish low opacity |
| `theme.color_bg_highlight_inline` | Inline highlight | `color_d_1` | `color_d_3` |
| `theme.color_bg_unfocussed` | Unfocused highlight | 85% of bg_highlight | 85% of bg_highlight |
| `theme.color_app_caption_bar` | Caption bar bg | Transparent | Transparent |
| `theme.color_white` | Pure white | `#FFFFFF` | `#FFFFFF` |
| `theme.color_makepad` | Makepad brand | `#FF5C39` | `#FF5C39` |

## Theme Color Variables -- Text and Labels

| Variable | Purpose |
|----------|---------|
| `theme.color_label_inner` | Primary text on inner elements (buttons, labels) |
| `theme.color_label_inner_hover` | Text on hover |
| `theme.color_label_inner_down` | Text when pressed |
| `theme.color_label_inner_focus` | Text when focused |
| `theme.color_label_inner_active` | Text when active/selected |
| `theme.color_label_inner_inactive` | Secondary/muted text |
| `theme.color_label_inner_disabled` | Disabled text |
| `theme.color_label_outer` | Primary text on outer elements (tabs, headers) |
| `theme.color_label_outer_off` | Outer text when off |
| `theme.color_label_outer_disabled` | Disabled outer text |
| `theme.color_text` | General text color |
| `theme.color_text_hover` | Text on hover |
| `theme.color_text_focus` | Text on focus |
| `theme.color_text_disabled` | Disabled text |
| `theme.color_text_placeholder` | Placeholder text |
| `theme.color_text_meta` | Metadata text |
| `theme.color_text_cursor` | Text cursor color |

## Theme Color Variables -- Widget States

Outset colors (buttons, raised elements):

| Variable | Purpose |
|----------|---------|
| `theme.color_outset` | Default button background |
| `theme.color_outset_hover` | Button on hover |
| `theme.color_outset_down` | Button when pressed |
| `theme.color_outset_active` | Active toggle state |
| `theme.color_outset_focus` | Focused button |
| `theme.color_outset_disabled` | Disabled button |
| `theme.color_outset_inactive` | Inactive button |

Inset colors (text inputs, checkboxes, radio buttons):

| Variable | Purpose |
|----------|---------|
| `theme.color_inset` | Default input background |
| `theme.color_inset_hover` | Input on hover |
| `theme.color_inset_focus` | Input on focus |
| `theme.color_inset_disabled` | Disabled input |
| `theme.color_inset_empty` | Empty input |

Selection and highlight:

| Variable | Purpose |
|----------|---------|
| `theme.color_selection_focus` | Text selection highlight |
| `theme.color_selection_hover` | Selection on hover |
| `theme.color_highlight` | General accent/highlight |
| `theme.color_cursor` | Cursor color |
| `theme.color_cursor_focus` | Focused cursor |

## Theme Color Variables -- Semantic/Status

| Variable | Purpose | Value |
|----------|---------|-------|
| `theme.color_error` | Error state | Red (`#C00`) |
| `theme.color_warning` | Warning state | Orange (`#FA0`) |
| `theme.color_high` | High severity | Red (`#C00`) |
| `theme.color_mid` | Medium severity | Orange (`#FA0`) |
| `theme.color_low` | Low severity | Yellow-green (`#8A0`) |
| `theme.color_panic` | Panic/critical | Magenta (`#f0f`) |

## Theme Color Variables -- Bevel System

The theme has a layered bevel system for 3D-like widget effects:

| Group | Variables | Purpose |
|-------|-----------|---------|
| `color_bevel*` | `_hover`, `_focus`, `_active`, `_down`, `_disabled` | Flat bevel |
| `color_bevel_inset_1*` | Same suffixes | Inner shadow (layer 1) |
| `color_bevel_inset_2*` | Same suffixes | Inner highlight (layer 2) |
| `color_bevel_outset_1*` | Same suffixes | Outer highlight (layer 1) |
| `color_bevel_outset_2*` | Same suffixes | Outer shadow (layer 2) |

## Theme Color Variables -- Additional Widget Colors

| Variable Group | Purpose |
|----------------|---------|
| `theme.color_icon*` | Icon colors (default, inactive, active, disabled) |
| `theme.color_mark*` | Checkmark/radio mark colors |
| `theme.color_val*` | Progress bar and slider fill colors |
| `theme.color_handle*` | Slider handle colors |
| `theme.color_shadow*` | Shadow effects |
| `theme.color_drag_quad` | Drag preview overlay |
| `theme.color_dock_tab_active` | Active dock tab background |

## Theme Font Variables

### Font Sizes

Font sizes are computed from `font_size_base` and `font_size_contrast`:

| Variable | Formula | Default Value |
|----------|---------|---------------|
| `theme.font_size_1` | base + 8 * contrast | 30.0 (largest heading) |
| `theme.font_size_2` | base + 4 * contrast | 20.0 (medium heading) |
| `theme.font_size_3` | base + 2 * contrast | 15.0 (small heading) |
| `theme.font_size_4` | base + 1 * contrast | 12.5 (subheading) |
| `theme.font_size_p` | base | 10.0 (body text) |
| `theme.font_size_code` | fixed | 9.0 (monospace code) |

### Font Styles (TextStyle objects)

| Variable | Description | Font File |
|----------|-------------|-----------|
| `theme.font_regular` | Regular weight body text | IBMPlexSans-Text.ttf |
| `theme.font_bold` | Bold/semibold text | IBMPlexSans-SemiBold.ttf |
| `theme.font_italic` | Italic text | IBMPlexSans-Italic.ttf |
| `theme.font_bold_italic` | Bold italic text | IBMPlexSans-BoldItalic.ttf |
| `theme.font_code` | Monospace code font | LiberationMono-Regular.ttf |
| `theme.font_label` | Label text (legacy) | IBMPlexSans-Text.ttf |
| `theme.font_icons` | Icon font (FontAwesome) | fa-solid-900.ttf |

Each font style includes multi-language support:
- Latin: IBM Plex Sans family
- Chinese: LXGW WenKai family
- Emoji: Noto Color Emoji

### Line Spacing Constants

| Variable | Value | Purpose |
|----------|-------|---------|
| `theme.font_wdgt_line_spacing` | 1.2 | Widget text |
| `theme.font_hl_line_spacing` | 1.05 | Heading text |
| `theme.font_longform_line_spacing` | 1.2 | Long-form text |

## Theme Spacing Variables

### Base Spacing

Spacing is derived from `space_factor` (default 6.0):

| Variable | Formula | Default Value |
|----------|---------|---------------|
| `theme.space_1` | 0.5 * space_factor | 3.0 (extra small) |
| `theme.space_2` | 1.0 * space_factor | 6.0 (small/standard) |
| `theme.space_3` | 1.5 * space_factor | 9.0 (medium) |

### Margin/Padding Presets (Inset objects)

All-sides presets:

| Variable | Description | Values |
|----------|-------------|--------|
| `theme.mspace_1` | XS padding all sides | 3px each |
| `theme.mspace_2` | SM padding all sides | 6px each |
| `theme.mspace_3` | MD padding all sides | 9px each |

Horizontal-only presets:

| Variable | Description | Values |
|----------|-------------|--------|
| `theme.mspace_h_1` | XS horizontal padding | left/right: 3px, top/bottom: 0 |
| `theme.mspace_h_2` | SM horizontal padding | left/right: 6px, top/bottom: 0 |
| `theme.mspace_h_3` | MD horizontal padding | left/right: 9px, top/bottom: 0 |

Vertical-only presets:

| Variable | Description | Values |
|----------|-------------|--------|
| `theme.mspace_v_1` | XS vertical padding | top/bottom: 3px, left/right: 0 |
| `theme.mspace_v_2` | SM vertical padding | top/bottom: 6px, left/right: 0 |
| `theme.mspace_v_3` | MD vertical padding | top/bottom: 9px, left/right: 0 |

### Dimension Variables

| Variable | Purpose | Default |
|----------|---------|---------|
| `theme.data_item_height` | Standard data row height | ~23px |
| `theme.data_icon_width` | Standard icon width | ~16px |
| `theme.data_icon_height` | Standard icon height | ~22px |
| `theme.container_corner_radius` | Container border radius | 5.0 |
| `theme.textselection_corner_radius` | Text selection radius | 1.25 |
| `theme.tab_height` | Tab bar height | 36.0 |

## Using Theme Variables in Splash

### Colors

```
// Background color
draw_bg.color: theme.color_bg_container

// Text color
draw_text.color: theme.color_label_inner

// Muted/secondary text
draw_text.color: theme.color_label_inner_inactive

// Color math -- multiply for opacity
draw_text.color: theme.color_label_inner_inactive * 0.8

// Semantic colors
draw_bg.color: theme.color_warning
draw_bg.color: theme.color_error
```

### Font Sizes

```
// Body text size
draw_text.text_style.font_size: theme.font_size_p

// Heading sizes
draw_text.text_style.font_size: theme.font_size_1   // Largest
draw_text.text_style.font_size: theme.font_size_2   // Medium
draw_text.text_style.font_size: theme.font_size_3   // Small
draw_text.text_style.font_size: theme.font_size_4   // Sub-heading

// Code font size
draw_text.text_style.font_size: theme.font_size_code
```

### Font Styles

```
// Bold text with custom size (override with {} syntax)
draw_text.text_style: theme.font_bold{font_size: theme.font_size_2}

// Bold text keeping default size
draw_text.text_style: theme.font_bold{}

// Code font
draw_text.text_style: theme.font_code{}

// Override font size using +: merge syntax
draw_text +: {text_style +: {font_size: theme.font_size_3}}
```

### Spacing

```
// Uniform padding
padding: theme.mspace_2

// Padding with overrides
padding: theme.mspace_2{left: theme.space_3, right: theme.space_3}

// Horizontal-only padding with custom values
padding: theme.mspace_h_1{left: theme.space_2, right: theme.space_2}

// Spacing between children
spacing: theme.space_2

// Computed spacing
width: Fill height: 9. * theme.space_1
padding: theme.mspace_3{left: theme.space_3 * 2, right: theme.space_3 * 2}
```

## Color Syntax Reference

Splash supports multiple color formats (not theme-specific but essential):

| Syntax | Example | Description |
|--------|---------|-------------|
| `#RGB` | `#f00` | Short hex (red) |
| `#RRGGBB` | `#ff0000` | Full hex |
| `#RRGGBBAA` | `#ff000080` | Hex with alpha |
| `#xRRGGBB` | `#x2ecc71` | Hex starting with `e` (prefix `#x`) |
| `#N` | `#D` | Grayscale shorthand |
| `vec4(r,g,b,a)` | `vec4(1.0, 0.0, 0.0, 1.0)` | RGBA float (0.0-1.0) |
| `theme.*` | `theme.color_highlight` | Theme variable |

**Important**: When a hex color starts with the letter `e`, use the `#x` prefix
to avoid ambiguity with scientific notation. For example, `#x2ecc71` not `#2ecc71`.

Color math is supported:
```
// Multiply for opacity
theme.color_label_inner_inactive * 0.8

// Mix two colors
mix(theme.color_w, theme.color_b, 0.5)
```

## Theme Switching at Runtime

You can switch themes dynamically in Rust event handlers:

```rust
impl MatchEvent for App {
    fn handle_actions(&mut self, cx: &mut Cx, actions: &Actions) {
        if self.ui.button(cx, ids!(toggle_theme)).clicked(actions) {
            script_eval!(cx, {
                mod.theme = mod.themes.dark  // or mod.themes.light
            });
            // All widgets using theme.* will pick up new values
            // You may need to trigger a re-render
        }
    }
}
```

Or switch before widgets load (in `App::run`) for a static theme choice:
```rust
fn run(vm: &mut ScriptVm) -> Self {
    crate::makepad_widgets::theme_mod(vm);
    script_eval!(vm, {
        mod.theme = mod.themes.light
    });
    crate::makepad_widgets::widgets_mod(vm);
    App::from_script_mod(vm, self::script_mod)
}
```

## Complete Theme-Aware UI Example

This example demonstrates a themed card list using only theme variables:

```
script_mod! {
    use mod.prelude.widgets.*

    let CardItem = RoundedView{
        width: Fill height: Fit
        padding: theme.mspace_2{left: theme.space_3, right: theme.space_3}
        flow: Right spacing: theme.space_2
        align: Align{y: 0.5}
        draw_bg.color: theme.color_bg_container
        draw_bg.border_radius: theme.container_corner_radius

        icon := Label{
            width: 24 height: 24
            text: ""
        }
        content := View{
            width: Fill height: Fit
            flow: Down spacing: theme.space_1
            title := Label{
                text: "Title"
                draw_text.color: theme.color_label_inner
                draw_text.text_style: theme.font_bold{font_size: theme.font_size_4}
            }
            subtitle := Label{
                text: "Subtitle"
                draw_text.color: theme.color_label_inner_inactive
                draw_text.text_style.font_size: theme.font_size_p
            }
        }
        badge := RoundedView{
            width: Fit height: Fit
            padding: theme.mspace_h_1
            draw_bg.color: theme.color_bg_highlight_inline
            draw_bg.border_radius: 4.0
            badge_label := Label{
                text: "new"
                draw_text.color: theme.color_highlight
                draw_text.text_style.font_size: theme.font_size_code
                draw_text.text_style: theme.font_bold{}
            }
        }
    }

    startup() do #(App::script_component(vm)){
        ui: Root{
            main_window := Window{
                pass.clear_color: theme.color_bg_app
                window.inner_size: vec2(400, 600)
                body +: {
                    width: Fill height: Fill
                    flow: Down spacing: 0

                    // Header
                    SolidView{
                        width: Fill height: Fit
                        padding: theme.mspace_3
                        draw_bg.color: theme.color_app_caption_bar
                        Label{
                            text: "My Cards"
                            draw_text.color: theme.color_label_inner
                            draw_text.text_style: theme.font_bold{font_size: theme.font_size_2}
                        }
                    }

                    // Card list
                    ScrollYView{
                        width: Fill height: Fill
                        padding: theme.mspace_2
                        flow: Down spacing: theme.space_1
                        CardItem{title.text: "First Card" subtitle.text: "Description here"}
                        CardItem{title.text: "Second Card" subtitle.text: "Another card"}
                    }

                    // Footer
                    SolidView{
                        width: Fill height: Fit
                        padding: theme.mspace_2
                        draw_bg.color: theme.color_bg_container
                        Label{
                            text: "2 items"
                            draw_text.color: theme.color_label_inner_inactive
                            draw_text.text_style.font_size: theme.font_size_code
                        }
                    }
                }
            }
        }
    }
}
```

## Best Practices

1. **ALWAYS use `theme.*` for colors in production apps** -- never hardcode `#ff0000`
   when `theme.color_error` exists. This enables theme switching and accessibility.

2. **Use theme fonts for consistent typography** -- prefer `theme.font_bold{font_size: theme.font_size_2}`
   over manually specifying font families.

3. **Use theme spacing for consistent layout** -- `theme.mspace_2` and `theme.space_2`
   create a harmonious rhythm. Avoid magic numbers like `padding: Inset{top: 8, ...}`.

4. **Override with `{}` syntax** -- extend theme values without replacing them:
   `theme.font_bold{font_size: 20}` keeps the bold font family but overrides size.
   `theme.mspace_2{left: theme.space_3}` keeps top/right/bottom but overrides left.

5. **Use merge `+:` for partial overrides** -- when you only want to change one
   nested property: `draw_text +: {text_style +: {font_size: theme.font_size_3}}`

6. **Choose the right text color variable**:
   - `theme.color_label_inner` for primary UI text (buttons, labels)
   - `theme.color_label_inner_inactive` for secondary/muted text
   - `theme.color_text` for general content text
   - `theme.color_text_placeholder` for placeholder text

7. **Use state-aware color variants** -- widgets that change on hover/focus/press
   should use the matching `_hover`, `_focus`, `_down` suffixes.

8. **Multiply for subtle opacity** -- `theme.color_label_inner_inactive * 0.8`
   creates a subtler variant without a new variable.

9. **Select theme before `widgets_mod`** -- the theme must be set between
   `theme_mod()` and `widgets_mod()` calls, so widget definitions pick up the
   correct theme values.

10. **For simple apps**, use `crate::makepad_widgets::script_mod(vm)` which loads
    everything with the default dark theme in one call.

## Source Files

- Theme dark: `widgets/src/theme_desktop_dark.rs`
- Theme light: `widgets/src/theme_desktop_light.rs`
- Theme skeleton: `widgets/src/theme_desktop_skeleton.rs`
- Theme loader: `widgets/src/lib.rs` (`theme_mod` and `widgets_mod` functions)
- Example usage: `examples/todo/src/app.rs` (light theme with full theme variable usage)
- Example usage: `examples/counter/src/app.rs` (default dark theme)

Files in this skill

  • SKILL.md19.9 KB
  • references/theme-variables.md20.6 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.