Skip to content
JavaScript js dom 4 min read

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. textContent is 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).

MethodInserts the node…Called on
parent.append(...)as the last child of parentthe parent
parent.prepend(...)as the first child of parentthe parent
node.before(...)as a previous siblingthe reference node
node.after(...)as a next siblingthe reference node
node.replaceWith(...)swaps the node for new contentthe 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 textContent over innerHTML for any text that originates from users or APIs.
  • Use the modern append/prepend/before/after methods — they accept strings and multiple nodes, unlike appendChild.
  • Batch many inserts through a DocumentFragment (or a single spread append) to minimize reflows.
  • Call node.remove() instead of parent.removeChild(node); it’s shorter and needs no parent reference.
  • Use replaceChildren() to clear a container — it’s clearer and safer than innerHTML = "".
  • Remember that cloneNode(true) copies descendants but not listeners added with addEventListener.
  • Configure a detached element fully in memory before inserting it, so the browser lays it out only once.
Last updated June 1, 2026
Was this helpful?