dom99 is a JavaScript framework that provides a nice API to manipulate the DOM: Select nodes, add event listeners, two-way data binding, populate HTML templates with data, leverage custom elements.


To spend less time in the development. dom99 encourages you to link the UI and the logic declaratively. dom99 naturally promotes to put only markup in HTML, only styling in CSS, and only logic in JS, instead of mixing things up. That prevents a lot of bugs from even existing. dom99 is fast, the source file is small (about 3KB minified+ gziped), has no external dependencies and is written respecting modern ES and HTML standards. dom99 is simple by design. There are no build steps required.

Why not

  • No support for some old browsers
  • Fear of the unknown
  • You have already a working code base that is maintainable
  • You need a comprehensive framework that has all the tools/components/CSS you need out of the box.
  • No built-in server side rendering mechanism


dom99 exposes a JS API Using d.start(); executes directives found in the HTML. After that, the DOM is linked to directly accessible JavaScript variables. Changing those JavaScript variables will propagate the changes to the DOM and vice versa.



Browser Support

  • Chrome
  • Samsung Internet
  • Firefox
  • Opera
  • Safari
  • Edge
  • Internet Explorer 9+*

* Supports also requires

Support may also require transpilation. You may also want to use built/dom99.iife.es5.min.js.Support may also require other polyfills and transpilation see documentation/examples/css/basics.css and polyfills/ and documentation/examples/

  1. ES2015 polyfill found under node_modules/@babel/polyfill/dist/polyfill.min.js
  2. template-mb
  3. template-mb-bootstrap
  4. polyfill.css
  5. remove.js
  6. dom-hidden.js


Copyright GrosSacASacs

Distributed under the Boost Software License, Version 1.0



Chat on Miaou

Issues reports

Report issue


Contributions are welcome.

How to


dom99 will browse the DOM and react if an element has one of the following attributes

The general syntax is

<tag data-keyword="token1-token2" > text ... </tag>

If you are not using browserify or ES modules you need to include this script tag in your html before other scripts that access dom99.

<script src="./node_modules/dom99/built/dom99.iife.js"></script>
<script src="js/yourJavascriptFile.js"></script>

In JavaScript

import * as d from "./node_modules/dom99/source/dom99.js";
// to start using dom99 pre selected elements use this statement

data-function and d.functions for event listeners

Store functions in the d.functions object

d.functions.functionName = aFunction;

aFunction is called when you click this button

<button data-function="click-functionName">Action</button>

data-variable and d.variables and d.feed for data binding

To changes the text of <p data-variable="talkings"></p> and all other element that share the variable "talkings"

d.feed({talkings: `Hi`});

Use the same data-variable="talkings" on <input> elements for two-way data-binding

data-list and data-use to display a list

<ol data-list="numbers" data-use="li"></ol>
d.feed(`numbers`, [

data-element and d.elements for direct element manipulation


Removes <h1 data-element="title">Hello</h1>

data-template to declare templates

<template data-template="d-comment">
    <p data-variable="text">default text</p>
    <time data-variable="date">default time</time>

data-scope to put html templates copies

<d-comment data-scope="first"></d-comment>
<d-comment data-scope="second"></d-comment>

To edit the first <d-comment>

d.feed(`first`, {
    text: `A new comment`,
    date: `Today`

Complete overview


Use start after having stored all the event handlers in d.functions, and before using references to nodes stored in d.elements.

This will look the for dom99 directives in the document tree.

Use HTML templates, it is healthy

There are 2 ways to use HTML templates

Both ways are complementary and use the same core ideas. To illustrate this imagine you want to have a web page with an article and comments. When the page loads you want to display the article and already display the 2 last comments without another client-server round-trip, later when the user scrolls down or clicks a button to show more comments we load more comments into the page with dynamic template insertion. We here define a comment as some text and a date. What we want initially is something like this

Dynamic template injection in general

<template data-template="d-tagname">
    <p data-variable="text" ></p>
    Any HTML ...
<div data-element="target"></div>

// manual, see templates3.html to see how to use shortcuts
// 0 make a description
let customElementDescription = {
    [`tagName`]: `d-tagname`,
    [`data-scope`]: `key`
// 1 create HTML Element
let customElement = d.createElement2(customElementDescription);

// 2 link it
d.start({ startElement: customElement });

// 3 insert the Element that has a clone as a child in the DOM

I encourage you to encapsulate what changes in functions.

About templates

If you have a <template> in your page, it is inert and not rendered. However the template itself with a data-template can be used to create copies of the content of the template. These copies can be inserted in your document. The value of the attribute data-scope is called the key in this documentation. You can use d.variables d.elements and d.functions inside templates. To differentiate the multiple template clones you use the key. For instance:

d.variables[`key>text`] = `A string`;

d.elements[`key>elementName`].className = `class-fun`;


d.variables[d.scopeFromArray([`key`, `text`])] = `A string`;

d.elements[d.scopeFromArray([`key`, `elementName`])].className = ...

d.functions function the other way around. You use the same event handlers for all template copies and handle differences with d.scopeFromEvent(event). Example

<!-- HTML -->
<template data-template="d-tagname">
    <button data-function="close" >CLOSE (X)</button>
    <button data-function="update" >Update (X)</button>
    <p data-variable="text" >Default Text</p>
    <div data-element="status" class="status" ></div>
    <ul data-list="myList" data-use="li" >
        <li>Default List Item</li>
    <div data-scope="encapsulated" >
        <p data-variable="text" >Other Default Text</p>

    Any HTML ...

<!-- used like -->
<d-tagName data-scope="myKey0"></d-tagName>

<!-- or -->
<div is="d-tagName" data-scope="myKey1"></div>

<!-- or -->
<div data-list="listKey" data-use="d-tagName"></div>

// JS
d.functions.update = function (event) {
    const scope = d.scopeFromEvent(event);
    // you can access variables, elements, lists scopes with d.scopeFromArray
    //  - text -
        const textScope = d.scopeFromArray([scope, `text`]);
        const textVariable = d.variables[textScope]; // read
        d.feed(textScope, `New Text`); // write
    // - elements -
        const statusScope = d.scopeFromArray([scope, `status`]);
        const statusDiv = d.variables[statusScope]; // read
        statusDiv.classList.add(`updated`) // use
    // - list -
        const listScope = d.scopeFromArray([scope, `myList`]);
        const listVariable = d.variables[listScope]; // read
        d.feed(listScope, [
        ]); // write with side effect
    // - encapsulated -
        // here d.contextFromArray takes a 3 item list to go 1 level deeper the tree
        const encapsulatedTextScope = d.contextFromArray([scope, `encapsulated`, `text`]);
        const textVariable2 = d.variables[encapsulatedTextScope]; // read
        d.feed(encapsulatedTextScope, `New Encapsulated Text`); // write

    // this looks verbose but it works for any kind of deep html composition.
    //If you want to target a specific element or variable you can directly write

Note that d.scopeFromEvent only works on child of an element that use data-scope or equivalent, not on the element with scopeFromEvent itself

Some Examples

Even more examples



Readme (Markdown)









Extra Tips

View components in components/ folder

You can handle new HTML with d.start({ startElement: document.body });. Already processed elements won't be affected at all because the * is added to the attribute value after that.

Open your console, handy warnings may appear to help.

You can add a class to your app element container like "not-ready". Then in your CSS display that .not-ready with a loading animation. Once you have initialized everything you can remove the "not-ready" class name.

You can change the dom99 syntax. To do that follow the instructions in js/dom99ConfigurationExample.js

Server side rendering is compatible with dom99, but you have to glue it together yourself. A future version will make this built in with a node extension.


General Tips

In short: Rendering and painting the DOM is slow, JavaScript itself is fast. Simply changing a class name of an element can cause the browser to do heavy computation under the hood. For very simple programs, performance may not be an issue at all.

  • Avoid Document Object Model (DOM) Access in big loops
  • Instead compute the result in your loop first, then assign the final result to the DOM
  • Avoid read/write alternations with the DOM
  • Instead chain reads, then chain writes
  • Use callbacks or Promises or equivalent for future events (example XMLHttpRequest), never block !
  • If you have more performance issues, profile first to know what is the cause
  • If you need to do heavy computation, consider using Web Workers
  • More tips
  • About layout trashing

How is dom99 fast ?

  • Load time element selection and binding instead of runtime element selection and binding
  • The abstraction layer is small, no assumptions are made
  • No complexity, no dirty checking, no separate model under the hood