template!() { /* proc-macro */ }
Expand description
The view
macro uses RSX (like JSX, but Rust!) It follows most of the
same rules as HTML, with the following differences:
- Text content should be provided as a Rust string, i.e., double-quoted:
view! { <p>"Here’s some text"</p> };
- Self-closing tags need an explicit
/
as in XML/XHTML
// ❌ not like this
view! { <input type="text" name="name"> }
// ✅ add that slash
view! { <input type="text" name="name" /> }
- Components (functions annotated with
#[component]
) can be inserted as camel-cased tags. (Generics on components are specified as<Component<T>/>
, not the turbofish<Component::<T>/>
.)
view! { <div><Counter initial_value=3 /></div> }
-
Dynamic content can be wrapped in curly braces (
{ }
) to insert text nodes, elements, or set attributes. If you insert a signal here, Leptos will create an effect to update the DOM whenever the value changes. (“Signal” here meansFn() -> T
whereT
is the appropriate type for that node: aString
in case of text nodes, abool
forclass:
attributes, etc.)Attributes can take a wide variety of primitive types that can be converted to strings. They can also take an
Option
, in which caseSome
sets the attribute andNone
removes the attribute.
let (count, set_count) = create_signal(0);
view! {
// ❌ not like this: `count.get()` returns an `i32`, not a function
<p>{count.get()}</p>
// ✅ this is good: Leptos sees the function and knows it's a dynamic value
<p>{move || count.get()}</p>
// 🔥 with the `nightly` feature, `count` is a function, so `count` itself can be passed directly into the view
<p>{count}</p>
}
- Event handlers can be added with
on:
attributes. In most cases, the events are given the correct type based on the event name.
view! {
<button on:click=|ev| {
log::debug!("click event: {ev:#?}");
}>
"Click me"
</button>
}
- DOM properties can be set with
prop:
attributes, which take any primitive type orJsValue
(or a signal that returns a primitive or JsValue). They can also take anOption
, in which caseSome
sets the property andNone
deletes the property.
let (name, set_name) = create_signal("Alice".to_string());
view! {
<input
type="text"
name="user_name"
value={move || name.get()} // this only sets the default value!
prop:value={move || name.get()} // here's how you update values. Sorry, I didn’t invent the DOM.
on:click=move |ev| set_name.set(event_target_value(&ev)) // `event_target_value` is a useful little Leptos helper
/>
}
- Classes can be toggled with
class:
attributes, which take abool
(or a signal that returns abool
).
let (count, set_count) = create_signal(2);
view! { <div class:hidden-div={move || count.get() < 3}>"Now you see me, now you don’t."</div> }
Class names can include dashes, and since v0.5.0 can include a dash-separated segment of only numbers.
let (count, set_count) = create_signal(2);
view! { <div class:hidden-div-25={move || count.get() < 3}>"Now you see me, now you don’t."</div> }
Class names cannot include special symbols.
let (count, set_count) = create_signal(2);
// class:hidden-[div]-25 is invalid attribute name
view! { <div class:hidden-[div]-25={move || count.get() < 3}>"Now you see me, now you don’t."</div> }
However, you can pass arbitrary class names using the syntax class=("name", value)
.
let (count, set_count) = create_signal(2);
// this allows you to use CSS frameworks that include complex class names
view! {
<div
class=("is-[this_-_really]-necessary-42", move || count.get() < 3)
>
"Now you see me, now you don’t."
</div>
}
- Individual styles can also be set with
style:
orstyle=("property-name", value)
syntax.
let (x, set_x) = create_signal(0);
let (y, set_y) = create_signal(0);
view! {
<div
style="position: absolute"
style:left=move || format!("{}px", x.get())
style:top=move || format!("{}px", y.get())
style=("background-color", move || format!("rgb({}, {}, 100)", x.get(), y.get()))
>
"Moves when coordinates change"
</div>
}
- You can use the
node_ref
or_ref
attribute to store a reference to its DOM element in a NodeRef to use later.
use leptos::html::Input;
let (value, set_value) = create_signal(0);
let my_input = create_node_ref::<Input>();
view! { <input type="text" _ref=my_input/> }
// `my_input` now contains an `Element` that we can use anywhere
- You can add the same class to every element in the view by passing in a special
class = {/* ... */},
argument after ``. This is useful for injecting a class provided by a scoped styling library.
let class = "mycustomclass";
view! { class = class,
<div> // will have class="mycustomclass"
<p>"Some text"</p> // will also have class "mycustomclass"
</div>
}
- You can set any HTML element’s
innerHTML
with theinner_html
attribute on an element. Be careful: this HTML will not be escaped, so you should ensure that it only contains trusted input.
let html = "<p>This HTML will be injected.</p>";
view! {
<div inner_html=html/>
}
Here’s a simple example that shows off several of these features, put together
pub fn SimpleCounter() -> impl IntoView {
// create a reactive signal with the initial value
let (value, set_value) = create_signal(0);
// create event handlers for our buttons
// note that `value` and `set_value` are `Copy`, so it's super easy to move them into closures
let clear = move |_ev| set_value.set(0);
let decrement = move |_ev| set_value.update(|value| *value -= 1);
let increment = move |_ev| set_value.update(|value| *value += 1);
view! {
<div>
<button on:click=clear>"Clear"</button>
<button on:click=decrement>"-1"</button>
<span>"Value: " {move || value.get().to_string()} "!"</span>
<button on:click=increment>"+1"</button>
</div>
}
}