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:

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 an attribute is set on your component in the DOM.

  • on_property_change lets you register a decoder to run whenever a property 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 to True 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

Search Document