egui
Immediate-mode GUIs in Rust with egui and eframe - fast iteration for tools, inspectors, and utilities.
Recipe
eframe::run_native(
"My Tool",
eframe::NativeOptions::default(),
Box::new(|cc| {
egui_extras::install_image_loaders(&cc.egui_ctx);
Ok(Box::new(MyApp::default()))
}),
)?;When to reach for this:
- Internal dev tools and live data visualizers
- Game engine debug UI (pairs with Bevy
bevy_egui) - Prototyping layouts without CSS/HTML
- Apps prioritizing developer speed over native look
Working Example
struct MyApp {
name: String,
age: u32,
}
impl Default for MyApp {
fn default() -> Self {
Self {
name: "Alice".into(),
age: 30,
}
}
}
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("Profile");
ui.text_edit_singleline(&mut self.name);
ui.add(egui::Slider::new(&mut self.age, 0..=120));
if ui.button("Greet").clicked() {
println!("Hello {}, age {}", self.name, self.age);
}
});
}
}What this demonstrates:
- Mutable app state on
selfeach frame - Standard widgets: text edit, slider, button
- Central panel layout baseline
Deep Dive
How It Works
- Each frame: input events ->
update-> layout widgets -> paint. - Widget IDs derived from memory locations and labels - stable IDs matter for focus.
Contextrequests repaint when animations or async data arrive.- Textures and fonts managed by egui atlas.
Immediate vs Retained
| Mode | egui | iced |
|---|---|---|
| Model | Rebuild UI each frame | Diff widget tree |
| Best for | Tools | Consumer apps |
Gotchas
- Heavy work in
update- drops frames. Fix: compute in background thread, poll results via channel. - Unstable widget IDs - focus jumps. Fix:
ui.push_idfor dynamic lists. - Not accessibility-first - screen reader support limited vs native toolkits. Fix: choose iced/Tauri for a11y-critical apps.
- High DPI scaling - verify on retina displays; set
pixels_per_pointappropriately. - Default fonts and CJK - load custom font bytes for non-Latin scripts.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| iced | Elm architecture, native feel | Need HTML ecosystem |
| Tauri | Web designers on team | Want single-language UI |
| Slint | Declarative UI with design tool | Pure immediate-mode preference |
| GTK via gtk-rs | GNOME integration | Cross-platform identical UX |
FAQs
eframe vs custom winit?
eframe is fastest start; custom integration when embedding in game engine.
Theming?
Visuals::dark() / light() or custom Spacing and Style on Context.
Plotting?
egui_plot crate for line/scatter charts inside same immediate-mode loop.
Async data?
ctx.request_repaint() when background thread fills buffer; read under Mutex in update.
Mobile?
egui on Android/iOS possible but not primary focus - test touch targets carefully.
Bevy integration?
bevy_egui plugin for in-game overlays - see game development section.
Persistence?
Serialize state to JSON on exit - not automatic like retained UI frameworks.
Input focus?
Use consistent labels/ids in loops; Memory helper for window positions.
Performance profiling?
puffin or tracy integration; reduce widget count per panel.
State architecture?
See State & Architecture for larger apps.
Related
- GUI Basics - egui intro example
- State & Architecture - scaling state
- iced - retained alternative
- Packaging & Distribution - ship binary
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.