Components
Generic Lume components
Components are template pieces that you can use in other templates. 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 can content the CSS and JavaScript needed on the client side. And the CSS and JS code is output only if the component is used.
- They are automatically available everywhere; no need to import them manually.
- For ESM module-based components (like JavaScript, TypeScript, JSX or TSX) it's the only way to hot-reload components without stopping and restarting the local server.
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 stored in _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>
{{ await comp.button({ text: "Login" }) }}
Components are case insensitive, so comp.button
, comp.Button
or comp.BuTtOn
returns the same component:
{{ await 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()
.
The component is an async function that accepts an object with the properties. This component is available 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
<h1>Welcome to my site.</h1>
{{ await 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
You can 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" })}
`
}) }}
But this way to consume components is not very practical. 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 %}
Components inside components
Components receive automatically the comp
property. This allows to invoke components inside other components. Let's say we want to create the search
component that uses button
internally. This is an example using a JSX template:
// _components/search.jsx
export default async function ({ comp }) {
return (
<form class="search">
<label>
Search:
<input type="search" name="q">
</label>
<comp.Button>Submit</comp.Button>
</form>
);
}
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" }) }}