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:
# Linux / macOS
curl -fsSL https://cli.moonbitlang.com/install/unix.sh | bash
# Windows (PowerShell)
irm https://cli.moonbitlang.com/install/powershell.ps1 | iexLinux (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:
sudo apt install build-essential cmake pkg-config \
libgtk-3-dev libpango1.0-dev libfontconfig1-dev libx11-dev libwebkit2gtk-4.1-devIf 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:
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 -Wait2. Create the project
moon new my_app
cd my_app
moon add NoahLiu/moonbit-libyueThe project generated by moon new needs three manual adjustments:
moon.mod: changepreferred_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 namedmain.mbtas the program entry; the template's default file name yieldsMissing main function.Replace the whole
moon.pkgwith (note this is moon's DSL format, not JSON):import { "NoahLiu/moonbit-libyue/yue", "NoahLiu/moonbit-libyue/yue/declarative", } options( "is-main": true, )importlets your code reach the library:@yue.*for the core package (windows, native widgets, painting,Signal/Store), and@declarative.*for the declarative layer (mount/mount_windowand the node constructors) which lives in theyue/declarativesub-package.is-maindeclares this package as an executable entry.
3. The first window
Replace main.mbt entirely with:
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()
}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)
| Syntax | Meaning |
|---|---|
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:
@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:
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 withStore/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_styleand the style parameter ofX::makespeak this key set - components.md: parameters and methods for every widget, in both setter and
X::makestyles
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.