A free, functional, immediate-mode GUI framework for rapid development of desktop applications written in Rust, supported by the Mozilla WebRender rendering engine.


  • Cross-platform GUI toolkit (Windows, Linux, Mac)
  • Immediate-mode API, widgets are composed via function composition
  • CSS styling engine, flexbox-based layout
  • Built-in controls for common user interface elements
  • Create custom widgets via function composition
  • SVG rendering engine, 2D drawing helpers (lines, circles, rects, etc.)
  • OpenGL integration
  • Async I/O helper functions
  • XML serialization and hot-reload, built-in XML-to-Rust compiler
  • Single deployment binary, minimal binary size (roughly 5MB all-incl.),
    CPU (0 - 4%) and RAM usage (~ 50MB total)
  • Hardware-accelerated OpenGL rendering (0.5 - 4ms)
  • React-like DOM diffing for incremental layout and styling changes
Calculator built using the Azul GUI framework Spreadsheet built with Azul GUI framework running on Windows SVG widget displaying a tiger on Linux

Hello world

extern crate azul; use azul::{prelude::*, widgets::{label::Label, button::Button}}; struct DataModel { counter: usize, } impl Layout for DataModel { fn layout(&self, _info: LayoutInfo<Self>) -> Dom<Self> { let label = Label::new(format!("{}", self.counter)).dom(); let button = Button::with_label("Update counter").dom() .with_callback(On::MouseUp, Callback(update_counter)); Dom::div() .with_child(label) .with_child(button) } } fn update_counter(event: CallbackInfo<DataModel>) -> UpdateScreen { += 1; Redraw } fn main() { let mut app = App::new(DataModel { counter: 0 }, AppConfig::default()).unwrap(); let window = app.create_window(WindowCreateOptions::default(), css::native()).unwrap();; }

A functional GUI framework for Rust applications

Easily compose custom widgets together by appending their DOM trees together. No macros, meta-compiler or external scripting language required. You can store your own widgets in external crates and re-use them throughout your projects.

struct DataModel { emails: Vec<EmailInfo>, } struct EmailInfo { from: String, subject: String, profile_pic: ImageId, message: TextId, } impl Layout for DataModel { fn layout(&self, _info: LayoutInfo<Self>) -> Dom<Self> { Dom::div().with_id("email-container") .with_child(self.emails.iter().map(layout_email_component).collect()) } } fn layout_email_component(email: &EmailInfo) -> Dom<DataModel> { Dom::div() .with_child( Dom::div().with_class("email-header") .with_child(Dom::image(email.profile_pic)) .with_child(Dom::label(email.from.clone()) ).with_child( Dom::div().with_class("email-content") .with_child(Dom::label(email.subject.clone()) ) .with_child(Dom::text(email.message)) }

Simple Task-based asynchronous IO

Azul provides simple helpers for asynchronous I/O, which are thread-based. Each task is a single thread, polled for completion by azul.

impl Layout for DataModel { fn layout(&self, _info: LayoutInfo<Self>) -> Dom<Self> { let button = Button::with_label("Connect to database...").dom() .with_callback(On::MouseUp, start_connection); let status = Label::new(match &self.connection_status.lock().unwrap() { Connected => format!("You are connected!"), Err(e) => format!("There was an error: {}", e), InProgress => format!("Loading..."), }).dom(); Dom::div() .with_child(status) .with_child(button) } } fn start_connection(event: CallbackInfo<DataModel>) -> UpdateScreen {|status| *status = ConnectionStatus::InProgress)?; event.state.add_task(Task::new(connect_to_db_async,; Redraw } fn connect_to_db_async(app_data: Arc<Mutex<ConnectionStatus>>, _: DropCheck) { thread::sleep(Duration::from_secs(2)); // simulate slow load app_data.modify(|state| state.connection_status = ConnectionStatus::Connected).unwrap(); }

Simple two-way data binding

Contrary to other IMGUI-like toolkits, azul provides automatic two way data binding - only minimal code changes to go from a static label to a dynamic input form.

struct DataModel { text_input_state: TextInputState, } impl Layout for DataModel { fn layout(&self, info: LayoutInfo<Self>) -> Dom<Self> { // Create a new text input field TextInput::new() // ... bind it to self.text_input - will automatically update .bind(info.window, &self.text_input_state, &self) // ... and render it in the UI .dom(&self.text_input_state) .with_callback(On::KeyUp, Callback(print_text_field)) } } fn print_text_field(event: CallbackInfo<DataModel>) -> UpdateScreen { println!("You've typed: {}",; DontRedraw }

Interested? Check out our user guide!