You are reading release documentation. Read the latest development docs.
Attribute Passthrough
Say you’re working on a reusable component library for Sycamore. You provide
components such as Button, Container, and Grid etc. which wraps native
HTML elements with extra behavior.
However, a downstream user of your library uses the Button component and wants
to set a custom id attribute on the wrapped button element. How do you
solve this?
One way to do this is to add an additional id prop to your component and
forward it to the button element. However, this approach is not scalable. If
we can support setting a custom id attribute, why not also support class,
disabled, etc. as well as all the different possible events.
For such scenarios, attribute passthrough comes to the rescue!
The attributes prop
You can add a special attributes prop with the Attributes type to your
component. You must then specify what kind of element these attributes are
expected to be applied on using the #[component(extend_element!(...))] syntax.
Finally, you can spread the attributes on the HTML element using the
attribute spread syntax.
// `html` means that we are spreading onto an HTML element.
// The other possible value is `svg`.
//
// `button` means that we are spreading onto a `button` element.
#[component(extend_element!(html:button))]
fn Button(
attributes: Attributes,
// We can still accept children.
children: Children,
// We can still accept other props besides `attributes`.
other_prop: i32,
) - View {
view! {
// The spread (`..xyz`) syntax applies all the attributes onto the element.
button(..attributes)
}
}
Passing attributes to components
Downstream users of your component can set HTML attributes (and events, props, etc.) in the same way as they would on an element.
view! {
Button(
// Pass as many HTML attributes/events as you want.
id="my-button",
class="btn btn-primary",
on_click=|_| {}
// You can still pass in regular props as well.
other_prop=123,
) {
// Children still gets passed into the `children` prop.
"Click me"
}
}
Attributes are still fully type checked and will cause compile errors for unknown attributes or wrong types.
For attributes applied to an element, pass static values directly and use an
explicit closure for reactive expressions, for example
button(disabled=move || is_disabled.get(), ..attributes). Attribute values
passed to a component are ordinary component props: they are not automatically
wrapped in closures, so provide a closure when the receiving prop expects a
dynamic value.
Intercepting attributes
To intercept an attribute, for instance, to modify it, simply add the attribute as a regular prop to your component. Say we wanted to add some classes to our button in addition to any extra classes we get from attribute passthrough. We could then write something like this.
use sycamore::web::StringAttribute;
#[component(extend_elements!(html:button))]
fn Button(
attributes: Attributes,
// StringAttribute is a type alias for MaybeDyn containing a string.
class: StringAttribute,
) - View {
// We still want the attribute to be reactive.
let class = move || format!("{class} custom-button");
view! {
button(class=class, ..attributes)
}
}
Because of how Rust’s method resolution works, setting class on our Button
component will reference the prop rather then the attribute, which lets us
intercept it.