---
name: azul-gui
description: Build native desktop (and web) GUI applications with the azul framework in Rust, C, C++, Python, and 30+ other language bindings — DOM/CSS UI, callbacks, widgets, headless + E2E testing.
---

# Building GUI applications with azul

azul is a cross-platform, MIT-licensed GUI framework for native desktop apps (web support is in progress). Full guide: https://azul.rs/guide.html — API reference: https://azul.rs/api.html (latest version is `0.2.0`).

## What azul is

azul pairs a **retained DOM/CSS UI** with an **immediate-mode-style** developer experience. You never hand-mutate widgets. Instead:

- Your application state lives in a single plain struct, type-erased into a `RefAny` (a refcounted, runtime-checked `Box<dyn Any>`-like handle).
- A **`LayoutCallback`** maps that data to a `Dom` tree. It runs on startup and again whenever a callback asks for a refresh.
- **CSS** (inline or stylesheet, with `:hover`, `:focus`, `@media`, and `@os(...)` queries) styles the DOM and drives the layout solver.
- **Event callbacks** receive the `RefAny`, downcast it to your struct, mutate it, and return an `Update` telling the framework whether to do nothing or rebuild the DOM.

The entire public API is a stable `repr(C)` ABI. Native Rust callbacks are still `extern "C"`. The 30+ language bindings are generated from a single `api.json`, so the *concepts* below map one-to-one across C, C++, Python, etc. — only syntax differs.

Depth: see https://azul.rs/guide/architecture.md, https://azul.rs/guide/dom.md, and https://azul.rs/guide/internals/dom.md.

## Mental model: a minimal Rust counter app

`App` owns the data + config; `WindowCreateOptions::create(layout_fn)` wires the layout callback; callbacks return `Update`. Minimal, correct against the current API:

```rust
use azul::prelude::*;
use azul::widgets::Button;

// Your application state: a single plain struct.
struct DataModel {
    counter: usize,
}

// Maps DataModel -> Dom. Runs on startup and on Update::RefreshDom.
extern "C"
fn my_layout_func(data: RefAny, _: LayoutCallbackInfo) -> Dom {
    // Runtime-checked downcast back to the concrete struct.
    let counter = match data.downcast_ref::<DataModel>() {
        Some(d) => format!("{}", d.counter),
        None => return Dom::create_body(),
    };

    let label = Dom::create_p_with_text(counter.as_str())
        .with_css("font-size: 50px");

    let mut button = Button::create("Update counter");
    // clone() bumps the RefAny refcount (thread-safe); it is NOT a deep copy.
    button.set_on_click(data.clone(), my_on_click);
    let button = button.dom().with_css("flex-grow: 1");

    Dom::create_body()
        .with_child(label)
        .with_child(button)
}

extern "C"
fn my_on_click(mut data: RefAny, _: CallbackInfo) -> Update {
    let mut data = match data.downcast_mut::<DataModel>() {
        Some(s) => s,
        None => return Update::DoNothing,
    };
    data.counter += 1;
    Update::RefreshDom // queue a relayout
}

fn main() {
    let data = DataModel { counter: 0 };
    let app_config = AppConfig::create();
    let window = WindowCreateOptions::create(my_layout_func);
    let app = App::create(RefAny::new(data), app_config);
    app.run(window);
}
```

Notes that bite: every callback is `extern "C"`; `downcast_ref`/`downcast_mut` are runtime-checked and may fail (return `Update::DoNothing`); `data.clone()` bumps a refcount, it does not deep-copy; `with_css` is the consuming builder form of `set_css`. Full walk-through: https://azul.rs/guide/hello-world/rust.md.

## Feature set

- DOM construction + CSS layout (flexbox-like solver, `:hover`/`:focus`/`@media`/`@os`)
- Built-in widgets (Button, TextInput, CheckBox, DropDown, lists, scroll regions, ...)
- OpenGL custom rendering surfaces
- Images and SVG
- Text input + text selection + IME
- Accessibility (screen-reader tree)
- Clipboard and native file dialogs
- Networking + background tasks (threads / timers) for async work
- Real-time media
- Routing (swap the layout callback, SPA-style)
- Headless rendering + JSON-driven E2E testing
- Web deployment (in progress) and mobile deployment

## Using the API reference as a search tool

Treat the published reference as your API search backend via WebFetch:

1. Fetch the version manifest to learn the latest version:
   `https://azul.rs/api/index.json` → `{ "latest": "0.2.0", "versions": [...] }`
2. Fetch the compact search index for that version and query it locally:
   `https://azul.rs/api/<version>.search.json` (e.g. `https://azul.rs/api/0.2.0.search.json`).
   Schema — top level `{ "v": version, "e": [entries] }`. Each entry uses short keys:

| key | meaning |
|-----|---------|
| `k` | kind: `m` module, `s` struct, `e` enum, `fp` fnptr, `ev` enum variant, `f` struct field, `fn` method, `cn` constructor |
| `n` | the entity's own name |
| `m` | module it lives in |
| `p` | parent class (for variants/fields/methods/constructors) |
| `a` | anchor fragment in the api page (no leading `#`) |
| `d` | plain-text doc body |
| `s` | signature line (fns/constructors/fields/callbacks) |

To answer "what methods does `Dom` have?" filter for `k == "fn" && p == "Dom"` and read each `s`. To find a type, match `n` against `k in (s, e, fp)`.
3. For prose + full rendering, fetch `https://azul.rs/api/<version>.html` and deep-link using the entry `a` anchor (`#<a>`).

## Verification: headless rendering + E2E testing

Always verify your app builds and behaves before declaring done — and you can do it windowless:

- **Headless render**: run with `AZ_BACKEND=headless` to execute the layout/render pipeline without opening a window (CI-friendly, lets you assert the DOM/layout was produced).
- **E2E test runner**: set `AZ_E2E=<path-to-json>` (or pass the JSON inline) to drive the app through a scripted sequence of synthetic events and state assertions. The test JSON schema lives in `tests/e2e/*.json` in the repo.
- Assert application state by checking your data model after the scripted events, and assert the rendered tree via the headless display list.

Details: https://azul.rs/guide/e2e-testing.md and https://azul.rs/guide/headless-rendering.md.

## Other languages

Rust is first-class, but the bindings are all generated from the same `api.json`, so the App / RefAny / LayoutCallback / Dom / CSS / Update model is identical everywhere — only the syntax changes. Start from the per-language hello world:

- https://azul.rs/guide/hello-world/rust.md
- https://azul.rs/guide/hello-world/c.md
- https://azul.rs/guide/hello-world/cpp.md
- https://azul.rs/guide/hello-world/python.md

For the full document dump (all guides concatenated) fetch https://azul.rs/llms-full.txt; for a structured index fetch https://azul.rs/llms.txt.
