The fastest way to get started is with our CLI scaffold:
npx create-html-elements
Choose between bare HTML & CSS or HTML with Tailwind (includes Prettier).
Keep your HTML DRY without framework overhead. Compose reusable components at build time with zero runtime JavaScript.
data-srcOr install the plugin manually:
npm install -D vite-plugin-html-elements
Add the plugin to your Vite config:
// vite.config.js
import { defineConfig } from 'vite';
import { htmlElements } from 'vite-plugin-html-elements';
export default defineConfig({
plugins: [htmlElements()]
});
For TypeScript projects:
// vite.config.ts
import { defineConfig } from 'vite';
import { htmlElements } from 'vite-plugin-html-elements';
export default defineConfig({
plugins: [htmlElements()]
});
Create reusable HTML elements in an
elements/
directory:
project/
├── public/
│ ├── favicon.png
│ └── logo.webp
├── src/
│ ├── elements/
│ │ ├── head.html
│ │ ├── header.html
│ │ └── footer.html
│ ├── index.html
│ ├── docs.html
│ ├── examples.html
│ └── styles.css
├── package.json
├── pnpm-lock.yaml
└── vite.config.js
Example element file:
<!-- elements/header.html -->
<header class="bg-primary-600 p-4">
<nav class="flex justify-between items-center">
<a href="/">Home</a>
<a href="/docs">Docs</a>
</nav>
</header>
Include elements in your HTML files using the
<element />
tag:
<!doctype html>
<html>
<body>
<element src="header.html" />
<main>
<h1>Your content</h1>
</main>
<element src="footer.html" />
</body>
</html>
Type:
boolean
Default:
false
Enable verbose logging to see which elements are being included during build.
htmlElements({ debug: true })
Slots let you inject dynamic content into reusable elements. Use
<slot />
as a placeholder:
<!-- elements/card.html -->
<div class="bg-white rounded-lg shadow-md p-6">
<slot />
</div>
<!-- Usage -->
<element src="card.html">
<h2>Card Title</h2>
<p>Card content goes here</p>
<button>Click me</button>
</element>
Pass string values to elements using props. Define placeholders with
{{propName}}:
<!-- elements/hero.html -->
<section class="{{className}}">
<h1>{{title}}</h1>
<p>{{subtitle}}</p>
<slot />
</section>
<!-- Usage -->
<element
src="hero.html"
title="Welcome to My Site"
subtitle="Build fast static sites"
className="bg-blue-500 text-white p-8"
>
<button>Get Started</button>
</element>
Render an element once per item in a local JSON array using
data-src.
Each object's keys are passed as props to the element template — no JavaScript required.
The file must be a top-level JSON array. Each object's keys map to
{{propName}}
placeholders in your element:
// src/data/products.json
[
{
"name": "Handmade Ceramic Vase",
"price": "$120",
"image": "/images/vase.jpg",
"paymentLink": "https://buy.stripe.com/xxx"
},
{
"name": "Original Oil Painting",
"price": "$450",
"image": "/images/painting.jpg",
"paymentLink": "https://buy.stripe.com/yyy"
}
]
<!-- elements/product-card.html -->
<div class="bg-secondary-800 rounded-lg p-6">
<img src="{{image}}" alt="{{name}}" class="rounded-lg mb-4 w-full" />
<h3 class="text-xl font-semibold text-white mb-2">{{name}}</h3>
<p class="text-primary-400 mb-4">{{price}}</p>
<a href="{{paymentLink}}">Buy Now</a>
</div>
<div class="grid gap-6 md:grid-cols-2">
<element src="product-card.html" data-src="./src/data/products.json" />
</div>
Props and slots work alongside data-src.
Static props are merged with each data item, with data values taking precedence on conflict.
Keep meta tags, stylesheets, and scripts consistent across all pages:
<!-- elements/head.html -->
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="/styles.css" />
<title>{{pageTitle}}</title>
</head>
<!-- Usage -->
<element src="head.html" pageTitle="Home | My Site" />
Reusable cards with props and slots for flexible content:
<!-- elements/feature-card.html -->
<div class="bg-secondary-800 rounded-lg p-6">
<div class="text-3xl mb-3">{{icon}}</div>
<h3 class="text-xl font-semibold mb-2">{{title}}</h3>
<slot />
</div>
<!-- Usage -->
<element src="feature-card.html" icon="⚡" title="Fast Builds">
<p>Lightning-fast hot reloading with Vite.</p>
</element>
Create consistent section layouts with customizable titles:
<!-- elements/section.html -->
<section class="py-16">
<div class="container mx-auto">
<h2 class="text-3xl font-bold mb-8">{{title}}</h2>
<slot />
</div>
</section>
<!-- Usage -->
<element src="section.html" title="Our Services">
<div class="grid grid-cols-3 gap-6">
<!-- Service cards here -->
</div>
</element>
Consistent button styles with customizable variants:
<!-- elements/button.html -->
<button class="px-6 py-3 rounded-lg {{variant}}">
<slot />
</button>
<!-- Usage -->
<element src="button.html" variant="bg-blue-500 text-white">
Get Started
</element>
<element src="button.html" variant="bg-gray-200 text-gray-800">
Learn More
</element>
Yes! It's fully compatible with other Vite plugins like Tailwind CSS.
Elements are just HTML. You can include
<script>
tags in your elements if needed.
This plugin is for static composition. For dynamic content, layer on JavaScript after your HTML is built.
Element paths are relative to
src/elements/
by default. You can organize with subdirectories:
<element src="components/nav.html" />
<element src="layouts/base.html" />
data-src with props and slots?Yes. Static props defined on the element are merged with each data item. Slots work as normal. Data item values take precedence over static props of the same name.
A top-level array of objects. Each object's keys map directly to
{{propName}}
placeholders in your element template.