Creating & Removing Elements
Most interactive web apps don’t ship every node in their initial HTML — they build the page on the fly in response to data and user actions. JavaScript gives you a small, modern toolkit for minting fresh DOM nodes, slotting them into precise positions, removing them again, and doing all of it efficiently. This page covers createElement, the insertion methods (append, prepend, before, after), removal with remove and replaceWith, document fragments for batched inserts, and cloning.
Creating an element
The starting point is document.createElement(tagName), which returns a detached element that isn’t yet part of the document. You configure it in memory, then insert it where you want.
const li = document.createElement("li");
li.textContent = "Buy oat milk";
li.className = "todo";
Use textContent to set text safely — it treats the input as plain text, so user-provided strings can’t inject markup. Reach for innerHTML only when you genuinely need to parse a trusted HTML string.
Security gotcha: Never pass unsanitized user input to
innerHTML. It parses the string as HTML and will execute injected<img onerror>or<script>-equivalent payloads.textContentis immune to this and is faster.
Inserting nodes
Older code used appendChild and insertBefore, but modern browsers expose a far cleaner family of methods that accept multiple nodes and plain strings (auto-converted to text nodes).
| Method | Inserts the node… | Called on |
|---|---|---|
parent.append(...) | as the last child of parent | the parent |
parent.prepend(...) | as the first child of parent | the parent |
node.before(...) | as a previous sibling | the reference node |
node.after(...) | as a next sibling | the reference node |
node.replaceWith(...) | swaps the node for new content | the reference node |
const list = document.querySelector("#todos");
const first = document.createElement("li");
first.textContent = "Wake up";
const last = document.createElement("li");
last.textContent = "Sleep";
list.append(last); // goes to the bottom
list.prepend(first); // jumps to the top
last.before("Work"); // a bare string becomes a text node sibling
All four accept any number of arguments, mixing elements and strings freely: el.append(spanA, " and ", spanB).
Removing and replacing
To detach a node, call .remove() on the node itself — no need to reference its parent, unlike the legacy parent.removeChild(child).
const item = document.querySelector(".todo");
item.remove();
replaceWith swaps a node for new content in one call:
const placeholder = document.querySelector("#loading");
const real = document.createElement("p");
real.textContent = "Loaded!";
placeholder.replaceWith(real);
Document fragments for performance
Inserting nodes one at a time into the live DOM can trigger repeated layout work. A DocumentFragment is a lightweight, off-screen container: you append children to it in memory, then insert the whole fragment in a single operation. The fragment itself dissolves on insertion, leaving only its children in the DOM.
const fruits = ["apple", "banana", "cherry"];
const fragment = document.createDocumentFragment();
for (const name of fruits) {
const li = document.createElement("li");
li.textContent = name;
fragment.append(li);
}
document.querySelector("#fruits").append(fragment); // one reflow
Output:
• apple
• banana
• cherry
Tip: For a fixed batch, you can skip the fragment and call
parent.append(...nodes)with a spread — modern engines batch that single call too. Fragments shine when you build incrementally in a loop.
Cloning nodes
node.cloneNode(deep) duplicates an existing node. Pass true for a deep clone (the node plus all descendants) or false (the default) to copy just the element shell. Cloning a configured template is often cheaper than rebuilding it from scratch.
const template = document.querySelector("#card-template");
const copy = template.cloneNode(true); // includes children
copy.querySelector("h3").textContent = "New card";
document.body.append(copy);
Note that clones do not carry over event listeners added via addEventListener, though inline attributes are preserved.
Building a list interactively
The pen below wires an input to a button to add <li> items, and uses event delegation plus .remove() to delete them.
<input id="task" placeholder="New task…" />
<button id="add">Add</button>
<ul id="list"></ul>
<script>
const input = document.querySelector("#task");
const list = document.querySelector("#list");
document.querySelector("#add").addEventListener("click", () => {
const text = input.value.trim();
if (!text) return;
const li = document.createElement("li");
li.textContent = text;
const del = document.createElement("button");
del.textContent = "✕";
del.style.marginLeft = "8px";
li.append(del);
list.append(li);
input.value = "";
input.focus();
});
// Event delegation: one listener handles every delete button
list.addEventListener("click", (e) => {
if (e.target.tagName === "BUTTON") {
e.target.closest("li").remove();
}
});
</script>
Here’s a fragment-powered render that paints a whole list from an array at once.
<button id="render">Render 5 rows</button>
<ul id="grid"></ul>
<script>
document.querySelector("#render").addEventListener("click", () => {
const grid = document.querySelector("#grid");
grid.replaceChildren(); // clear existing rows
const fragment = document.createDocumentFragment();
for (let i = 1; i <= 5; i++) {
const li = document.createElement("li");
li.textContent = `Row ${i}`;
fragment.append(li);
}
grid.append(fragment); // single insertion
});
</script>
Best Practices
- Prefer
textContentoverinnerHTMLfor any text that originates from users or APIs. - Use the modern
append/prepend/before/aftermethods — they accept strings and multiple nodes, unlikeappendChild. - Batch many inserts through a
DocumentFragment(or a single spreadappend) to minimize reflows. - Call
node.remove()instead ofparent.removeChild(node); it’s shorter and needs no parent reference. - Use
replaceChildren()to clear a container — it’s clearer and safer thaninnerHTML = "". - Remember that
cloneNode(true)copies descendants but not listeners added withaddEventListener. - Configure a detached element fully in memory before inserting it, so the browser lays it out only once.