Creating and Removing Elements

Learn how to build new DOM elements from scratch with createElement, insert them at different positions in the tree, remove or replace existing elements, and use DocumentFragment for efficient batch insertion.

Real interactive pages don't just modify existing elements — they build entirely new ones on the fly, like adding a new item to a to-do list, and remove elements a user no longer needs. This lesson covers the full toolkit for creating, positioning, and removing DOM nodes.

createElement

document.createElement(tagName) creates a brand-new element node of the given type (like "div", "li", or "button") that exists in memory but is not yet part of the visible page — it has no parent and won't appear on screen until you explicitly insert it somewhere in the tree.

After creating an element, you typically configure it (set its textContent, attributes, or classes) before inserting it, since it's usually cheaper and cleaner to fully prepare an element off-screen and insert it once, rather than inserting it first and modifying it in place afterward.

Inserting elements: append, appendChild, prepend

parent.appendChild(node) is the classic method for adding a node as the last child of parent — it only accepts a single actual Node object (not a plain string), and it returns the appended node.

parent.append(...nodes) is the modern equivalent: it also adds content as the last child, but it's more flexible — it accepts multiple arguments, and unlike appendChild, it accepts plain strings directly (inserting them as text), not just Node objects. parent.prepend(...nodes) works the same way but inserts at the beginning, as the first child(ren), instead of the end.

Inserting relative to an element: before and after

node.before(...nodes) inserts new content as a sibling immediately before node (at the same level in the tree, not inside it), and node.after(...nodes) inserts new content as a sibling immediately after it. These are useful when you need to insert something next to an existing element rather than inside a container.

Like append/prepend, before() and after() accept multiple arguments and allow plain strings, not just Node objects, making them convenient for quick insertions without manually creating a text node.

Removing and replacing elements

element.remove() removes the element from the DOM entirely — a simple, direct method that doesn't require going through the element's parent (unlike the older pattern of parent.removeChild(element), which still works but is more verbose).

element.replaceWith(newNode) removes element from the tree and puts newNode in its exact place in one step, which is more concise than manually inserting the new element and then removing the old one separately.

DocumentFragment

document.createDocumentFragment() creates a special lightweight container that lives outside the normal document tree. You can append many elements into it, and when you finally insert the fragment itself into the real DOM, all of its children are moved in at once — and the (now-empty) fragment itself is discarded, leaving only its contents behind.

The performance benefit: inserting elements into the live DOM one at a time can trigger a separate layout/render step for each insertion when done in a loop, whereas building everything inside a DocumentFragment first and inserting it once triggers only a single layout update, which matters when adding many elements at once (like rendering a long list).

Creating, inserting, and removing

createElement only builds the element in memory — nothing appears on the page until it's inserted somewhere. append()/prepend()/before()/after() accept strings directly; appendChild() requires an actual Node object. element.remove() is simpler than the older parent.removeChild(element) pattern and doesn't require a reference to the parent.

const li = document.createElement("li");
li.textContent = "New task";
li.className = "todo-item";

const list = document.querySelector("#todo-list");
list.appendChild(li);       // classic, adds as last child
list.append("plain text");  // modern, accepts strings too
list.prepend(li);           // adds as first child instead

li.before(document.createElement("hr")); // insert sibling before li
li.after("a trailing note");              // insert sibling after li

li.remove();                     // removes li entirely
li.replaceWith(document.createElement("li")); // swap in a new node

Code Examples

Creates a brand-new <p> element, sets its text safely with textContent, and inserts it as the last child of <body>, where it becomes visible on the page. Expected result: A new paragraph reading "Welcome to SampleApp!" appears at the end of the page body.

document.body.appendChild(paragraph) is the step that actually places the new element into the visible DOM tree.

Builds a compound list item containing both text and a delete button, attaches a click handler that removes the whole <li> from the DOM when clicked, and demonstrates append() accepting a mix of a string and a Node in one call. Expected result: Two new <li> items appear in #todo-list, each with its own text and a working Delete button that removes just that item when clicked.

li.append(" ", removeBtn) inserts both a plain text string and an actual button element as children of li in a single call.

Builds every list item inside an off-DOM DocumentFragment first, then inserts the whole fragment into the real list in one operation, avoiding five separate live-DOM insertions (and the layout cost that would come with each one). Expected result: #big-list ends up containing five new <li> elements, inserted in a single DOM operation rather than five separate ones. For a handful of elements the performance difference is invisible, but for rendering hundreds or thousands of items (like search results), batching insertions through a DocumentFragment can meaningfully reduce layout thrashing.

list.appendChild(fragment) moves all of the fragment's children into #big-list at once; the fragment itself ends up empty and is discarded.

const paragraph = document.createElement("p");
paragraph.textContent = "Welcome to SampleApp!";
document.body.appendChild(paragraph);
function addTodo(text) {
  const li = document.createElement("li");
  li.textContent = text;

  const removeBtn = document.createElement("button");
  removeBtn.textContent = "Delete";
  removeBtn.addEventListener("click", () => li.remove());

  li.append(" ", removeBtn);
  document.querySelector("#todo-list").appendChild(li);
}

addTodo("Learn the DOM");
addTodo("Build a to-do app");
function renderList(items) {
  const list = document.querySelector("#big-list");
  list.textContent = ""; // clear existing content

  const fragment = document.createDocumentFragment();

  items.forEach(text => {
    const li = document.createElement("li");
    li.textContent = text;
    fragment.appendChild(li);
  });

  list.appendChild(fragment); // one single insertion into the live DOM
}

renderList(["Item 1", "Item 2", "Item 3", "Item 4", "Item 5"]);

Where before/after/prepend/append insert relative to an element

before()/after() insert new siblings next to the element (outside it, at the same tree level), while prepend()/append() insert new children inside it, at the start or end respectively.

[ before(x) ]
<li id="item">
   [ prepend(x) ]  ← first child
   ...existing children...
   [ append(x)  ]  ← last child
</li>
         [ after(x)  ]

Important Rules

createElement() only builds a node in memory — you must explicitly insert it (append, prepend, before, after, etc.) for it to appear on the page.

append()/prepend()/before()/after() accept plain strings and multiple arguments; appendChild() only accepts a single Node object.

element.remove() deletes the element directly, with no need to reference its parent, unlike the older parent.removeChild(element) pattern.

Use a DocumentFragment to batch many insertions into one DOM operation when adding a large number of elements at once.

Where this shows up

Every dynamically rendered list — search results, a shopping cart, a notification feed, chat messages, a to-do app — is built by creating and inserting DOM elements at runtime. Removing elements powers 'delete' buttons, dismissible alerts, and closing modals. DocumentFragment-based batching is a standard technique in performance-sensitive rendering code, including many of the patterns that underlie modern front-end frameworks.

list.appendChild("New item"); // TypeError: not a Node list.append("New item"); // OR: create a text node / element first for appendChild appendChild() strictly requires a real Node object and throws a TypeError if given a plain string. The newer append() method is more forgiving and accepts strings directly, inserting them as text nodes automatically.

items.forEach(text => { const li = document.createElement("li"); li.textContent = text; list.appendChild(li); // inserts into the live DOM on every iteration });

// Build all <li> elements into a DocumentFragment first, then append the fragment once after the loop.

Appending directly to the live DOM inside a loop can force the browser to recalculate layout on every single iteration for a large list. Batching the work through a DocumentFragment and inserting once is more efficient.

[Multiple Choice] Which method can insert both a plain text string and an Element node in the same call, as the last children of a parent? A. parent.appendChild(...) B. parent.append(...) C. parent.insertBefore(...) D. parent.createElement(...)

B. parent.append(...) — append() (unlike the older appendChild()) accepts multiple arguments that can be a mix of plain strings and Node objects, inserting them all as the last children in order. appendChild() only accepts exactly one Node.

[True / False] A DocumentFragment is itself inserted into and remains visible as a wrapper element in the final DOM. A. True B. False

B. False — False. When a DocumentFragment is appended into the live DOM, only its children are actually moved into the document — the fragment itself is never inserted as a visible wrapper and ends up empty afterward.

Write a function addNotification(message) that creates a new <div class="notification"> containing the given message, inserts it at the top (as the first child) of an element with id "notifications", and removes it automatically when it's clicked.

function addNotification(message) { const note = document.createElement("div"); note.className = "notification"; note.textContent = message; note.addEventListener("click", () => note.remove()); document.querySelector("#notifications").prepend(note); } addNotification("Your changes were saved.");

Use createElement, textContent, classList (or className), prepend() to insert at the top, and an event listener that calls .remove() on click.