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.

$ npm install tagshift
Try the playground Source
  • ~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 props and slots. Nothing is rendered yet.

component.props

Values for ${name} placeholders, converted with String() and inserted as text. Missing, undefined and null render as an empty string.

component.slots

Content for each slot by name — default for the unnamed one. A Node, a string, or any iterable of them; nodes are moved, not copied.

  • undefined or delete brings 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 props and slots. The first call builds it; later calls update the same element in place.

component.element

The rendered root element as of the last render(), without updating it; null before the first render().

escape(value)

Replaces &, <, >, " and ' with HTML entities, for markup you build yourself. Non-strings are converted with String(); undefined and null become an empty string.

html`…`

A template tag that parses its markup into a DocumentFragment, escaping every interpolated value with escape(). Pass the result as slot content.

TemplateError

Thrown 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.