Introduction
Welcome to the official iced-markup guide.
iced-markup is a declarative, JSX-inspired Domain Specific Language (DSL) built for the Iced GUI library in Rust.
The goal of this project is to take the pain out of building complex, nested layouts by replacing deeply nested builder chains with a clean, readable markup syntax that expands at compile-time—meaning zero runtime cost and maximum performance.
Why use iced-markup?
- Readability: Your UI code actually looks like a UI, not a giant tower of
.push()calls. - Speed: It uses procedural macros to generate the exact code you would have written by hand.
- Native Logic: Use real Rust
ifandforblocks inside your UI definitions. - Power: Shorthands for events, styling pipes, and component slots make high-level UI construction effortless.
Installation
To get started with iced-markup, you need to add it to your Cargo.toml.
From crates.io
Add the dependency to your project:
cargo add iced-markup
Or manually:
[dependencies]
iced = "0.13"
iced-markup = "0.1.0"
Running Examples
If you want to play with the code locally:
-
Clone the repository:
git clone https://github.com/nayandas69/iced-markup cd iced-markup -
Run the counter example:
cargo run --example counter
Usage Guide
The logic behind iced-markup is simple: we map Iced builder calls to a JSX-inspired syntax.
Basic Syntax
The general shape of a widget is:
widget_name(constructor_args) ![attributes] | style { children }
Widgets
Every widget corresponds to a function in iced::widget.
#![allow(unused)] fn main() { view! { text("Hello") {} } }
Attributes
Attributes are mapped to method calls on the widget. They go inside ![...].
#![allow(unused)] fn main() { view! { column ![spacing: 20, padding: 40] { text("Welcome") {} } } }
Event Shorthands
Use the + prefix for common events:
+click:on_press+input:on_input+submit:on_submit
#![allow(unused)] fn main() { button("Submit") ![+click: Message::Submit] {} }
Styling Pipes
Use the | operator to apply styles easily.
#![allow(unused)] fn main() { text("Warning") | |_| text::danger {} }
Dynamic Logic
iced-markup isn't just a static template; it's high-performance code generation.
Native Control Flow
You can use real Rust expressions directly inside the macro. This is much cleaner than building conditional vectors manually.
Conditional Rendering
#![allow(unused)] fn main() { view! { column { if user.is_online { text("Online") | |_| text::success {} } else { text("Offline") | |_| text::muted {} } } } }
List Rendering
#![allow(unused)] fn main() { view! { column { for item in &self.items { text(item) {} } } } }
Component Slots
The @ slot syntax allows you to inject special handlers or named parameters into a widget.
#![allow(unused)] fn main() { scrollable { @on_scroll: Message::Scrolled, column { // ... } } }
Future Ideas
iced-markup is just getting started. Here are some ideas for future versions:
The Roadmap
- Hot Reloading Integration: Exploring ways to reload markup without full re-compiles.
- Custom Style DSL: More concise way to define styles directly in the markup.
- Expanded Widget Support: Better out-of-the-box support for less common Iced widgets.
- IDE Tooling: Language server features (LSP) for syntax highlighting and autocomplete in VS Code and Neovim.
Why it matters
Building UIs in Rust should be as fast and pleasant as building for the web, but with the performance and safety of a compiled language. iced-markup is a giant step in that direction.