Skip to content

DOM Factory

Helpers for creating DOM nodes — elements, text nodes, fragments, and parsed HTML — in single expressions. Namespaced under dom to avoid collisions with generic names.

When to use

Use these helpers when you need to create DOM elements in a single expression with multiple attributes, classes, or event listeners and no template library. They keep element creation compact in utility code and tests.

The pattern

Basic element with attributes

Create a DOM element with properties set in a single expression — dom.element() takes a tag name and an options object, returning a fully configured element ready to insert:

javascript
const heading = dom.element("h1", {
  id: "title",
  textContent: "Hello, world",
});

Element with event listener

Inline event handlers work because DOM elements accept onclick and similar properties through plain assignment:

javascript
const button = dom.element("button", {
  textContent: "Save",
  onclick() {
    save();
  },
});

Composing a tree

Nest dom.element() calls using childNodes to build a tree in a single expression. Strings in the array become text nodes automatically — append() handles that natively:

javascript
const card = dom.element("div", {
  className: "card",
  childNodes: [
    dom.element("h2", { textContent: name }),
    dom.element("p", { textContent: bio }),
    dom.element("a", { href: url, textContent: "Profile" }),
    " · ",
    dom.element("a", { href: repo, textContent: "Source" }),
  ],
});
document.body.append(card);

Fragments

dom.fragment() groups multiple nodes without a wrapper element:

javascript
const links = dom.fragment(
  dom.element("a", { href: url, textContent: "Profile" }),
  " · ",
  dom.element("a", { href: repo, textContent: "Source" }),
);
container.append(links);

Setting dataset and style

dataset, style, and childNodes work directly in the options object — the factory handles them specially instead of assigning them as properties:

javascript
const badge = dom.element("span", {
  className: "badge",
  textContent: count.toString(),
  dataset: { status: "active" },
  style: { color: "green" },
});

Parsing HTML strings

dom.html() parses an HTML string into a DocumentFragment, working both as a regular function call and as a tagged template literal:

javascript
// Function call
const nav = dom.html('<nav><a href="/">Home</a></nav>');
  
// Tagged template literal
const greeting = dom.html`<h1>Hello, ${name}</h1>`;

Both return a DocumentFragment ready to insert. The tagged template form interpolates values into the markup before parsing.

Implementation

javascript
const dom = {
  element(tagName, options = {}) {
    const el = document.createElement(tagName);
    const { dataset, style, childNodes, ...rest } = options;
    Object.assign(el, rest);
    if (dataset) Object.assign(el.dataset, dataset);
    if (style) Object.assign(el.style, style);
    if (childNodes) el.append(...childNodes);
    return el;
  },
  
  text(content) {
    return document.createTextNode(content);
  },
  
  fragment(...children) {
    const frag = document.createDocumentFragment();
    frag.append(...children);
    return frag;
  },
  
  html(strings, ...values) {
    const markup = strings.raw ? String.raw(strings, ...values) : strings;
    const template = this.element("template", {
      innerHTML: markup,
    });
    return template.content;
  },
};

Trade-offs

ApproachProsCons
dom factoryOne expressionCustom helper
createElementNative, no helperVerbose, multi-step
Template + innerHTMLReadable markupXSS risk
Framework (compiled)Full reactivityBuild step required

The factory is best for small-scale DOM construction — utility functions, test fixtures, or lightweight prototypes. For large component trees, prefer templates or a framework.