HTML lacks the component system, reactivity, and other paradigms that underpin modern UI development. OOHTML brings these capabilities to HTML and makes it possible to directly author user interfaces in HTML.
OOHTML is Object-Oriented HTML. It is a semantic layer over standard HTML that adds new behaviours to the DOM — including reactivity and a declarative component system.
It comes as a script that plugs diretly into the DOM and have new semantics take effect. No compile step or setup is required. And that makes it especially convenient to work with.
<script src="https://unpkg.com/@webqit/oohtml/dist/main.lite.js"></script>OOHTML enables a simple "define-and-use" system in HTML that is based on two complementary elements — the <template> and <import> elements. It makes it really easy to share repeating structures and stay organized.
OOHTML gives HTML the concept of data-binding ({ expression }) and reactivity that lets you embed application data in markup and have them stay in sync with application state. You get framework-grade reactivity without the overhead.
OOHTML extends the existing CSS scoping system to support the familiar <style scoped> syntax, introduces scoping for scripts (<script scoped>), and solves namespacing for IDs (namespace). They form a complete scoping system that is both declarative and powerful.
All of the above can be seen in a three-step tour. Each sample document below can be previewed directly in the browser:
At its core, OOHTML is a component system. It lets you write HTML as reusable components.
The standard <template> element already lets you define reusable markup. OOHTML completes the idea by introducing the <import> element.
You write the following:
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/@webqit/oohtml/dist/main.lite.js"></script>
<!-- A reusable component -->
<template def="card">
<article>
<h2>Static title</h2>
<p>Static body text.</p>
</article>
</template>
</head>
<body>
<!-- The import -->
<import ref="card"></import>
</body>
</html>It resolves to the following, at runtime:
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/@webqit/oohtml/dist/main.lite.js"></script>
<!-- The reusable component -->
<template def="card">
<article>
<h2>Static title</h2>
<p>Static body text.</p>
</article>
</template>
</head>
<body>
<!-- The resolved import -->
<article>
<h2>Static title</h2>
<p>Static body text.</p>
</article>
</body>
</html><template def>— called a HTML module – defines the reusable markup.<import ref>— called an import – instantiates that module in its place.- The relationship between
<template>and<import>continues live such that changes in<template>or<import>either dissolve or re-resolve the import.
Note
Later we'll cover the various usage patterns supported by the <template> and <import> system. We will also introduce file-based components and remote imports.
As a complete component system, OOHTML extends the DOM to support data-binding and reactivity.
OOHTML follows the conventional syntax for data-binding — { expression } — but has that written inside HTML comments: <?{ expression }?> or <!--?{ expression }?-->. These start life as normal HTML comments but render application data at runtime.
You write the following:
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/@webqit/oohtml/dist/main.lite.js"></script>
</head>
<body>
<h1><?{ title }?></h1>
<p><!--?{ content }?--></p>
<p>Count: <?{ count }?></p>
<script>
document.bind({
title: "Hello OOHTML",
content: "Pure HTML, now reactive.",
count: 0,
});
setInterval(() => {
document.bindings.count++;
}, 1000);
</script>
</body>
</html>It resolves to the following, at runtime:
<!DOCTYPE html>
<html>
<head>
…
</head>
<body>
<h1>Hello OOHTML</h1>
<p>Pure HTML, now reactive.</p>
<p>Count: 0</p>
<!-- increments live -->
<script>
document.bind({
title: "Hello OOHTML",
content: "Pure HTML, now reactive.",
count: 0,
});
setInterval(() => {
document.bindings.count++;
}, 1000);
</script>
</body>
</html><?{ expression }?>and<!--?{ expression }?-->function as comments but embed reactive expressions.- Both styles –
<? ?>and<!-- -->– are valid HTML comments and are interchangeable.
- Both styles –
document.bind({ ... })binds data to the DOM – at the document level.- The data converges to
document.bindings– a reactive data interface.
- The data converges to
- Embedded expressions resolve from the bound data and stay in sync with it. Changes to data are automatically reflected in the UI.
Note
Later we'll cover OOHTML's attribute-based binding syntax. We'll also formally introduce Mutation-Based Reactivity – the form of reactivity that OOHTML is based on.
From the component and data-binding systems above to the scoping system yet to be discussed – OOHTML's features compose nicely into various usage patterns.
The document below brings some of that to life.
You write:
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/@webqit/oohtml/dist/main.lite.js"></script>
<template def="card">
<!-- Component with scoped IDs -->
<article namespace>
<h2 id="title"><?{ localTitle }?> – <?{ globalTitle }?></h2>
<p id="body"><?{ body }?></p>
<p>Local count: <?{ count }?></p>
<!-- Scoped styles -->
<style scoped>
:scope {
border: 1px solid #ddd;
padding: 1rem;
margin-bottom: 1rem;
}
#title {
color: teal;
}
#body {
opacity: 0.85;
}
</style>
<!-- Scoped scripts -->
<script scoped>
this.bind({
localTitle: "Card Title",
body: "Rendered inside the component.",
count: 0,
});
setInterval(() => this.bindings.count++, 1000);
</script>
</article>
</template>
</head>
<body>
<h2 id="title"><?{ globalTitle }?></h2>
<p id="body"><?{ body }?></p>
<p>Global count: <?{ count }?></p>
<!-- Import the component -->
<import ref="card"></import>
<!-- Import the component again -->
<import ref="card"></import>
<p class="footer">Footer: <?{ footerNote }?></p>
<script>
document.bind({
globalTitle: "Card Demo",
body: "Rendered outside the component.",
footerNote: "Rendered outside the component.",
count: 0,
});
setInterval(() => document.bindings.count++, 2000);
</script>
</body>
</html>It resolves to the following, at runtime:
<!DOCTYPE html>
<html>
<head>
<!-- existing contents -->
</head>
<body>
<h2 id="title">Card Demo</h2>
<p id="body">Rendered outside the component.</p>
<p>Global count: 0</p>
<article namespace>
<h2 id="title">Card Title – Card Demo</h2>
<p id="body">Rendered inside the component.</p>
<p>Local count: 0</p>
<style scoped>
/* existing contents */
</style>
<script scoped>
/* existing contents */
</script>
</article>
<article namespace>
<h2 id="title">Card Title – Card Demo</h2>
<p id="body">Rendered inside the component.</p>
<p>Local count: 0</p>
<style scoped>
/* existing contents */
</style>
<script scoped>
/* existing contents */
</script>
</article>
<p class="footer">Footer: Rendered outside the component.</p>
<script>
/* existing contents */
</script>
</body>
</html>- We have a single component imported twice.
- Style, script, and IDs scoped to the component so that repeating the structure in the DOM don't create collisions.
- Bindings resolve seamlessly inside and outside the component.
- The template ↔ import relationship hold live as before.
- Such that if you located the original
<template def> → <article>element in the browser's console and deleted the node, all imports would dissolve; restored, all imports would resolve.
- Such that if you located the original
- Similarly, the namespace ↔ ID relationship, and the data ↔ binding relationship, all hold live.
By simply enhancing HTML, OOHTML makes it possible to directly author modern user interfaces in HTML and effectively removes the tooling tax traditionally associated with UI development. In place of a compile step, you get a back-to-the-basics experience and an edit-in-the-browser workflow.
Tip
In addition to inline components, OOHTML also supports file-based components. It's companion CLI tool – OOHTML CLI – lets you define your components in files and have them come together into a single file that you can directly import into your page.
Tip
OOHTML solves the UI side of your application. You would need a framework to build a complete app with OOHTML. Webflo is a modern fullstack framework that converges on OOHTML for the UI. You even get Hot Module Replacement (HMR) on top as you edit your HTML components.
OOHTML comes as its own addition to the DOM – alongside Web Components and Shadow DOM. Far from an Anti-Shadow DOM effort, OOHTML complements the HTML authoring experience inside the Shadow DOM itself – as it does outside of it. When used in the Shadow DOM, the Shadow DOM simply becomes the document that OOHTML sees – the Shadow Root itself (#shadow-root) being the new document root that OOHTML works with.
Leveraging OOHTML in the Shadow DOM requires no additional step. Simply have the OOHTML script loaded in the main document as before and write.
For a quick way to see OOHTML in the Shadow DOM, we could suppose the whole of example 3 above as the Shadow DOM of a certain Web Component. It would look like this:
Web Component + Shadow DOM + OOHTML – (Click to show)
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/@webqit/oohtml/dist/main.lite.js"></script>
<script type>
customElements.define('demo-component', class extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
// Shadow DOM markup
this.shadowRoot.innerHTML = `
<template def="card" scoped>
<!-- Reusable markup -->
<article namespace>
<h2 id="title"><?{ localTitle }?> – <?{ globalTitle }?></h2>
<p id="body"><?{ body }?></p>
<p>Local count: <?{ count }?></p>
<style scoped>
:scope {
border: 1px solid #ddd;
padding: 1rem;
margin-bottom: 1rem;
}
#title {
color: teal;
}
#body {
opacity: 0.85;
}
</style>
<scr` + `ipt scoped>
this.bind({
localTitle: "Card Title",
body: "Rendered inside the component.",
count: 0,
});
setInterval(() => this.bindings.count++, 1000);
</scr` + `ipt>
</article>
</template>
<h2 id="title"><?{ globalTitle }?></h2>
<p id="body"><?{ body }?></p>
<p>Global count: <?{ count }?></p>
<import ref="card">1</import>
<!-- Import 1 -->
<import ref="card">2</import>
<!-- Import 2 -->
<p class="footer">Footer: <?{ footerNote }?></p>
<scri` + `pt>
this.shadowRoot.bind({
globalTitle: "Card Demo",
body: "Rendered outside the component.",
footerNote: "Rendered outside the component.",
count: 0,
});
setInterval(() => this.shadowRoot.bindings.count++, 2000);
</scri` + `pt>`;
}
});
</script>
</head>
<body>
<demo-component></demo-component>
</body>
</html>[!IMPORTANT] The example above follows the pattern that OOHTML currently supports: injecting the shadow DOM markup in the
connectedCallback()phase. Doing so in the constructor currently doesn't work. Declarative Shadow DOM is also not supported yet. We hope to overcome this limitation in future versions.
OOHTML adds a coherent set of capabilities to HTML: a declarative component system, data-binding and reactivity, scoped styles and scripts, namespaces for IDs, and the underlying context model that ties them together.
This README introduces the ideas. The full reference each lives in the Wiki.
| Capability | Description | Reference |
|---|---|---|
| HTML Imports | Declarative & imperative module imports (<template def> · <import ref>) including remote modules, inheritance, contexts, and live resolution. |
HTML Imports |
| Data Binding | Comment-based bindings (<?{ }?>), attribute-based bindings (render="…”), list rendering, and runtime updates. |
Data Binding |
| DOM Scoping | Style and script scoping (<style scoped>, <script scoped>) Namespaces for IDs. |
DOM Scoping |
| Bindings API | Reactive state surfaces on any DOM node (node.bindings, node.bind()), powering all binding resolution. |
Bindings API |
| Context API | The request/response infrastructure that powers imports, bindings, namespacing, and scoping resolution. | Context API |
| Live Scripts | Fine-grained reactivity embedded directly in JavaScript using the Quantum runtime. | Live Scripts |
| SSR Model | Server-rendering rules, import hydration, and binding preservation. | SSR |
| Advanced Topics | Imports contexts, module inheritance, execution timing, Web Component integration, and more. | Advanced Topics |
Many of these pages coming soon.
OOHTML can be used in two ways:
- Directly in the browser, via a client-side script
- Via npm – for integration with bundlers, jsdom, SSR, etc.
This is the simplest way to use OOHTML in a normal web page.
<script src="https://unpkg.com/@webqit/oohtml/dist/main.lite.js"></script>Put this script early in the document.
It must be a classic script — without an async or defer attribute — so the script can load as a blocking script.
Important
Early placement in the document and loading as a blocking script helps ensure that OOHTML is present in the document's parsing phase. OOHTML needs to be present while the document is parsed so it can activate the needed behavior on the relevant elements.
OOHTML ships with two builds:
main.lite.js– the lite edition; the default and recommended. Provides async execution for scoped scripts and "live" scripts.main.js– the full edition; needed when you require synchronous script timing for scoped scripts and "live" scripts.
For integrating with bundlers, testing, or server-side operations, install OOHTML and its @webqit/use-live dependency:
npm i @webqit/oohtml @webqit/use-liveThen bootstrap OOHTML on the target DOM instance:
import * as UseLive from '@webqit/use-live/lite';
import init from '@webqit/oohtml/src/init.js';
init.call(window, UseLive/*, options */);For pure server-side operations and SSR, install jsdom and use its window object as OOHTML's window option:
import { JSDOM } from "jsdom";
import * as UseLive from '@webqit/use-live/lite';
import init from '@webqit/oohtml/src/init.js';
const dom = new JSDOM(`<html><body></body></html>`);
init.call(dom.window, UseLive/*, options */);For the best SSR setup, see OOHTML SSR.
All forms of contributions are welcome at this time. For example, syntax and other implementation details are all up for discussion. Also, help is needed with more formal documentation. And here are specific links:
MIT.