MIT · ES module · zero dependencies
Templates in. Elements out.
Turn a <template> into a component class. Fill its slots, interpolate ${props}, and update the rendered element in place — with one tiny module and no build step.
- ~1.3 KBminified + gzipped
- 0dependencies
- 1function to learn
Three steps, no magic.
Write plain HTML, turn it into a class, render. Change props or slots and call render() again — the same element is updated.
01 Write a template
<template id="stat-tile">
<div class="tile tile--${trend}">
<span class="label">${label}</span>
<strong>${value}</strong>
<slot name="footer"></slot>
</div>
</template>
02 Define a class
import { define } from 'tagshift';
const StatTile = define(
document.getElementById('stat-tile'),
);
03 Render and update
const cpu = new StatTile({
props: { label: 'CPU', value: '12%' },
});
grid.append(cpu.render());
cpu.props.value = '48%';
cpu.render(); // same element
Live: every second
for (const metric of metrics) {
Object.assign(metric.tile.props, {
value: metric.value,
delta: metric.delta,
trend: metric.delta >= 0 ? 'up' : 'down',
scale: metric.value / metric.max,
});
metric.tile.render();
}
Result
Outlines flash on the nodes render() just wrote to.
Small on purpose.
Everything a template needs to become a component, and nothing that needs a compiler.
-
Zero dependencies
One ES module. Import it from npm or straight from a CDN — no bundler, no runtime.
-
Updates in place
render()writes only the text and attributes whose result changed. Nothing is re-created. -
Safe by default
Props are inserted as text, never parsed as HTML, so a stray
<script>stays a string. -
Familiar slots
Default and named
<slot>s with fallback content, following the rules of Shadow DOM. -
Just HTML
Templates are plain markup.
${name}works in text and in attribute values. -
Typed
Hand-written TypeScript declarations ship with the package, with no TypeScript in the source.
Playground.
Edit the template, props or slots. The output below is rendered by tagshift itself, and every edit updates the same element.
Preview
Rendered HTML
API.
The whole of it.
define(template)-
Returns a component class for a
<template>. The template is copied, so later changes to it have no effect.It must contain exactly one root element, which is not a
<slot>; whitespace and comments around it are ignored. new Component({ props, slots })Creates an instance holding copies of
propsandslots. Nothing is rendered yet.component.props-
Values for
${name}placeholders, converted withString()and inserted as text. Missing,undefinedandnullrender as an empty string. component.slots-
Content for each slot by name —
defaultfor the unnamed one. ANode, a string, or any iterable of them; nodes are moved, not copied.undefinedordeletebrings back the slot's fallback;[]empties it.- A slot is refilled only when its value is no longer the same (
Object.is), so assign a new array instead of changing one in place.
component.render()Returns the rendered root element, updated with the current
propsandslots. The first call builds it; later calls update the same element in place.component.elementThe rendered root element as of the last
render(), without updating it;nullbefore the firstrender().escape(value)Replaces
&,<,>,"and'with HTML entities, for markup you build yourself. Non-strings are converted withString();undefinedandnullbecome an empty string.html`…`A template tag that parses its markup into a
DocumentFragment, escaping every interpolated value withescape(). Pass the result as slot content.TemplateErrorThrown for a template without exactly one root element, content for a slot the template does not declare, or a slot name declared twice. An update that throws leaves the element as it was.