lustre/component
Lustre’s component system is built on top of the Custom Elements API and the Shadow DOM API. This module helps you configure new components and interact with existing ones.
While it’s not required, understanding the spec and how it works will help you get the most out of Lustre’s component system. The following resources are a great place to start:
-
https://developer.mozilla.org/en-US/docs/Web/Web_Components
-
https://css-tricks.com/web-components-demystified/
-
https://github.com/web-padawan/awesome-web-components
Examples
We have a small number of examples showing how to set up and use components that are a great place to see some code:
This list of examples is likely to grow over time, so be sure to check back every now and then to see what’s new!
Getting help
If you’re having trouble with Lustre or not sure what the right way to do something is, the best place to get help is the Gleam Discord server. You could also open an issue on the Lustre GitHub repository.
Types
The configuration for a Lustre component
. In
Lustre, components are real custom elements. You can use this configuration
to define what features the component supports and what platform functionality
it should have access to.
pub opaque type Config(msg)
Options are used to configure a component’s behaviour.
-
on_attribute_change
lets you register a callback that runs whenever anattribute
is set on your component in the DOM. -
on_property_change
lets you register a decoder to run whenever aproperty
is set on the component.See this note on the difference between attributes and properties.
-
form_associated
marks the component as “form-associated”, allowing your component to participate in form submission and get accesss to form-specific events.-
on_form_autofill
lets you register a callback that runs when the browser autofills your component’s"value"
attribute. -
on_form_reset
lets you register a message that runs when a form containing your component is reset. -
on_form_restore
lets you register a callback that runs when the browser restores your component’s"value"
attribute, often after a page reload or the user navigating back or forward in their history.
-
-
open_shadow_root
lets you control whether the component uses an open or closed shadow root. -
adopt_styles
lets you control whether the component should attempt to adopt stylesheets from its parent document. All Lustre components use shadow DOM to get access to certain features like form-associated elements or HTML slots. Unfortunately, this means typically styles in the shadow DOM are isolated from the parent document.Setting
adopt_styles
toTrue
tells Lustre to attempt to adopt or clone stylesheets from the parent document into the shadow DOM. This can give you an experience similar to components in other frameworks like React or Vue.
Note: Not all options are available for server components. For example server components cannot be form-associated and participate in form submission.
pub opaque type Option(msg)
Values
pub fn adopt_styles(adopt: Bool) -> Option(a)
Configure whether a component should attempt to adopt stylesheets from its parent document. Components in Lustre use the shadow DOM to unlock native web component features like slots, but this means elements rendered inside a component are isolated from the document’s styles.
To get around this, Lustre can attempt to adopt all stylesheets from the parent document when the component is first created; meaning in many cases you can use the same CSS to style your components as you do the rest of your application.
By default, this option is enabled. You may want to disable this option if you are building a component for use outside of Lustre and do not want document styles to interfere with your component’s styling
pub fn clear_form_value() -> Effect(a)
Clear a form value previously set with set_form_value
.
When the form is submitted, this component’s value will not be included in
the form data.
pub fn default_slot(
attributes: List(Attribute(a)),
fallback: List(Element(a)),
) -> Element(a)
Create a default slot for a component. Any elements rendered as children of
the component will be placed inside the default slot unless explicitly
redirected using the slot
attribute.
If no children are placed into the slot, the fallback
elements will be
rendered instead.
To learn more about Shadow DOM and slots, see this excellent guide:
https://javascript.info/slots-composition
pub fn exportparts(names: List(String)) -> Attribute(a)
While the part
attribute can be used to expose parts of a component
to its parent, these parts will not automatically become available to the
document when components are nested inside each other.
The exportparts
attribute lets you forward the parts of a nested component
to the parent component so they can be styled from the parent document.
Consider we have two components, "my-component"
and "my-nested-component"
with the following view
functions:
import gleam/int
import lustre/attribute.{property}
import lustre/component
import lustre/element.{element}
import lustre/element/html
fn my_component_view(model) {
html.div([], [
html.button([], [html.text("-")]),
element(
"my-nested-component",
[
property("count", model.count),
component.exportparts(["count"]),
],
[]
)
html.button([], [html.text("+")]),
])
}
fn my_nested_component_view(model) {
html.p([component.part("count")], [html.text(int.to_string(model.count))])
}
The <my-nested-component />
component has a part called "count"
which the
<my-component />
then forwards to the parent document using the "exportparts"
attribute. Now the following CSS can be used to style the <p>
element nested
deep inside the <my-component />
:
my-component::part(count) {
color: red;
}
Notice how the styles are applied to the <my-component />
element, not the
<my-nested-component />
element!
To learn more about the CSS Shadow Parts specification, see:
-
https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/exportparts
-
https://developer.mozilla.org/en-US/docs/Web/CSS/::part
pub fn form_associated() -> Option(a)
Mark a component as “form-associated”. This lets your component participate in form submission and respond to additional form-specific events such as the form being reset or the browser autofilling this component’s value.
Note: form-associated components are not supported in server components for both technical and ideological reasons. If you’d like a component that participates in form submission, you should use a client component!
pub fn named_slot(
name: String,
attributes: List(Attribute(a)),
fallback: List(Element(a)),
) -> Element(a)
Create a named slot for a component. Any elements rendered as children of
the component with a slot
attribute matching the name
will be
rendered inside this slot.
If no children are placed into the slot, the fallback
elements will be
rendered instead.
To learn more about Shadow DOM and slots, see this excellent guide:
https://javascript.info/slots-composition
pub fn on_attribute_change(
name: String,
decoder: fn(String) -> Result(a, Nil),
) -> Option(a)
Register a decoder to run whenever the named attribute changes. Attributes
can be set in Lustre using the attribute
function, set directly on the component’s HTML tag, or in JavaScript using
the setAttribute
method.
Attributes are always strings, but your decoder is responsible for decoding the string into a message that your component can understand.
pub fn on_form_autofill(handler: fn(String) -> a) -> Option(a)
Register a callback that runs when the browser autofills this
form-associated component’s "value"
attribute. The
callback should convert the autofilled value into a message that you handle
in your update
function.
Note: server components cannot participate in form submission and configuring this option will do nothing.
pub fn on_form_reset(msg: a) -> Option(a)
Set a message to be dispatched whenever a form containing this form-associated component is reset.
Note: server components cannot participate in form submission and configuring this option will do nothing.
pub fn on_form_restore(handler: fn(String) -> a) -> Option(a)
Set a callback that runs when the browser restores this
form-associated component’s "value"
attribute. This is
often triggered when the user navigates back or forward in their history.
Note: server components cannot participate in form submission and configuring this option will do nothing.
pub fn on_property_change(
name: String,
decoder: Decoder(a),
) -> Option(a)
Register decoder to run whenever the given property is set on the component.
Properties can be set in Lustre using the property
function or in JavaScript by setting a property directly on the component
object.
Properties can be any JavaScript object. For server components, properties will be any JSON-serialisable value.
pub fn open_shadow_root(open: Bool) -> Option(a)
Configure whether a component’s Shadow Root is open or closed. A closed shadow root means the elements rendered inside the component are not accessible from JavaScript outside the component.
By default a component’s shadow root is open. You may want to configure this option manually if you intend to build a component for use outside of Lustre.
pub fn part(name: String) -> Attribute(a)
Lustre’s component system is built on top the Custom Elements API and the
Shadow DOM API. A component’s view
function is rendered inside a shadow
root, which means the component’s HTML is isolated from the rest of the
document.
This can make it difficult to style components from CSS outside the component.
To help with this, the part
attribute lets you expose parts of your component
by name to be styled by external CSS.
For example, if the view
function for a component called "my-component
“
looks like this:
import gleam/int
import lustre/component
import lustre/element/html
fn view(model) {
html.div([], [
html.button([], [html.text("-")]),
html.p([component.part("count")], [html.text(int.to_string(model.count))]),
html.button([], [html.text("+")]),
])
}
Then the following CSS in the parent document can be used to style the
<p>
element:
my-component::part(count) {
color: red;
}
To learn more about the CSS Shadow Parts specification, see:
-
https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/part
-
https://developer.mozilla.org/en-US/docs/Web/CSS/::part
pub fn remove_pseudo_state(value: String) -> Effect(a)
Remove a custom state set by set_pseudo_state
.
pub fn set_form_value(value: String) -> Effect(a)
Set the value of a form-associated component. If the
component is rendered inside a <form>
element, the value will be
automatically included in the form submission and available in the form’s
FormData
object.
pub fn set_pseudo_state(value: String) -> Effect(a)
Set a custom state on the component. This state is not reflected in the DOM
but can be selected in CSS using the :state
pseudo-class. For example,
calling set_pseudo_state("checked")
on a component called "my-checkbox"
means the following CSS will apply:
my-checkbox:state(checked) {
border: solid;
}
If you are styling a component by rendering a <style>
element inside the
component, the previous CSS would be rewritten as:
:host(:state(checked)) {
border: solid;
}
pub fn slot(name: String) -> Attribute(a)
Associate an element with a named slot in a component. Multiple elements can be associated with the same slot name.
To learn more about Shadow DOM and slots, see:
https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/slot
https://javascript.info/slots-composition