Skip to main content

component

Macro component 

Source
component!() { /* proc-macro */ }
Expand description

A procedural macro for defining reusable HTML components.

The component! macro creates a struct and implements the Component trait, enabling composable and reusable HTML templates.

§Basic usage

use plait::{component, html, render};

component! {
    pub fn Card {
        div(class: "card") {
            #children
        }
    }
}

let html = render(html! {
    @Card {
        "Card content"
    }
});
// Output: <div class="card">Card content</div>

§Syntax

component! {
    [visibility] fn ComponentName[<generics>]([props]) [where clause] {
        // component body (html! syntax)
    }
}

§Component props

Define typed props in the function signature:

component! {
    pub fn Button<'a>(class: &'a str, size: u32) {
        button(class: format_args!("btn {} size-{}", class, size)) {
            #children
        }
    }
}

// Usage:
html! {
    @Button(class: "primary", size: 2) {
        "Click me"
    }
}
// Output: <button class="btn primary size-2">Click me</button>

§Special placeholders

§#children - Child content

The #children placeholder renders content passed between the component’s opening and closing tags:

component! {
    pub fn Wrapper {
        div(class: "wrapper") {
            #children
        }
    }
}

html! {
    @Wrapper {
        span { "Child 1" }
        span { "Child 2" }
    }
}
// Output: <div class="wrapper"><span>Child 1</span><span>Child 2</span></div>

§#attrs - Attribute spreading

The #attrs placeholder spreads HTML attributes passed to the component. This enables attribute forwarding for flexible component APIs:

component! {
    pub fn Button<'a>(class: &'a str) {
        button(class: format_args!("btn {}", class), #attrs) {
            #children
        }
    }
}

// Attributes after `;` are spread via #attrs:
html! {
    @Button(class: "primary"; id: "submit", disabled?: true) {
        "Submit"
    }
}
// Output: <button class="btn primary" id="submit" disabled>Submit</button>

§Generics and lifetimes

Components support full Rust generics:

component! {
    pub fn List<'a, T: std::fmt::Display>(items: &'a [T]) {
        ul {
            for item in items {
                li { (item) }
            }
        }
    }
}

§Component composition

Components can be nested and composed:

component! {
    pub fn Button<'a>(class: &'a str) {
        button(class: format_args!("btn {}", class), #attrs) {
            #children
        }
    }
}

component! {
    pub fn Card {
        div(class: "card") {
            @Button(class: "card-btn"; #attrs) {
                #children
            }
        }
    }
}

§Calling components

Components are called using @ComponentName syntax within html!:

html! {
    @ComponentName(prop1: value1, prop2: value2; attr1: val1, attr2?: optional) {
        // children
    }
}

The syntax is:

  • @ComponentName - Component invocation prefix
  • (...) - Optional arguments section
    • Before ; - Component props (matched to component parameters)
    • After ; - HTML attributes (spread via #attrs)
  • { ... } - Children content (rendered via #children)

If a component has no props, you can omit everything before the semicolon:

html! {
    @Card(; class: "highlighted") {
        "Content"
    }
}

§Generated code

The macro generates:

  1. A struct with the component’s props as public fields:

    pub struct Button<'a> {
        pub class: &'a str,
    }
  2. An implementation of the Component trait:

    impl<'a> Component for Button<'a> {
        fn render(
            self,
            f: &mut HtmlFormatter<'_>,
            attrs: impl FnOnce(&mut HtmlFormatter<'_>),
            children: impl FnOnce(&mut HtmlFormatter<'_>),
        ) {
            // component body
        }
    }