VanillaTemplates
A tiny, HTML-first template engine for Vanilla JavaScript. Uses only valid HTML (<var> and data-* attributes) for data binding, loops, conditionals, includes, styles and events. Single-pass rendering, no virtual DOM, no bundler step, usable in the browser and for static site generation.
Install / Use
/learn @Tehes/VanillaTemplatesREADME
Vanilla JS Template Engine
A lightweight and simple JavaScript template engine that uses valid HTML syntax
and <var> elements for data binding, offering built-in directives for:
- Loops with
data-loop - Conditional rendering with
data-if - Attribute binding with
data-attr - Style binding with
data-style - Event binding with
data-event - Includes via
data-include
It provides a minimalistic solution to dynamically populate HTML templates with JavaScript data, without needing any specialized template language.
Features
- Valid HTML Syntax: The engine uses standard HTML elements and attributes, with no need for a custom template language or syntax.
- Data Binding with
<var>: Inject JavaScript data into HTML using<var>as placeholders. - Loop Support: Iterate over arrays and object maps with
data-loop, creating dynamic lists and exposing context helpers. - Conditional Rendering with
data-if: Show or hide elements based on boolean expressions, including negation for inverse logic. - Attribute Binding with
data-attr: Dynamically set any HTML attribute (e.g.,src,href,alt) from data. - Style Binding with
data-style: Dynamically set CSS style properties on elements. - Event Binding with
data-event: Declaratively bind DOM event listeners via an expliciteventsmap. - Includes with
data-include: Load and render external HTML partials via<var data-include>. - Per-Render Include De-Duplication: Within one
renderTemplate(...)call, repeated includes with the samesrcare fetched only once. - Nested Loops & Context Helpers: Support nested loops with helper variables
_index,_first,_last,_key, and_valuefor advanced templating scenarios. - Single DOM Walk: Perform a single recursive traversal for efficient performance.
- Zero DOM Footprint: Strip out all
<var>placeholders and directive wrappers after rendering, leaving only final HTML. - XSS Safety & Error Handling: Inject content via
textContentto prevent XSS and throw descriptive errors for invalid bindings.
Why <var>?
HTML already defines the
<var> element
for “variables” in a broad sense.
In addition to serving as placeholders, <var> elements are also used as
directive wrappers for grouping and applying loop (data-loop), conditional
(data-if), and include (data-include) logic to multiple nodes. Using it as a
placeholder tag brings three advantages:
- Valid Mark‑up – Your template stays 100 % HTML; no proprietary braces
like
{{name}}. - Editor Support – Because it’s a real element, you get syntax highlighting, auto‑closing tags and can nest it anywhere.
- Clean Output – During rendering, every
<var>(and any<var data-loop>container) is replaced with its resolved content and then removed withreplaceWith. The browser never sees the placeholder after hydration.
Tip: Two special cases use empty <var> tags:
-
Loop container
A<var>that carriesdata-loop="items"but no inner text works as the wrapper that gets duplicated. After rendering, the wrapper vanishes – your DOM stays clean. -
Primitive array item
Inside a loop over an array of primitive values (strings, numbers …), leave the inner<var>empty. The engine will substitute it with the current item.<var data-loop="user.hobbies"> <li><var></var></li> </var> <!-- produces <li>Reading</li> … -->
Basic Usage
HTML Template Example
<h2>Hello <var>name</var>!</h2>
<p>You got <var>todos.length</var> To-do(s).</p>
JSON Example
{
"name": "John Doe",
"todos": [
"read documentation",
"drink green tea"
]
}
This will produce:
<h2>Hello John Doe!</h2>
<p>You got 2 To-do(s).</p>
Includes
<!-- Include a user profile partial -->
<var data-include="profile.html"></var>
<!-- Loop over a list of items -->
<ul>
<var data-loop="items">
<li><var></var></li>
</var>
</ul>
API
/**
* Renders a <template> element into the DOM.
*
* @param {HTMLTemplateElement} template - the <template> element to render.
* @param {object} data - the data object for binding.
* @param {HTMLElement} target - the container to append rendered content.
* @param {object} [options]
* @param {boolean} [options.replace=false] - if true, clears target before rendering.
* @param {Record<string, Function>} [options.events] - explicit event handlers for data-event.
*/
renderTemplate(template, data, target, { replace = false, events } = {});
// Example usage:
// Append mode:
renderTemplate(tpl, data, target);
// Replace mode:
renderTemplate(tpl, data, target, { replace: true });
// Declarative event binding:
renderTemplate(tpl, data, target, { events: { onSave, onHover, onLeave } });
Data Binding
The engine uses <var> elements to bind data from JavaScript objects to HTML
elements. The text inside the <var> tag corresponds to the key or nested key
of the object.
Example
<var>user.name</var>
<!-- This will be replaced by the value of user.name from the data object -->
Looping Over Data
To handle arrays, the engine provides a data-loop attribute. When applied to
an element, it will iterate over the corresponding array in the data and
duplicate the element for each item.
HTML Loop Example
<ul>
<var data-loop="todos">
<li><var></var></li>
</var>
</ul>
<!-- Repeats for each hobby in the user.hobbies array -->
JSON Example
{
"todos": [
"Read documentation",
"Make green tea",
"Write code",
"Deploy app"
]
}
This will produce:
<ul>
<li>Read documentation</li>
<li>Make green tea</li>
<li>Write code</li>
<li>Deploy app</li>
</ul>
Looping over Object Properties
data-loop also accepts a plain object.
Inside such a loop you get five helper keys:
| Helper | Meaning |
| --------------------------------- | ------------------------------ |
| _key | current property name |
| _value (or empty <var></var>) | current value (for primitives) |
| _index | zero-based counter |
| _first | true for the first entry |
| _last | true for the last entry |
<dl class="settings-list">
<var data-loop="settings">
<dt>
<strong>#<var>_index</var></strong>
— <var>_key</var>
<span data-if="_first">(first)</span>
<span data-if="_last">(last)</span>:
</dt>
<dd><var></var></dd>
</var>
</dl>
{
"settings": {
"theme": "dark",
"language": "de",
"itemsPerPage": 20,
"autoSave": true,
"welcomeMessage": "Hello, Tino!",
"maxUploadSizeMB": 50
}
}
renders as:
<dl class="settings-list">
<dt>
<strong>#0</strong>
— theme <span>(first)</span>:
</dt>
<dd>dark</dd>
<dt>
<strong>#1</strong>
— language:
</dt>
<dd>de</dd>
<dt>
<strong>#2</strong>
— itemsPerPage:
</dt>
<dd>20</dd>
<dt>
<strong>#3</strong>
— autoSave:
</dt>
<dd>true</dd>
<dt>
<strong>#4</strong>
— welcomeMessage:
</dt>
<dd>Hello, Tino!</dd>
<dt>
<strong>#5</strong>
— maxUploadSizeMB <span>(last)</span>:
</dt>
<dd>50</dd>
</dl>
Advanced Example
This example focuses on how the object-map loop exposes _key and _value (an
array) so you can nest a second loop over each section’s items.
{
"sections": {
"Work Experience": [
{
"title": "Senior Software Engineer",
"company": "Acme Tech Co.",
"period": "Jan 2021 – Present"
},
{
"title": "Full-Stack Developer",
"company": "Bright Solutions LLC",
"period": "Jun 2018 – Dec 2020"
}
],
"Education": [
{
"title": "M.Sc. Computer Science, University of Westshire",
"period": "Sep 2016 – May 2018"
},
{
"title": "B.Sc. Information Technology, Eastfield College",
"period": "Sep 2012 – May 2016"
}
]
}
}
<!-- Outer loop: iterate object map of sections -->
<var data-loop="sections">
<section>
<!-- Section title from the key -->
<h3><var>_key</var></h3>
<!-- Inner loop: iterate the array in _value -->
<ul>
<var data-loop="_value">
<li>
<strong><var>title</var></strong>
<var data-if="company"> @ <var>company</var></var>
<br>
<small><var>period</var></small>
</li>
</var>
</ul>
</section>
</var>
renders as:
<section>
<h3>Work Experience</h3>
<ul>
<li>
<strong>Senior Software Engineer</strong> @ Acme Tech Co.<br>
<small>Jan 2021 – Present</small>
</li>
<li>
<strong>Full-Stack Developer</strong> @ Bright Solutions LLC<br>
<small>Jun 2018 – Dec 2020</small>
</li>
</ul>
</section>
<section>
<h3>Education</h3>
<ul>
<li>
<strong>M.Sc. Computer Science, University of Westshire</strong>
<br>
<small>Sep 2016 – May 2018</small>
</li>
<li>
<strong>B.Sc. Information Technology, Eastfield
