Function Tags
Function tags provide an HTML-like syntax for calling template functions, making component composition intuitive and readable.
Basic Syntax
Function tags look like HTML tags but start with <@ and call template functions:
@* Self-closing function tag *@
<@header::apply title="Page Title" />
@* Function tag with content *@
<@layout::apply title="My App">
Page content goes here
@layout::apply> The apply Shorthand
If the function name is apply, you can omit it from the tag:
@* These are equivalent: *@
<@layout::apply title="Page">Content@layout::apply>
<@layout title="Page">Content@layout>
@* Self-closing shorthand: *@
<@header::apply title="Nav" />
<@header title="Nav" /> This shorthand is especially useful since apply is the conventional name for a template's main render function.
Attribute Types
Function tags support several types of attributes:
String Literals (Quoted)
<@button label="Click me" />
<@modal title="Confirm Action" /> Expressions (With @)
@* Simple variable *@
<@user_card user=@current_user />
@* Complex expression *@
<@pagination total=@(pages.len()) current=@page_number />
@* Method calls *@
<@list items=@users.iter().filter(|u| u.active).collect() /> Boolean Flags
@* Just the name = true *@
<@button label="Submit" disabled />
<@input type="text" readonly required /> Type-Aware Coercion
Attribute values are automatically coerced to match the expected parameter type. No @ needed for primitive literals:
@fn input(name: String, required: bool, max_length: i32, ratio: f64) {
}
@* All of these work - values coerced to parameter type *@
<@input name="email" required max_length=100 ratio=0.5 />
<@input name="age" required=true max_length=3 ratio=1.0 />
<@input name="note" required=false max_length=500 ratio=0.75 /> Coercion rules:
bool: attribute presence =true;=true/=false→ bool literali8-i128,isize: numeric strings → signed integer literalu8-u128,usize: numeric strings → unsigned integer literalf32,f64: float strings → float literal- Whitespace is trimmed:
max=" 42 "works correctly - Complex types still need
@:data=@some_expression
Variable Shorthand
When a parameter name matches a variable name, use the shorthand:
@* Instead of: *@
<@user_card user=@user active=@active />
@* Use shorthand: *@
<@user_card @user @active />
@* For references: *@
<@form @&validation_errors /> A common mistake is putting @ inside quotes. This is wrong:
@* ❌ WRONG - @ inside quotes is literal text *@
<@component name="@user.name" />
@* ✅ CORRECT - use unquoted expression *@
<@component name=@user.name />Default Parameters
Template functions can have default parameter values. When calling, you can omit any parameter that has a default:
@* Define a function with defaults *@
@fn button(
label: String,
variant: String = "primary",
size: String = "md",
disabled: bool = false,
) {
}
@* Call with all defaults *@
<@button label="Click" />
@* Override just variant *@
<@button label="Cancel" variant="secondary" />
@* Override multiple *@
<@button label="Delete" variant="danger" size="lg" />
@* Set boolean flag *@
<@button label="Wait..." disabled /> Default Value Types
Default values can be any valid Rust expression:
@fn card(
title: String,
class: String = "card",
max_width: u32 = 400,
show_border: bool = true,
icon: Option = None,
theme: Theme = Theme::Light,
) {
...
} Closure and Callback Defaults
Both render callbacks and regular closures can have default values:
Render Callback Defaults
@* Render callback with default template content *@
@fn layout(header: @() = @() {
Default Header
}) {
@@header()
}
@* Alternative: lambda with @@Out type *@
@fn layout(header: @() = |_: @Out| {
Default Header
}) {
@@header()
}
@* With parameters *@
@fn data_table(row_renderer: @(&Item) = @(item: &Item) {
@@item.name
}) {
@@for item in items { @@row_renderer(item) }
} Regular Closure Defaults
@* Closure with default implementation *@
@fn apply(square: |a: i32| -> i32 = |a: i32| { a * a }) {
Result: @@square(7)
}
@* Filter function with default *@
@fn filtered_list(
items: Vec- ,
filter: |&Item| -> bool = |item: &Item| { item.active }
) {
@@for item in items.iter().filter(filter) {
- @@item.name
}
} Content Parameter
If a function's last parameter is of type Content, the tag body automatically becomes that parameter:
@* Define function with Content parameter *@
@fn card(title: String, content: Content) {
@title
@safe(content.render())
}
@* The tag body becomes the content parameter *@
<@card title="Welcome">
This entire section becomes the content parameter.
It can contain any HTML or template code.
@card> Optional Content
Make content optional with Option<Content>:
@fn modal(
title: String,
footer: Option = None,
content: Content,
) {
@title
@safe(content.render())
@if let Some(f) = footer {
}
} Closing Tags
Function tags can be closed in several ways:
Full Closing Tag
<@layout::apply title="Page">
Content here
@layout::apply> Shorthand </@>
Closes the nearest open function tag:
<@layout title="Page">
<@sidebar>
Sidebar content
@> @* Closes sidebar *@
Main content
@> @* Closes layout *@ Self-Closing
<@icon name="check" />
<@button label="Submit" /> Calling Components from Components
Function tags shine when building component hierarchies:
@import "layouts/base.wtz" as base
@import "components/header.wtz" as header
@import "components/sidebar.wtz" as sidebar
@import "components/footer.wtz" as footer
@fn apply(title: String, user: Option, content: Content) {
<@base title=@title>
<@header title=@title user=@user />
<@sidebar user=@user />
@safe(content.render())
<@footer />
@base>
} Nested Function Tags
Function tags can be deeply nested:
<@page_layout title="Dashboard">
<@content_area>
<@card title="Statistics">
<@stats_grid>
@for stat in statistics {
<@stat_item
label=@stat.label
value=@stat.value
icon=@stat.icon
/>
}
@stats_grid>
@card>
<@card title="Recent Activity">
@for item in activity {
<@activity_item @item />
}
@card>
@content_area>
@page_layout> Conditional Function Tags
Use control flow with function tags:
@* Conditionally render different components *@
@if user.is_premium {
<@premium_dashboard @user />
} else {
<@basic_dashboard @user />
}
@* Conditional inside function tag content *@
<@card title="User Profile">
@if let Some(avatar) = user.avatar {
<@avatar src=@avatar />
}
@user.name
@card> Conditional Attributes
Use @if inside function tags for conditional attributes:
<@button
label="Submit"
@if is_loading { disabled }
@if is_primary { variant="primary" }
/>
<@input
name="email"
@if let Some(err) = errors.get("email") {
error=@err
class="invalid"
}
/> Underscore Parameter Convention
Function parameters often use underscore prefixes to avoid conflicts with Rust keywords. Function tags can omit these underscores:
@* Function with underscore parameters *@
@fn button(_type: String = "button", _class: String = "btn") {
}
@* Call without underscores (preferred) *@
<@button type="submit" class="btn-primary" />
@* With underscores also works *@
<@button _type="submit" _class="btn-primary" /> Complete Example
Here's a complete example showing a page layout with multiple components:
@* templates/layouts/app.wtz *@
@import "components/nav.wtz" as nav
@import "components/footer.wtz" as footer
@use crate::models::User
@fn apply(
title: String,
user: Option = None,
show_sidebar: bool = true,
content: Content,
) {
@title - MyApp
<@nav user=@user />
<@footer />
}
@* Usage from another template: *@
<@app::apply title="Dashboard" user=@current_user>
Welcome back!
<@dashboard_widgets user=@current_user />
@app::apply>