The View DSL

Sycamore uses the view! macro as an ergonomic way to create complex user interfaces.

Dislike macros? Check out the builder API.

The view! macro always produces an expression of type View.

The simplest view is an empty view. This can be constructed like this:

view! {}

Equivalently, you can also use:

View::default()

Elements

Elements have a special terse syntax, as opposed to HTML. You only need to write the tag name a single time and you don’t need a bunch of angle brackets. Text nodes are just string literals.

These elements can also be nested and contain text nodes.

view! {
    // An empty div
    div {}
    // A div with a paragraph
    div {
        p { "Hello, world!" }
    }
    // Custom elements also work
    my-custom-element {}
}

Interpolation

Views can contain interpolated values. Anything that implements Into<View> can be used. This includes strings, numbers, signals, functions that return these types, and other views.

When converting a function into a view, it automatically becomes a dynamic view. The view! macro, however, will do this automatically for you by wrapping any complex expression in a closure.

let value = 123;
let signal = create_signal(456);

view! {
    p {
        "Value: " (value)
    }
    p {
        // Equivalent of (move || signal.get() + 1)
        (signal.get() + 1)
    }
}

Here is another example where we interpolate views.

let details = view! {
    div { "Details" }
};

let outer_view = view! {
    h1 { "Why is Rust the best language" }
    (details)
};

Attributes

Attributes (including classes and ids) can also be specified.

view! {
    p(class="my-class", id="my-paragraph", aria-label="My paragraph", "custom_attribute"="foo")
    button(disabled=true) {
       "My button"
    }
}

All attributes are type-checked so that, e.g. the disabled attribute only accepts values of type bool. For attributes that are not part of the HTML spec, you can wrap the attribute name in quotes to create a custom attribute.

Setting inner html

The special dangerously_set_inner_html attribute is used to set an HTML string as the child of an element. This should generally be avoided if possible because it is a possible security risk. Never pass user input to this attribute as that will create an XSS (Cross-Site Scripting) vulnerability.

view! {
    div(dangerously_set_inner_html="<span>Inner HTML!</span>")

    // DO NOT DO THIS!!!
    div(dangerously_set_inner_html=user_input)
}

Interpolating strings directly do not have this problem since that will always result in a text node, not arbitrary HTML nodes.

Setting node refs

The special r#ref attribute is used to set a NodeRef to point at the element.

let node = create_node_ref();
view! {
    button(r#ref=node)
}

For more details, see Node Ref.

Properties

Properties are set using the prop:* directive.

view! {
    input(r#type="checkbox", prop:indeterminate=true)
}

There are some properties that do not have a matching attribute, such as indeterminate in HTML, and which must be set using the prop:* directive. Other properties such as value have unintuitive behavior when using the attribute version.

Events

Events are attached using the on:* directive.

view! {
    button(on:click=|_| { /* do something interesting */ }) {
        "Click me"
    }
}

Optional attributes

Stringy attributes can also be optional. To make an attribute optional, simply pass in an Option! For example:

let attr = create_signal(None::<String>);
view! {
    div(data-attr=attr)
    // Will render <div></div> instead of <div data-attr></div> if attr is None.
}

Components

Calling components from the view! macro follows essentially the same syntax as for elements.

#[component(inline_props)]
fn Button(class: String) -> View { ... }

view! {
    div {
        Button(class="my-button".to_string())
    }
}

Components can also accept children just like elements.