Creating Elements
Sometimes the page already has the structure you need and you only change text. More often, you build new DOM yourself — list items, cards, table rows, toast notifications. The classic recipe is create → configure → insert. This page shows that pattern, the modern insertion methods, and a few performance tricks that matter once you start building hundreds of nodes at a time.
createElement: making a node
document.createElement(tagName) returns a brand-new element that is not yet attached to the page. You configure it, then insert it.
const li = document.createElement("li");
li.className = "task";
li.textContent = "New task";
document.querySelector(".list").append(li);Until you insert it, the element exists only in memory. That is useful: you can build a whole subtree, fully configured, before touching the live page.
Setting properties and attributes
Most things can be set as a JavaScript property:
const a = document.createElement("a");
a.href = "/about";
a.textContent = "About us";
a.className = "nav-link active";
a.id = "about-link";Some attributes have no matching property — data-*, custom ARIA attributes, or attributes for components that don't ship with the browser. Use setAttribute then:
a.setAttribute("data-track", "nav-about");
a.setAttribute("aria-current", "page");We'll cover the attribute vs property distinction in detail on the next page; for now, "use the property if there is one, fall back to setAttribute" is a good rule.
Inserting: append, prepend, before, after
The modern insertion API is small and consistent:
parent.append(...nodes)— add as last children.parent.prepend(...nodes)— add as first children.sibling.before(...nodes)— insert just before this element.sibling.after(...nodes)— insert just after this element.old.replaceWith(...nodes)— swapoldout for the new nodes.
four positions, no math
const list = document.querySelector(".list");
const newItem = document.createElement("li");
newItem.textContent = "Fresh";
list.append(newItem); // last child
list.prepend(newItem); // first child (the same node is moved, not copied)
const middle = list.children[1];
middle.before(newItem); // just before middle
middle.after(newItem); // just after middleStrings and elements together
append, prepend, before and after accept any mix of strings and nodes. Strings are inserted as text nodes — safe, no HTML parsing.
const p = document.createElement("p");
const strong = document.createElement("strong");
strong.textContent = "Heads up:";
p.append(strong, " backups complete.");
// <p><strong>Heads up:</strong> backups complete.</p>replaceWith and remove
swap and delete
const banner = document.querySelector(".old-banner");
const fresh = document.createElement("div");
fresh.className = "new-banner";
fresh.textContent = "Welcome back!";
banner.replaceWith(fresh);
document.querySelector(".tooltip")?.remove();element.remove() detaches the element from its parent. It does not destroy it — you can keep the reference and re-insert it later. Letting the reference go out of scope is what frees the memory.
Building bigger subtrees
a card from scratch
function buildCard({ title, body, href }) {
const card = document.createElement("article");
card.className = "card";
const h = document.createElement("h2");
h.textContent = title;
const p = document.createElement("p");
p.textContent = body;
const link = document.createElement("a");
link.href = href;
link.textContent = "Read more";
card.append(h, p, link);
return card;
}
document.body.append(
buildCard({ title: "DOM", body: "Trees of nodes.", href: "/dom" }),
buildCard({ title: "Events", body: "Bubbling and capture.", href: "/events" }),
);DocumentFragment: many nodes, one insertion
Inserting nodes one by one into a live page triggers layout each time. When you have many to add, build them into a DocumentFragment first and insert the fragment in one shot — only the final insertion causes a layout pass.
const list = document.querySelector(".list");
const frag = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
const li = document.createElement("li");
li.textContent = "Task " + i;
frag.append(li);
}
list.append(frag); // one reflow instead of 10001000 <li> elements appear, with one layout pass instead of a thousand.
Cloning
const template = document.querySelector(".task-template");
const copy = template.cloneNode(true); // deep clone
copy.classList.remove("task-template");
copy.querySelector(".title").textContent = "New task";
document.querySelector(".list").append(copy);For more elaborate templates, the <template> element lets you write inert HTML in your markup and clone its contents with template.content.cloneNode(true) — no parsing of user strings needed.