A configurable Eleventy plugin that enables a powerful component system for building dynamic, reusable HTML components across your static site.
👉 Build your components once and use them anywhere.
- 🧩 Dynamic Component Rendering - Render components based on content data
- 🎨 Template Language Agnostic - Works with Nunjucks, Liquid, WebC, Vento, and more
- 🏗️ Flexible Configuration - Customizable directories and options
- 🚀 Production Ready - Excludes development components from production builds
- 🔧 Developer Friendly - Comprehensive error handling and debugging
npm install eleventy-plugin-reusable-componentsAdd the plugin to your Eleventy configuration file (.eleventy.js or eleventy.config.js).
View configuration code
import reusableComponents from "eleventy-plugin-reusable-components";
export default function(eleventyConfig) {
eleventyConfig.addPlugin(reusableComponents);
}Create a component file at src/components/callout.liquid:
View Liquid component template
---
title: Callout
# Default values
heading: Lorem ipsum dolor sit
description: A callout component to highlight important information.
links:
- linkUrl: "#"
linkText: Learn more
background: light
---
<div class="callout callout--{{ background }}">
<h3 class="callout__heading">{{ heading }}</h3>
<p class="callout__description">{{ description }}</p>
{% if links %}
<div class="callout__links">
{% for link in links %}
<a href="{{ link.linkUrl }}" class="callout__link">{{ link.linkText }}</a>
{% endfor %}
</div>
{% endif %}
</div>View Nunjucks component template
---
title: Callout
# Default values
heading: Lorem ipsum dolor sit
description: A callout component to highlight important information.
links:
- linkUrl: "#"
linkText: Learn more
background: light
---
<div class="callout callout--{{ background }}">
<h3 class="callout__heading">{{ heading }}</h3>
<p class="callout__description">{{ description }}</p>
{% if links %}
<div class="callout__links">
{% for link in links %}
<a href="{{ link.linkUrl }}" class="callout__link">{{ link.linkText }}</a>
{% endfor %}
</div>
{% endif %}
</div>View WebC component template
---
title: Callout
# Default values
heading: Lorem ipsum dolor sit
description: A callout component to highlight important information.
links:
- linkUrl: "#"
linkText: Learn more
background: light
---
<div :class="`callout callout--${background}`">
<h3 class="callout__heading" @text="heading"></h3>
<p class="callout__description" @text="description"></p>
<div class="callout__links" webc:if="links">
<a
:href="link.linkUrl"
class="callout__link"
@text="link.linkText"
webc:for="link of links">
</a>
</div>
</div>View Vento component template
---
title: Callout
# Default values
heading: Lorem ipsum dolor sit
description: A callout component to highlight important information.
links:
- linkUrl: "#"
linkText: Learn more
background: light
---
<div class="callout callout--{{ background }}">
<h3 class="callout__heading">{{ heading }}</h3>
<p class="callout__description">{{ description }}</p>
{{ if links }}
<div class="callout__links">
{{ for link of links }}
<a href="{{ link.linkUrl }}" class="callout__link">{{ link.linkText }}</a>
{{ /for }}
</div>
{{ /if }}
</div>In any template, use the renderComponent filter:
View Liquid usage example
{{-
{
"type": "callout",
"heading": "Help organize the 11ty Meetup!",
"description": "A callout component to highlight important information.",
"links": [
{
"linkUrl": "https://11tymeetup.dev/",
"linkText": "Join the 11ty Meetup!"
}
],
"background": "warning"
} | renderComponent
-}}Or from frontmatter:
---
title: My Page
# Callout component data
callout:
type: callout
heading: Help organize the 11ty Meetup!
description: A callout component to highlight important information.
links:
- linkUrl: https://11tymeetup.dev/
linkText: Join the 11ty Meetup!
background: warning
---
{{ callout | renderComponent }}View Nunjucks usage example
{{-
{
"type": "callout",
"heading": "Help organize the 11ty Meetup!",
"description": "A callout component to highlight important information.",
"links": [
{
"linkUrl": "https://11tymeetup.dev/",
"linkText": "Join the 11ty Meetup!"
}
],
"background": "warning"
} | renderComponent | safe
-}}Or from frontmatter:
---
title: My Page
# Callout component data
callout:
type: callout
heading: Help organize the 11ty Meetup!
description: A callout component to highlight important information.
links:
- linkUrl: https://11tymeetup.dev/
linkText: Join the 11ty Meetup!
background: warning
---
{{ callout | renderComponent | safe }}View WebC usage example
<template @html="{
'type': 'callout',
'heading': 'Help organize the 11ty Meetup!',
'description': 'A callout component to highlight important information.',
'links': [
{
'linkUrl': 'https://11tymeetup.dev/',
'linkText': 'Join the 11ty Meetup!'
}
],
'background': 'warning'
} | renderComponent"></template>Or from frontmatter:
---
title: My Page
# Callout component data
callout:
type: callout
heading: Help organize the 11ty Meetup!
description: A callout component to highlight important information.
links:
- linkUrl: https://11tymeetup.dev/
linkText: Join the 11ty Meetup!
background: warning
---
<template @html="callout | renderComponent"></template>View Vento usage example
{{-
{
"type": "callout",
"heading": "Help organize the 11ty Meetup!",
"description": "A callout component to highlight important information.",
"links": [
{
"linkUrl": "https://11tymeetup.dev/",
"linkText": "Join the 11ty Meetup!"
}
],
"background": "warning"
} |> renderComponent |> safe
-}}Or from frontmatter:
---
title: My Page
# Callout component data
callout:
type: callout
heading: Help organize the 11ty Meetup!
description: A callout component to highlight important information.
links:
- linkUrl: https://11tymeetup.dev/
linkText: Join the 11ty Meetup!
background: warning
---
{{ callout |> renderComponent |> safe }}You can render multiple components by passing an array of component data:
View Liquid multiple components example
---
title: My Page
# Mixed component data
components:
- type: callout
heading: Help organize the 11ty Meetup!
description: A callout component to highlight important information.
links:
- linkUrl: https://11tymeetup.dev/
linkText: Join the 11ty Meetup!
background: warning
- type: text-and-image
heading: About Our Community
description: Join thousands of developers building amazing things with Eleventy.
image: /assets/images/community.jpg
imageAlt: Community members collaborating
layout: image-right
---
{{ components | renderComponent }}View Nunjucks multiple components example
---
title: My Page
# Mixed component data
components:
- type: callout
heading: Help organize the 11ty Meetup!
description: A callout component to highlight important information.
links:
- linkUrl: https://11tymeetup.dev/
linkText: Join the 11ty Meetup!
background: warning
- type: text-and-image
heading: About Our Community
description: Join thousands of developers building amazing things with Eleventy.
image: /assets/images/community.jpg
imageAlt: Community members collaborating
layout: image-right
---
{{ components | renderComponent | safe }}View WebC multiple components example
---
title: My Page
# Mixed component data
components:
- type: callout
heading: Help organize the 11ty Meetup!
description: A callout component to highlight important information.
links:
- linkUrl: https://11tymeetup.dev/
linkText: Join the 11ty Meetup!
background: warning
- type: text-and-image
heading: About Our Community
description: Join thousands of developers building amazing things with Eleventy.
image: /assets/images/community.jpg
imageAlt: Community members collaborating
layout: image-right
---
<template @html="components | renderComponent"></template>View Vento multiple components example
---
title: My Page
# Mixed component data
components:
- type: callout
heading: Help organize the 11ty Meetup!
description: A callout component to highlight important information.
links:
- linkUrl: https://11tymeetup.dev/
linkText: Join the 11ty Meetup!
background: warning
- type: text-and-image
heading: About Our Community
description: Join thousands of developers building amazing things with Eleventy.
image: /assets/images/community.jpg
imageAlt: Community members collaborating
layout: image-right
---
{{ components |> renderComponent |> safe }}Note: The
renderComponentfilter accepts a template language parameter ("njk","liquid","webc","vto", etc.) and can process both single components and arrays of components. The filter automatically merges component default values with your provided data - any missing fields will use the defaults from the component file.
View default options
const defaultOptions = {
componentsDir: "src/components/*.*",
collectionName: "components",
enableRenderPlugin: true,
excludeFromProduction: true
};Define components directly in your page's frontmatter:
View Liquid frontmatter example
---
title: My Page
components:
- type: callout
heading: Welcome!
description: Thanks for visiting our site
background: primary
- type: text-and-image
heading: About Us
description: Learn more about our company
image: /assets/images/about.jpg
imageAlt: About our company
---
<main>
{{ components | renderComponent }}
</main>View Nunjucks frontmatter example
---
title: My Page
components:
- type: callout
heading: Welcome!
description: Thanks for visiting our site
background: primary
- type: text-and-image
heading: About Us
description: Learn more about our company
image: /assets/images/about.jpg
imageAlt: About our company
---
<main>
{{ components | renderComponent | safe }}
</main>View WebC frontmatter example
---
title: My Page
components:
- type: callout
heading: Welcome!
description: Thanks for visiting our site
background: primary
- type: text-and-image
heading: About Us
description: Learn more about our company
image: /assets/images/about.jpg
imageAlt: About our company
---
<main>
<template @html="components | renderComponent"></template>
</main>View Vento frontmatter example
---
title: My Page
components:
- type: callout
heading: Welcome!
description: Thanks for visiting our site
background: primary
- type: text-and-image
heading: About Us
description: Learn more about our company
image: /assets/images/about.jpg
imageAlt: About our company
---
<main>
{{ components |> renderComponent |> safe }}
</main>Define components inline within your template:
View Liquid inline definition example
{% assign heroComponent = {
type: "hero",
heading: "Welcome to Our Site",
description: "Thanks for visiting! We're excited to share our content with you.",
background: "primary"
} %}
{% assign features = [
{
type: "callout",
heading: "Fast Performance",
description: "Built for speed and efficiency.",
background: "success"
},
{
type: "callout",
heading: "Easy to Use",
description: "Simple and intuitive interface.",
background: "info"
}
] %}
<main>
{{ heroComponent | renderComponent }}
<section class="features">
{{ features | renderComponent }}
</section>
</main>View Nunjucks inline definition example
{%- set heroComponent = {
type: "hero",
heading: "Welcome to Our Site",
description: "Thanks for visiting! We're excited to share our content with you.",
background: "primary"
} -%}
{%- set features = [
{
type: "callout",
heading: "Fast Performance",
description: "Built for speed and efficiency.",
background: "success"
},
{
type: "callout",
heading: "Easy to Use",
description: "Simple and intuitive interface.",
background: "info"
}
] -%}
<main>
{{ heroComponent | renderComponent | safe }}
<section class="features">
{{ features | renderComponent | safe }}
</section>
</main>View WebC inline definition example
<script webc:setup>
const heroComponent = {
type: "hero",
heading: "Welcome to Our Site",
description: "Thanks for visiting! We're excited to share our content with you.",
background: "primary"
};
const features = [
{
type: "callout",
heading: "Fast Performance",
description: "Built for speed and efficiency.",
background: "success"
},
{
type: "callout",
heading: "Easy to Use",
description: "Simple and intuitive interface.",
background: "info"
}
];
</script>
<main>
<template @html="heroComponent | renderComponent"></template>
<section class="features">
<template @html="features | renderComponent"></template>
</section>
</main>View Vento inline definition example
{{ set heroComponent = {
type: "hero",
heading: "Welcome to Our Site",
description: "Thanks for visiting! We're excited to share our content with you.",
background: "primary"
} }}
{{ set features = [
{
type: "callout",
heading: "Fast Performance",
description: "Built for speed and efficiency.",
background: "success"
},
{
type: "callout",
heading: "Easy to Use",
description: "Simple and intuitive interface.",
background: "info"
}
] }}
<main>
{{ heroComponent |> renderComponent |> safe }}
<section class="features">
{{ features |> renderComponent |> safe }}
</section>
</main>Store component data in separate JSON files for better organization:
View data file example
{
"hero": {
"type": "hero",
"heading": "Welcome to Our Site",
"subheading": "Building amazing experiences",
"image": "/assets/images/hero-bg.jpg",
"ctaText": "Learn More",
"ctaUrl": "/about/"
},
"sections": [
{
"type": "text-and-image",
"heading": "Our Mission",
"description": "We strive to create exceptional digital experiences that make a difference.",
"image": "/assets/images/mission.jpg",
"imageAlt": "Our mission in action",
"layout": "image-right"
},
{
"type": "callout",
"heading": "Ready to Get Started?",
"description": "Join thousands of satisfied customers today.",
"background": "primary",
"links": [
{
"linkText": "Sign Up Now",
"linkUrl": "/signup/"
},
{
"linkText": "Learn More",
"linkUrl": "/features/"
}
]
},
{
"type": "stats-grid",
"stats": [
{ "number": "10k+", "label": "Happy Customers" },
{ "number": "99.9%", "label": "Uptime" },
{ "number": "24/7", "label": "Support" },
{ "number": "50+", "label": "Countries" }
]
}
]
}View Liquid template usage
---
title: Homepage
---
<main>
{{ homepage.hero | renderComponent }}
{{ homepage.sections | renderComponent }}
</main>View Nunjucks template usage
---
title: Homepage
---
<main>
{{ homepage.hero | renderComponent | safe }}
{{ homepage.sections | renderComponent | safe }}
</main>View WebC template usage
---
title: Homepage
---
<main>
<template @html="homepage.hero | renderComponent"></template>
<template @html="homepage.sections | renderComponent"></template>
</main>View Vento template usage
---
title: Homepage
---
<main>
{{ homepage.hero |> renderComponent |> safe }}
{{ homepage.sections |> renderComponent |> safe }}
</main>Components should follow this structure:
View component structure example
---
title: ComponentName
# Default values
heading: "default heading"
description: "default description"
---
<!-- Component template here -->
<div class="component-name">
<h2>{{ heading }}</h2>
<p>{{ description }}</p>
</div>title: Used for component matching (gets slugified)
The plugin matches components by comparing:
- Component's
title(from frontmatter) → slugified - Content item's
typeproperty → slugified
Examples:
- Component:
title: "Text and Image"→ slug:"text-and-image" - Content:
type: "text-and-image"→ Match! ✅ - Component:
title: "Callout"→ slug:"callout" - Content:
type: "callout"→ Match! ✅
Components automatically merge their default values with the data you provide. This means you only need to specify the fields you want to override - any missing fields will use the defaults from the component file.
Example:
If your component has these defaults:
---
title: Callout
# Default values
heading: "Default Heading"
description: "Default description"
background: "light"
links:
- linkUrl: "#"
linkText: "Default Link"
---And you use it with partial data:
{% assign myCallout = {
type: "callout",
heading: "Custom Heading"
} %}
{{ myCallout | renderComponent: "liquid" }}The component will render with:
heading: "Custom Heading" (from your data)description: "Default description" (from component default)background: "light" (from component default)links: Default links array (from component default)
This ensures components always have complete data to work with, even when you only provide a subset of the required fields.
The plugin handles errors gracefully:
- Missing component: Returns empty string
- Invalid data: Returns empty string
- Missing collections: Returns empty string
- Template errors: Logged to console, returns fallback
- Check component title matches type:
View component title matching example
<!-- Component file -->
title: "My Component" <!-- becomes "my-component" -->
<!-- Usage -->
type: "my-component" <!-- must match! -->- Verify component collection:
View component collection debug example
<!-- Debug: List all components -->
{% for component in collections.components %}
<p>{{ component.data.title }} → {{ component.data.title | slugify }}</p>
{% endfor %}- Check file location:
- Default:
src/components/*.njk - Custom: Set via
componentsDiroption
- Default:
Make sure to specify the correct template language parameter for your template engine:
- Liquid:
{{ item | renderComponent }}(auto-escaped by default) - Nunjucks:
{{ item | renderComponent | safe }} - WebC:
<template @html="item | renderComponent"></template> - Vento:
{{ item |> renderComponent |> safe }}
If no template language is specified, the filter will use the calling template's language by default.
| Option | Type | Default | Description |
|---|---|---|---|
componentsDir |
string |
"src/components/*.*" |
Glob pattern for component files |
collectionName |
string |
"components" |
Name of components collection |
enableRenderPlugin |
boolean |
true |
Enable Eleventy Render Plugin |
excludeFromProduction |
boolean |
true |
Exclude components from production |
Matches content items to component templates and renders them with automatic default value merging.
Parameters:
item(Object|Array): Content item(s) withtypepropertytemplateLang(string): Optional. Template language ("njk", "liquid", "webc", "vto", etc.). If not specified, defaults to the calling template's language.
Returns:
string: Fully rendered HTML content or empty string
Behavior:
The filter automatically merges component default values with your provided data. Any fields not specified in your data will use the default values from the component's frontmatter. This ensures components always have complete data to render properly.
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests
- Submit a pull request
MIT License - see LICENSE file for details.