How to Use the view! Macro for Conditional Attributes and Control Flow in Topcoat
The view! macro in Topcoat lets you write HTML-like templates in Rust that support conditional attributes and full Rust control-flow constructs including if, for, match, and let statements.
The tokio-rs/topcoat framework provides a type-safe templating system through the view! macro, which transforms HTML-like syntax into Rust code. Unlike traditional template engines that require separate templating languages, Topcoat allows you to leverage native Rust control-flow directly within your markup. This integration enables sophisticated UI patterns with automatic attribute omission for false or None values.
Conditional Attributes in view!
Topcoat’s attribute system evaluates expressions to determine whether an attribute should appear in the final output. This behavior is implemented in crates/topcoat-view/src/props.rs, where the omission logic handles boolean and optional values according to standard HTML semantics.
Boolean and Optional Attribute Values
When you supply an expression inside parentheses for an attribute value, the macro evaluates it at runtime. If the expression returns false or None, the attribute is omitted entirely. If it returns true, the attribute appears with an empty value. If it returns Some(value), the attribute renders with the supplied value.
view! {
<button
disabled=(is_disabled)
aria-current=(is_current.then_some("page"))
title=(maybe_title)
>
"Save"
</button>
}
In this example, disabled appears only when is_disabled is true, aria-current emits only when is_current is true, and title renders only when maybe_title contains Some value. Literal attributes like disabled="" are always emitted regardless of value.
Dynamic Attribute Generation with Control Flow
The view! macro supports control-flow constructs directly inside attribute lists. You can use if, for, and match statements to emit zero or many attributes based on runtime conditions.
view! {
<a href="/posts"
if current {
aria-current="page"
class="active"
}
>"Posts"</a>
}
Control Flow Inside view! Blocks
The macro expansion in crates/topcoat-view/src/view.rs supports full Rust control-flow statements within the markup body. Each branch can contain complete markup trees, and the macro generates code that appends nodes to the view builder appropriately. Dynamic text content like (post.title) uses the unescaped text handling implemented in crates/topcoat-view/src/unescaped.rs.
Conditional Rendering with if/else
Use standard Rust if and else blocks to toggle between different markup structures.
view! {
if user.is_some() {
<a href="/account">"Account"</a>
} else {
<a href="/login">"Sign in"</a>
}
}
Iterating with for Loops
The for construct generates child nodes by iterating over collections. The loop variable is available within the markup block.
view! {
<ul>
for post in posts {
<li><a href=(post.url)>(post.title)</a></li>
}
</ul>
}
You can also use for loops inside attribute lists to generate multiple attributes dynamically from collections.
view! {
<div
for (name, value) in attrs {
(name)=(value)
}
></div>
}
Pattern Matching with match
The match statement works inside both markup bodies and attribute lists, allowing you to select attributes or content based on enum variants.
view! {
<article
match state {
State::Open => class="open",
State::Closed => aria-disabled="true",
}
></article>
}
Variable Bindings with let
Use let bindings within attribute lists to compute values once and reference them in multiple places.
view! {
<a
let href = post.url()
href=(href)
data-slug=(post.slug)
> (post.title) </a>
}
Summary
- Conditional attributes automatically omit when expressions evaluate to
falseorNone, and appear whentrueorSome(value). - Control-flow constructs including
if/else,for,match, andletwork inside both markup bodies and attribute lists. - The macro expansion generates efficient Rust code that calls the
Viewbuilder API incrates/topcoat-view/src/view.rs. - Dynamic attribute generation allows emitting multiple attributes from collections using
forloops inside attribute lists.
Frequently Asked Questions
Can I use arbitrary Rust expressions inside the view! macro?
Yes, the view! macro accepts any valid Rust expression inside parentheses for attribute values or within control-flow blocks. The macro transpiles your markup into standard Rust code, so you can call methods, use match expressions, and access variables from the surrounding scope.
How does Topcoat handle boolean attributes like disabled or checked?
Topcoat follows standard HTML boolean attribute semantics. When a boolean expression evaluates to true, the attribute is rendered with an empty value. When false, the attribute is omitted entirely. This logic is implemented in crates/topcoat-view/src/props.rs.
Can I nest control-flow statements inside each other?
Yes, you can nest if, for, and match statements arbitrarily within a view! block. Each nested construct generates the appropriate Rust control-flow code that appends to the view builder, maintaining full type safety throughout the template hierarchy.
Where can I find the complete syntax documentation for the view! macro?
The official syntax documentation and examples are located in crates/topcoat-view/macro/docs/view.md within the tokio-rs/topcoat repository. This file covers all supported constructs including conditional attributes, control-flow integration, and literal value handling.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →