A configurable Eleventy plugin that enables a powerful component system for building dynamic, reusable HTML components across your static site.
npm install eleventy-plugin-reusable-components

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
``bash`
npm install eleventy-plugin-reusable-components
Add the plugin to your Eleventy configuration file (.eleventy.js or eleventy.config.js).
View configuration code
`javascript
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
`liquid
---
title: Callout
{{ description }}
View Nunjucks component template
`njk
---
title: CalloutDefault values
heading: Lorem ipsum dolor sit
description: A callout component to highlight important information.
links:
- linkUrl: "#"
linkText: Learn more
background: light
---
{{ heading }}
{{ description }}
{% if links %}
{% endif %}
`
View WebC component template
`html
---
title: CalloutDefault values
heading: Lorem ipsum dolor sit
description: A callout component to highlight important information.
links:
- linkUrl: "#"
linkText: Learn more
background: light
---
`
View Vento component template
`vento
---
title: CalloutDefault values
heading: Lorem ipsum dolor sit
description: A callout component to highlight important information.
links:
- linkUrl: "#"
linkText: Learn more
background: light
---
{{ heading }}
{{ description }}
{{ if links }}
{{ /if }}
`$3
In any template, use the
renderComponent filter:
View Liquid usage example
`liquid
---
title: My PageCallout 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
`njk
---
title: My PageCallout 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
`html
---
title: My PageCallout 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
---
`
View Vento usage example
`vento
---
title: My PageCallout 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 }}
`$3
You can render multiple components by passing an array of component data:
View Liquid multiple components example
`liquid
---
title: My PageMixed 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
`njk
---
title: My PageMixed 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
`html
---
title: My PageMixed 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
---
`
View Vento multiple components example
`vento
---
title: My PageMixed 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
renderComponent filter 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.Configuration
$3
View default options
`javascript
const defaultOptions = {
componentsDir: "src/components/.",
collectionName: "components",
enableRenderPlugin: true,
excludeFromProduction: true
};
`Usage Examples
$3
Define components directly in your page's frontmatter:
View Liquid frontmatter example
`liquid
---
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
---
{{ components | renderComponent }}
`
View Nunjucks frontmatter example
`njk
---
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
---
{{ components | renderComponent | safe }}
`
View WebC frontmatter example
`html
---
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
---
`
View Vento frontmatter example
`vento
---
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
---
{{ components |> renderComponent |> safe }}
`$3
Define components inline within your template:
View Liquid inline definition example
`liquid
{% 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"
}
] %}
{{ heroComponent | renderComponent }}
{{ features | renderComponent }}
`
View Nunjucks inline definition example
`njk
{%- 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"
}
] -%}
{{ heroComponent | renderComponent | safe }}
{{ features | renderComponent | safe }}
`
View WebC inline definition example
`html
`
View Vento inline definition example
`vento
{{ 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"
}
] }}
{{ heroComponent |> renderComponent |> safe }}
{{ features |> renderComponent |> safe }}
`$3
Store component data in separate JSON files for better organization:
#### Data File:
src/_data/homepage.json
View data file example
`json
{
"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" }
]
}
]
}
`#### Template Usage
View Liquid template usage
`liquid
---
title: Homepage
---
{{ homepage.hero | renderComponent }}
{{ homepage.sections | renderComponent }}
`
View Nunjucks template usage
`njk
---
title: Homepage
---
{{ homepage.hero | renderComponent | safe }}
{{ homepage.sections | renderComponent | safe }}
`
View WebC template usage
`html
---
title: Homepage
---
`
View Vento template usage
`vento
---
title: Homepage
---
{{ homepage.hero |> renderComponent |> safe }}
{{ homepage.sections |> renderComponent |> safe }}
`Component Structure
Components should follow this structure:
View component structure example
`liquid
---
title: ComponentNameDefault values
heading: "default heading"
description: "default description"
---
{{ heading }}
{{ description }}
`$3
-
title: Used for component matching (gets slugified)$3
The plugin matches components by comparing:
- Component's
title (from frontmatter) โ slugified
- Content item's type property โ slugifiedExamples:
- Component:
title: "Text and Image" โ slug: "text-and-image"
- Content: type: "text-and-image" โ Match! โ
- Component: title: "Callout" โ slug: "callout"
- Content: type: "callout" โ Match! โ
$3
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:
`yaml
---
title: CalloutDefault values
heading: "Default Heading"
description: "Default description"
background: "light"
links:
- linkUrl: "#"
linkText: "Default Link"
---
`And you use it with partial data:
`liquid
{% 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.
Error Handling
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
Troubleshooting
$3
1. Check component title matches type:
View component title matching example
`liquid
title: "My Component"
type: "my-component"
`2. Verify component collection:
View component collection debug example
`liquid
{% for component in collections.components %}
{{ component.data.title }} โ {{ component.data.title | slugify }}
{% endfor %}
`3. Check file location:
- Default:
src/components/*.njk
- Custom: Set via componentsDir option$3
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:
- Vento: {{ item |> renderComponent |> safe }}If no template language is specified, the filter will use the calling template's language by default.
API Reference
$3
| 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 |$3
####
renderComponentMatches content items to component templates and renders them with automatic default value merging.
Parameters:
-
item (Object|Array): Content item(s) with type property
- templateLang (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 stringBehavior:
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.
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Add tests
5. Submit a pull request
MIT License - see LICENSE file for details.