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 if and for blocks 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:

  1. Clone the repository:

    git clone https://github.com/nayandas69/iced-markup
    cd iced-markup
    
  2. 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.