API Reference

HTMP is designed to be intuitive. If you know HTML and JavaScript, you already know how to use it. Below is the complete API reference for v1.2.2.

The 3 Pillars of HTMP

HTMP is built on a simple, declarative philosophy that separates UI structure from data and logic:

Creating the Pattern

The templateString passed to HTMP can be created in three ways, depending on your project structure and developer experience (DX) preferences:

Variation 1: String Literal (Best for JS Modules)

Define your HTML directly as a JavaScript string. This is useful when separating UI logic into .js files.

Con: IDEs (like VS Code) will not provide syntax highlighting or HTML validity checking inside standard strings.

const pattern = `<div id="app"><h1>{{ title }}</h1></div>`;
const app = new HTMP('app', pattern);

Variation 2: Native <template> Tag (Best for Full-Page DX)

Write HTML directly in your index.html inside a <template> tag. This is the recommended approach for Landing Pages or static HTML files.

Pro: IDEs provide full syntax highlighting, autocomplete, and HTML validity checking. It also prevents FOUC (Flash of Unstyled Content) since template contents are not rendered until HTMP mounts them.

<!-- In index.html -->
<template id="page-template">
  <div id="app">
    <h1>{{ title }}</h1>
  </div>
</template>
// In app.js
const pattern = document.getElementById('page-template').innerHTML;
const app = new HTMP('app', pattern);

Variation 3: Silent Takeover (Best for SSR / Static HTML)

Take over existing HTML already rendered by the server (PHP, Laravel, etc.). You must use .replace('id="rootId"', '') to remove the original ID from the string, preventing duplicate IDs in the DOM when HTMP clones and mounts it.

// HTML is already in the body, rendered by backend
// <div id="app"><h1>{{ title }}</h1></div>

const rootEl = document.getElementById('app');
// Clone outerHTML and strip the ID to avoid duplication
const pattern = rootEl.outerHTML.replace('id="app"', '');

const app = new HTMP('app', pattern);
app.mount('app'); // Mounts and replaces the original SSR element

Installation & Setup

HTMP requires zero build steps. The recommended way to use it in production is via the jsDelivr CDN (ESM). This also allows you to monitor download statistics for the library.

CDN (Recommended)

<script type="module" src="https://cdn.jsdelivr.net/npm/htm-projection@v1.1.0/+esm"></script>

Or import it directly inside your JS module:

import { HTMP } from 'https://cdn.jsdelivr.net/npm/htm-projection@v1.1.0/+esm';

NPM / Yarn

For TypeScript or bundled projects (Vite/Webpack), you can install it via NPM:

npm install htm-projection

Initialization

Creates a new HTMP instance, parses the HTML template, and builds the internal Registry Map.

new HTMP(rootId, templateString)

const app = new HTMP<T>(rootId: string, templateString: string)

TypeScript Support: You can pass a TypeScript interface to the class for full Type-Safety.

import { HTMP } from 'htm-projection';

interface AppState {
  title: string;
  count: number;
}

// Initialize with Type
const app = new HTMP<AppState>('app', templateString);

State Management & Scope

HTMP uses a direct state manipulation pattern. State changes are intercepted by a root Proxy, which automatically triggers the render pipeline.

setProxy(stateObject)

Registers initial state values. Accepts an Object Literal.

app.setProxy({
  title: "Todo App",
  todos: []
});

Direct State Manipulation (Global Scope)

The app.proxy object is a live reference. Because it is attached to the app instance, it can be accessed and mutated from anywhere within your JavaScript module, not just inside setProgram.

// Updating a primitive from anywhere in your JS file
app.proxy.title = "New Title";

// Updating an array immutably
app.proxy.todos = [...app.proxy.todos, { id: 1, text: "New Task" }];

// Deep Reactivity (Modifying nested properties directly)
app.proxy.todos[0].done = true;

Methods & Event Handlers

Registers functions that can be triggered by @event bindings in the template.

setProgram(methodsObject)

Accepts an Object Literal containing multiple functions. These functions are bound to the app.programs object.

app.setProgram({
  handleInput: (e: Event) => {
    app.proxy.title = (e.target as HTMLInputElement).value;
  },
  addTodo: () => {
    // Logic here
  }
});

Calling Handlers Externally (External Triggers)

Because handlers are stored in app.programs, you can trigger them programmatically from anywhere in your module (e.g., from a WebSocket event or a third-party script).

// Manually calling a registered handler
app.programs.addTodo();

// Useful for external integrations like WebSockets
socket.on('new_notification', (data) => {
  app.proxy.notificationCount++;
  // You can also call programs if needed
  app.programs.showToast(data.message);
});

Template Syntax

Text Interpolation ({{ }})

<h1>{{ title }}</h1>
<p>{{ todos.length === 0 ? 'No tasks' : 'Tasks available' }}</p>

Attribute Binding (:)

<button :disabled="isLocked">Save</button>
<div :class="isActive ? 'active' : 'hidden'"></div>

Event Binding (@)

<button @click="addTodo()">Add</button>
<input @input="handleInput(e)" />

List Rendering (:for)

<ul>
  <li :for="todo in todos">
    {{ todo.text }}
  </li>
</ul>

Animations & Transitions

HTMP does not include a custom JavaScript animation API (like GSAP or Framer Motion). Instead, it embraces native Web APIs. Because HTMP only surgically patches the DOM (changing class or nodeValue), you get smooth 60FPS animations by combining HTMP reactivity with pure CSS.

1. CSS Transitions (Class Binding)

When HTMP updates an attribute like :class, the browser automatically triggers CSS transitions. No JavaScript animation logic needed.

/* CSS */
.button { transition: background-color 0.3s ease; }
.active { background-color: #1267F5; }
<!-- HTML -->
<button :class="isActive ? 'active' : ''">Toggle</button>

2. View Transitions API (SPA Navigation)

For complex UI changes (like swapping a code box or transitioning between SPA pages), wrap your Proxy update inside the native document.startViewTransition() function. HTMP will patch the DOM, and the browser will handle the snapshot morphing.

// JavaScript
app.setProgram({
  switchTab: (tabName) => {
    // 1. Tell browser to start transition snapshot
    document.startViewTransition(() => {
      // 2. HTMP updates the Proxy and patches DOM
      app.proxy.activeTab = tabName;
      app.proxy.activeCode = examples[tabName].code;
    });
  }
});

3. Named View Transitions (Targeted UX)

To animate specific elements (e.g., a sliding code box instead of the whole page), assign a unique view-transition-name in your CSS. The browser will isolate the animation to that specific element.

/* CSS: Target specific element, not root */
#active-code-box {
  view-transition-name: code-slide;
}

/* Define slide-in/slide-out animation */
::view-transition-old(code-slide) {
  animation: slide-out-left 0.3s ease-in forwards;
}
::view-transition-new(code-slide) {
  animation: slide-in-right 0.3s ease-out forwards;
}

Why this approach? The UX Designer owns the visual effects (CSS), and the Developer owns the data flow (Proxy). HTMP stays at 3.9 KB, and the browser's native C++ engine handles the heavy lifting for 60FPS rendering.

Lifecycle Hooks

onMount(callback)

app.onMount(() => {
  console.log('Component is mounted.');
});

onUnmount(callback)

app.onUnmount(() => {
  console.log('Component hidden. State preserved.');
});

onDestroy(callback)

app.onDestroy(() => {
  console.log('Component destroyed completely.');
});

Execution Controls

mount(targetId?)

app.mount(); // Mounts to rootId
app.mount('new-root'); // Mounts to different container

unmount()

app.unmount(); // Detaches from DOM, state remains in memory

remount()

app.remount(); // attach back to DOM with state remains

destroy()

app.destroy(); // Obliterates component, clears memory