Components

Generic Lume components

Components are template pieces that you can use in other templates. In JavaScript, TypeScript and JSX templates, create a component in its own file and import it wherever you need it.

Create a component

With the JSX plugin enabled, create a button component:

export default function Button({ children }) {
  return <button class="button">{children}</button>;
}

Import it in a page and pass its content as children:

import Button from "./_components/button.tsx";

export default function () {
  return <Button>Login</Button>;
}

A component can import other components in the same way. Pass the data each component needs as props, and include its styles and browser scripts in your site's CSS and JavaScript.

For components that return HTML strings instead of JSX, see the Modules plugin documentation.

Reloading changes

Since Lume 3.3.0 (requires Deno 2.9.0 or later), changes to local modules inside your src folder, including nested imports, are automatically reloaded in --serve or --watch mode. Older versions require restarting the server after changes to imported components.

Important

These components generate static HTML at build time; they don't run in the browser. For interactive client-side components, use a tool like the esbuild plugin to bundle your browser code.

Lume-managed components

Lume also provides a component system for automatic CSS and JavaScript collection, inherited directory data, and sharing components between template engines. It exposes components through the comp variable. Direct imports don't use this system: they don't collect component assets, inherit data or make children and content equivalent.

Some template engines like Vento, Nunjucks, Pug or Liquid already have ways to reuse code (like includes, macros, etc). Lume components have the following advantages:

  • They are template engine agnostic. For example, you can create your components in JSX or JavaScript and use them in Nunjucks.
  • They don't just output HTML, but also the CSS and JavaScript needed on the client side. And the CSS and JS code is output only if the component is used at least once.
  • They are automatically available everywhere; no need to import them manually.

Important

Lume components don't run in the browser. They are intended to generate static HTML code at build time.

For interactive client-side components (with onclick callbacks and similar stuff) you may want to use the esbuild plugin to compile your JavaScript code, but the architecture is up to you. Lume is not a frontend framework.

Create your own components

Components are stored in the _components directory. Like with _data, you can create _components directories in different sub-directories to make them available only to specific pages. To create a new component, just create a file in this directory with the name of your component and the extension of the template engine you want to use. For example, a component in Vento that renders a button could be saved as _components/button.vto:

<button class="button">{{ text }}</button>

This component is available in your layouts under the comp variable. This is a global variable that contains all components. In our example, we can render the button component with the comp.button() function:

<h1>Welcome to my site.</h1>
{{ comp.button({ text: "Login" }) }}

Components are case insensitive, so comp.button, comp.Button or comp.BuTtOn returns the same component:

{{ comp.Button({ text: "Login" }) }}

Components can be saved in subdirectories. For example, the button component could be saved in the ui subdirectory (_components/ui/button.vto). In this case, you can access this component with comp.ui.button().

Technically, a component is an async function that accepts an object with the properties and can be run in all template engines.

JS/TS templates:

Example of using the button component in a JavaScript page:

export default async function ({ comp }) {
  return `
  <h1>Welcome to my site.</h1>
  ${await comp.button({ text: "Login" })}
`;
}

More info at Modules plugin documentation

Vento templates

Vento automatically add the await keyword when printing a component.

<h1>Welcome to my site.</h1>
{{ comp.button({ text: "Login" }) }}

More info at Vento plugin documentation

Nunjucks templates:

<h1>Welcome to my site.</h1>
{{ comp.button({ text: "Login" }) | await | safe }}

More info at Nunjucks plugin documentation

Eta templates:

<h1>Welcome to my site.</h1>
<%~ await comp.button({ text: "Login" }) %>

JSX

Lume components can be used like JSX components if you're using the JSX plugin:

export default function ({ comp }) {
  return (
    <>
      <h1>Welcome to my site.</h1>
      <comp.Button text="Login" />
    </>
  );
}

Like any JSX component, you can pass children to the component.

export default function ({ comp }) {
  return <comp.title>This is a title</comp.title>;
}

The children content is passed as children and content properties (both are equivalent):

// _components/title.tsx

export default function title({ children }) {
  return <h1>{children}</h1>;
}

More info at JSX plugin documentation

Nested components

Because the comp variable is available inside the components, it's possible to nest components inside other components. In JSX this is easy:

export default function ({ comp }) {
  return (
    <comp.container>
      Content of the Container component
      <comp.button>This is a button inside a container</comp.button>
    </comp.container>
  );
}

Vento

In other template engines like Vento, due the components are functions, it's possible to pass the content in this way:

{{ await comp.container({
  content: `
    Content of the Container component
    ${await comp.button({ content: "This is a button inside a container" })}
    `
}) }}

To make this easier, Vento has the comp special tag that allows to consume components similarly to JSX:

{{ comp container }}
  Content of the Container component

  {{ comp button }}
    This is a button inside the Container component
  {{ /comp }}
{{ /comp }}

Nunjucks

Nunjucks has a very similar tag:

{% comp "Container" %}
  Content of the Container component

  {% comp "Button" %}
    This is a button inside the Container component
  {% endcomp %}
{% endcomp %}

Component assets

Components can export CSS and JS code. To do this, the component needs to export css or js variables.

In our example, we may want to apply some styles to the button. In a Vento template, the way to export data is using the front matter:

---
css: |
  .button {
    background-color: blue;
    color: white;
  }
---
<button class="button">{{ text }}</button>

This CSS code will be exported to your dest folder in the /style.css file (or other configured file) together with the CSS code of other used components. Note that if the component is not used, the CSS code won't be exported. This is a useful feature that allows having a library of many components and only exporting the CSS and JS code that you only need.

Folder components

To make easier to create components with CSS and JS, it's possible to create a component in a folder, with the CSS and JS code in different files. To do that, you have to use the following structure:

_components
  └── button
      ├── comp.vto
      ├── style.css
      └── script.js

Any folder containing a comp.* file will be loaded as a component using the folder name as the component name, and the style.css and script.js files will be loaded as the CSS and JavaScript code for the component. This makes the creation of components more ergonomic, especially for cases with a lot of CSS and JS code.

Additionally, it's possible to add a script.ts file instead of script.js to use TypeScript. Lume will compile it to JavaScript automatically.

Register components from the _config file

In addition to the _components folder, you can register components dynamically in the _config file with the function site.component(), which is useful for plugins. This function takes two arguments: the component context and the component object:

site.component("ui", {
  name: "button",
  css: ".btn { background: blue; color: white }",
  render({ text }) {
    return `<button class="btn">${text}</button>`;
  },
});

Now, you can use the component like this:

{{ comp.ui.button({ text: "Login" }) }}