Skip to content

Five-Minute Tutorial ​

No MoonBit knowledge needed — programming experience in any language is enough. This tutorial builds a desktop app from scratch: a window, a button, and a counter that refreshes automatically on every click.

1. Install the toolchain (once) ​

MoonBit toolchain:

sh
# Linux / macOS
curl -fsSL https://cli.moonbitlang.com/install/unix.sh | bash
# Windows (PowerShell)
irm https://cli.moonbitlang.com/install/powershell.ps1 | iex
Linux (Ubuntu 24.04) environment setup

You need build-essential (gcc / g++ / make), CMake, pkg-config, plus the development packages for GTK3 / Pango / FontConfig / X11 / WebKit2GTK:

sh
sudo apt install build-essential cmake pkg-config \
  libgtk-3-dev libpango1.0-dev libfontconfig1-dev libx11-dev libwebkit2gtk-4.1-dev

If a runtime error complains about missing GTK libraries, this apt list is most likely incomplete.

Windows (10/11, x64) environment setup

You need Python 3, MoonBit, CMake, and the MSVC C++ toolchain (including ATL); when moon compiles native code on Windows it looks for cl on PATH, so run from the "x64 Native Tools Command Prompt for VS 2022", or call vcvars64.bat first:

powershell
irm https://cli.moonbitlang.com/install/powershell.ps1 | iex            # MoonBit
winget install Kitware.CMake
winget install Microsoft.VisualStudio.2022.BuildTools -e --override "--quiet --wait --norestart --add Microsoft.VisualStudio.Workload.VCTools --includeRecommended"
# add ATL:
Start-Process -FilePath 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\setup.exe' -ArgumentList 'modify','--installPath','"C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools"','--add','Microsoft.VisualStudio.Component.VC.ATL','--quiet','--norestart' -Verb RunAs -Wait

2. Create the project ​

sh
moon new my_app
cd my_app
moon add NoahLiu/moonbit-libyue

The project generated by moon new needs three manual adjustments:

  • moon.mod: change preferred_target = "wasm" to "native" — this library goes through native FFI and supports only the native backend.

  • Rename the entry file: my_app.mbt -> main.mbt — the current moon only recognizes a file named main.mbt as the program entry; the template's default file name yields Missing main function.

  • Replace the whole moon.pkg with (note this is moon's DSL format, not JSON):

    import {
      "NoahLiu/moonbit-libyue/yue",
      "NoahLiu/moonbit-libyue/yue/declarative",
    }
    
    options(
      "is-main": true,
    )

    import lets your code reach the library: @yue.* for the core package (windows, native widgets, painting, Signal/Store), and @declarative.* for the declarative layer (mount/mount_window and the node constructors) which lives in the yue/declarative sub-package. is-main declares this package as an executable entry.

3. The first window ​

Replace main.mbt entirely with:

moonbit
fn main {
  if !@yue.initialize() {
    return
  }
  let clicks = @yue.Signal::new(0)
  let _ = @declarative.mount_window(
    [
      @declarative.label("Hello, MoonBit + libyue!", style=[("margin", 20.0)]),
      @declarative.button("Click me", on_click=fn() { clicks.update(fn(n) { n + 1 }) }),
      @declarative.bind(clicks, fn(n) { "Clicked \{n} times" }),
      @declarative.button("Quit", on_click=fn() { @yue.quit() }),
    ],
    title="Tutorial example",
    size=Some((360.0, 220.0)),
    center=true,
    on_close=fn(_w) { @yue.quit() },
  )
  @yue.run()
}
sh
moon run .

A window appears: a label and a button; click the button and the "Clicked N times" line in the middle increments itself — this is signal reactivity: when state changes, bound spots refresh automatically, with no "update the text on click" glue code. mount_window takes an array of nodes that declaratively describes the UI; the label/button styles can be swapped for the component library's themed widgets at any time (see "Next steps").

4. MoonBit syntax in five rows (only what the code above uses) ​

SyntaxMeaning
fn main { ... }program entry
if !cond { return }early return; ! is logical not
let clicks = ...immutable binding (mutable is let mut; not needed in this example)
fn(n) { n + 1 }anonymous function (lambda), passed as a callback
on_click=fn() { ... }named argument: parameters with defaults are passed by name
"Clicked \{n} times"string interpolation; \{expression} embeds any value
Some((360.0, 220.0))the "has a value" form of an optional; None is the absence

Learning MoonBit systematically: the official Chinese tutorial / English tutorial; if you prefer interactive learning-by-playing, the official Tour of MoonBit walks lesson by lesson in the browser.

5. Next steps ​

With the first app running, pick a path by goal:

Want it to look good — reskin in one line, light/dark follows the system automatically:

moonbit
@yue.theme_apply({ ..@yue.default_theme(), primary: "#1E4FA3" })

Customization items like the primary color, corners, and dark mode are in the "Theme" section of components-ui.md; swapping label/button for the component library's themed widgets (label_t/button_t) is in the same document.

Want something to copy — the demo board is the material library:

sh
git clone https://github.com/lb091188/moonbit-libyue && cd moonbit-libyue
moon run examples/showcase    # 14 pages: basic / form / navigation / data display / feedback / events & layout / system integration...

Pick a page from the left menu and write along on the right; each page's source is its own file (examples/showcase/pages/*.mbt) — copy and adapt. To start smaller: moon run examples/hello (original widgets) and moon run examples/hello-themed (themed).

Want to learn systematically — three documents, three facets:

  • declarative.md: describing UI with the node tree (Node/mount) and binding state with Store/Signal — the click counter in section 3 of this tutorial is its minimal form
  • layout.md: the full set of style keys (flex/padding/margin...); both set_style and the style parameter of X::make speak this key set
  • components.md: parameters and methods for every widget, in both setter and X::make styles

Running into problems — adaptation.md records field-tested pitfalls and root causes per platform; for the tray design see tray.md.

6. A note for library contributors ​

This library started from a simple idea (the full story in aboutlibyue.md): writing native desktop apps in MoonBit. It now covers most of the widgets in libyue's C++ documentation, but much remains worth doing:

  • Platform compatibility: the mainline verification so far is Ubuntu (XFCE/GNOME/KDE) and Windows; macOS has CI builds only, no real machine — any issue you hit on a mac is valuable input.
  • Widgets and capabilities: entries in the libyue documentation not yet wrapped, and per-platform adaptation pitfalls — all welcome, starting from an issue.
  • Docs and examples: the tutorial, the component docs, and every page of the demo board welcome improvements.

No barrier to joining: open an issue describing the problem or idea, or send a PR directly. Before starting, remember only three rules — moon check && moon test with zero errors and zero warnings before committing, write field-tested pitfalls together with their fixes into adaptation.md, and commit one cohesive change per batch. The full process (FFI conventions for adding widgets, native-layer releases) is in the repo-root AGENTS.md.