Getting started

You can write an app and see it without a phone. This takes a few minutes on a Linux computer.

What you cannot do yet

Install on a phone from here with one command. zrn install installs for the user on the computer it runs on. Every build made on a laptop so far is x86_64 (or any for Python). See Status and gaps.

What you need

Install

git clone https://github.com/Zerone-Laboratories/zrn-sdk
cd zrn-sdk
export PATH="$PWD/tools:$PATH"     # zrn and zrn-exec
zrn doctor                         # what is missing, and the command that fixes it

A healthy zrn doctor, from the machine that built this site:

ok   python               3.14
ok   cc                   /usr/sbin/cc
ok   pkg-config           /usr/sbin/pkg-config
ok   make                 /usr/sbin/make
ok   c++                  /usr/sbin/c++
ok   cargo                /usr/sbin/cargo
ok   libraries            all present
ok   libzrnui             .../libzrnui/build
ok   fonts                .../libzrnui/fonts
ok   crypto               python-cryptography
opt  dev key              ~/.config/zrn/keys/dev.key
     fix: zrn keygen (zrn pack also makes one)
ok   headless compositor  kwin_wayland
ok   offscreen GPU        /dev/dri/renderD*

zrn build builds libzrnui from the tree the first time if it is not installed. To install it for the whole system: make -C libzrnui && sudo make -C libzrnui install PREFIX=/usr.

Your first app

zrn new "Notes" --lang c --id io.github.you.notes   # or cpp, python, rust
cd notes
zrn build
zrn render out.png                                  # no phone, no display

It writes zrnapp.toml, icon.svg, main.c, AGENTS.md and .gitignore. A cut-down version of what each template holds:

#include <zrnui.h>

static zui_node *status;

static void tapped(zui_node *button, void *data) {
	zui_set_text(status, "Tapped");
	zui_toast(zui_node_app(button), "Hello from Notes");
}

int main(void) {
	zui_app *app = zui_app_new("io.github.you.notes", "Notes");
	zui_node *page = zui_page(app, "Notes");
	zui_node *card = zui_section(page, "Status");
	status = zui_heading(card, "Not tapped yet");
	zui_button(page, "Tap me", tapped, NULL);
	return zui_run(app, page);
}
#include <zrnui.hpp>

int main() {
	zrn::ui::App app("io.github.you.notes", "Notes");
	auto page = app.page("Notes");
	auto status = page.section("Status").heading("Not tapped yet");
	page.button("Tap me", [&]() {
		status.set_text("Tapped");
		app.toast("Hello from Notes");
	});
	return app.run(page);
}
fn main() {
    let app = zrnui::App::new("io.github.you.notes", "Notes");
    let page = app.page("Notes");
    let status = page.section("Status").heading("Not tapped yet");
    let app_ref = &app;
    page.button("Tap me").on_click(move || {
        status.set_text("Tapped");
        app_ref.toast("Hello from Notes");
    });
    std::process::exit(app.run(page));
}
import zrnui

app = zrnui.App("io.github.you.notes", "Notes")
page = app.page("Notes")
status = page.section("Status").heading("Not tapped yet")


def tapped():
    status.set_text("Tapped")
    app.toast("Hello from Notes")


page.button("Tap me", tapped)
app.run(page)

Look at it

zrn render out.png                          # first page, 360x740 points at scale 2
zrn render --page 1 out.png                 # by creation order
zrn render --page "Wi-Fi" --scroll 300 out.png
zrn render --style dotmatrix out.png
zrn render --scale 4 out.png                # the S9's 1440x2960
zrn run                                     # a phone-sized window
zrn run --headless --script "wait 500; tap 180 170; wait 600; snap after.png; quit"

The style flag is only for looking. An app cannot pick its style: the system does. zrn render draws through the same GLES2 path as a running app, using Mesa's surfaceless EGL. --page counts pages in the order your code creates them, which is not the order they open.

Next

UI elements shows what you can put on a page. Package and publish shows how it reaches people.

Checked against zrn-sdk commit ea16771. Pictures are real renders of code that compiled. Source and proof.