Home Module 10 Creating & Removing Elements

1. Introduction

So far you have learned to select and modify existing elements. The real power of DOM manipulation is the ability to create brand new elements and add them to the page, or remove elements that are no longer needed — all without a page reload.

This is how modern web apps work: a to-do list adds a new item when you press Enter. A photo gallery inserts new images as you scroll. A chat app appends new messages in real time. All of these involve creating and inserting DOM elements dynamically.

This lesson covers:

  • Creating elements with createElement
  • Inserting with appendChild, prepend, insertAdjacentElement
  • Removing with remove() and removeChild()
  • Cloning elements with cloneNode()
  • Replacing elements with replaceWith()
  • innerHTML as a shortcut (and its trade-offs)
  • DocumentFragment for batch inserts

2. Theory

2.1 Creating an element

// Step 1: create the element (not yet in the page)
const li = document.createElement('li');

// Step 2: configure it
li.textContent = 'New item';
li.classList.add('todo-item');
li.dataset.id = '123';

// Step 3: insert it into the page
const list = document.querySelector('#todo-list');
list.appendChild(li);

Until you insert an element, it exists in memory but is not visible on the page.

2.2 Inserting elements

const list = document.querySelector('ul');
const newItem = document.createElement('li');
newItem.textContent = 'New item';

// appendChild — adds at the END of the parent
list.appendChild(newItem);

// prepend — adds at the BEGINNING
list.prepend(newItem);

// insertAdjacentElement — precise positioning
// 'beforebegin' — before the element itself
// 'afterbegin'  — first child of the element
// 'beforeend'   — last child (same as appendChild)
// 'afterend'    — after the element itself
list.insertAdjacentElement('afterend', newItem);

// before() / after() — relative to a sibling
const refItem = document.querySelector('li:nth-child(2)');
refItem.before(newItem);  // insert before the 2nd li
refItem.after(newItem);   // insert after the 2nd li

// append() — like appendChild but accepts strings too
list.append(newItem, 'plain text node');

2.3 Removing elements

// Modern — remove() directly on the element
const item = document.querySelector('.remove-me');
item.remove(); // removes from the DOM

// Legacy — removeChild (parent must call it)
const list = document.querySelector('ul');
const item = document.querySelector('li');
list.removeChild(item);

// Remove all children (clear a container)
const container = document.querySelector('#results');
container.innerHTML = ''; // simplest way to clear

// Alternative (avoids innerHTML)
while (container.firstChild) {
  container.removeChild(container.firstChild);
}

2.4 Cloning elements

const template = document.querySelector('.card-template');

// cloneNode(false) — shallow clone (element only, no children)
const shallow = template.cloneNode(false);

// cloneNode(true)  — deep clone (element + all children)
const deep = template.cloneNode(true);

// Modify the clone before inserting
deep.querySelector('.card-title').textContent = 'New Card';
document.querySelector('#grid').appendChild(deep);

2.5 Replacing elements

const oldEl = document.querySelector('#old');
const newEl = document.createElement('p');
newEl.textContent = 'I replaced the old element';

// replaceWith — replace an element with another
oldEl.replaceWith(newEl);

// replaceChild (legacy, parent must call it)
parent.replaceChild(newEl, oldEl);

2.6 innerHTML shortcut

Setting innerHTML is fast to write but has trade-offs:

// Quick way to insert multiple elements at once
container.innerHTML = `
  <div class="card">
    <h2>Title</h2>
    <p>Description here.</p>
  </div>
`;

// WARNING: destroys existing children and event listeners
// WARNING: XSS risk if content includes user input

// Safe: only use innerHTML with fully controlled, static strings

2.7 DocumentFragment — batch DOM inserts

Inserting many elements one at a time causes multiple reflows. A DocumentFragment lets you build a tree off-screen, then insert everything in one operation:

const fragment = document.createDocumentFragment();
const items = ['Apple', 'Banana', 'Cherry'];

for (const name of items) {
  const li = document.createElement('li');
  li.textContent = name;
  fragment.appendChild(li); // no reflow yet
}

// One DOM insert = one reflow
document.querySelector('ul').appendChild(fragment);

2.8 Building elements from data (the pattern you will use most)

const products = [
  { id: 1, name: 'Laptop',  price: 999 },
  { id: 2, name: 'Phone',   price: 699 },
  { id: 3, name: 'Tablet',  price: 499 },
];

function createProductCard(product) {
  const card  = document.createElement('div');
  card.className = 'card';
  card.dataset.id = product.id;

  const title = document.createElement('h2');
  title.textContent = product.name;

  const price = document.createElement('p');
  price.textContent = `$${product.price}`;

  const btn = document.createElement('button');
  btn.textContent = 'Add to Cart';
  btn.addEventListener('click', () => addToCart(product));

  card.append(title, price, btn);
  return card;
}

const grid = document.querySelector('#product-grid');
const frag = document.createDocumentFragment();
for (const p of products) frag.appendChild(createProductCard(p));
grid.appendChild(frag);

3. Real World Example

// To-do list with add and delete
const form     = document.querySelector('#todo-form');
const input    = document.querySelector('#todo-input');
const list     = document.querySelector('#todo-list');

form.addEventListener('submit', e => {
  e.preventDefault(); // prevent page reload

  const text = input.value.trim();
  if (!text) return; // guard clause

  // Create the item
  const li  = document.createElement('li');
  li.className = 'todo-item';

  const span = document.createElement('span');
  span.textContent = text;

  const deleteBtn = document.createElement('button');
  deleteBtn.textContent = '✕';
  deleteBtn.addEventListener('click', () => li.remove());

  li.append(span, deleteBtn);
  list.appendChild(li);

  // Clear the input
  input.value = '';
  input.focus();
});

4. Code Example

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Creating Elements Demo</title>
  <style>
    body { font-family: system-ui, sans-serif; max-width: 600px; margin: 2rem auto; padding: 1rem; }
    .card { border: 1px solid #ddd; border-radius: 8px; padding: 1rem; margin: 0.5rem 0; display: flex; justify-content: space-between; align-items: center; }
    .del-btn { background: crimson; color: white; border: none; border-radius: 4px; padding: 0.25rem 0.5rem; cursor: pointer; }
    .controls { display: flex; gap: 0.5rem; margin: 1rem 0; }
    input { flex: 1; padding: 0.4rem; border: 1px solid #ccc; border-radius: 4px; }
  </style>
</head>
<body>
  <h1>Note Cards</h1>
  <div class="controls">
    <input type="text" id="note-input" placeholder="Add a note...">
    <button id="add-btn">Add</button>
    <button id="clear-btn">Clear All</button>
  </div>
  <div id="notes-container"></div>

  <script>
    const input     = document.querySelector('#note-input');
    const addBtn    = document.querySelector('#add-btn');
    const clearBtn  = document.querySelector('#clear-btn');
    const container = document.querySelector('#notes-container');

    let noteId = 0;

    function createNoteCard(text) {
      noteId++;
      const card = document.createElement('div');
      card.className = 'card';
      card.dataset.noteId = noteId;

      const span = document.createElement('span');
      span.textContent = text;

      const delBtn = document.createElement('button');
      delBtn.className = 'del-btn';
      delBtn.textContent = 'Delete';
      delBtn.addEventListener('click', () => {
        card.remove();
        updateCount();
      });

      card.append(span, delBtn);
      return card;
    }

    function updateCount() {
      const count = container.children.length;
      document.title = `Note Cards (${count})`;
    }

    addBtn.addEventListener('click', () => {
      const text = input.value.trim();
      if (!text) return;
      container.appendChild(createNoteCard(text));
      input.value = '';
      input.focus();
      updateCount();
    });

    // Allow Enter key to add
    input.addEventListener('keydown', e => {
      if (e.key === 'Enter') addBtn.click();
    });

    clearBtn.addEventListener('click', () => {
      container.innerHTML = '';
      updateCount();
    });

    // Seed with two initial notes
    ['Learn DOM manipulation', 'Build something cool'].forEach(text => {
      container.appendChild(createNoteCard(text));
    });
    updateCount();
  </script>
</body>
</html>

5. Code Breakdown

createNoteCard() factory function

A function that creates a complete note card DOM element — including its delete button with a pre-wired event listener — and returns it. The caller just appends the returned element. This separates the creation logic from the insertion logic, making it easy to add notes from different triggers (button click, Enter key, initial seed).

card.dataset.noteId = noteId

Each card gets a unique ID stored as a data attribute. This is useful when you later need to look up the card by ID — e.g., to sync with a server or find it in an array.

card.append(span, delBtn)

append() (plural) accepts multiple arguments and appends them all in order. It is more concise than calling appendChild twice. It also accepts plain strings (which become text nodes).

delBtn closes over card

The delete button's event handler references card from the enclosing createNoteCard function — this is a closure. Each delete button removes its own card, not someone else's, because each button closes over its own card variable.

container.innerHTML = '' to clear

Setting innerHTML to an empty string removes all children. Simple and fast for full clears. In this case it is safe because the container only holds our programmatically created cards (no external event listeners to worry about losing).

updateCount()

container.children.length is a live count of direct child elements. Updating the page title with the count is a small but professional touch that gives users real-time feedback.

6. Common Mistakes

Mistake 1 — Forgetting to insert the created element

const li = document.createElement('li');
li.textContent = 'New item';
// Bug — never appended, never appears on page!

// Fix
list.appendChild(li);

Mistake 2 — Using innerHTML when event listeners are attached

// This destroys the existing list items AND their event listeners
list.innerHTML = list.innerHTML + '<li>New item</li>';

// Better — create and append
const li = document.createElement('li');
li.textContent = 'New item';
list.appendChild(li);

Mistake 3 — Inserting a node twice

const item = document.createElement('li');
list1.appendChild(item); // item is in list1
list2.appendChild(item); // item MOVES to list2 (not cloned!)

// To insert copies, use cloneNode(true)
list2.appendChild(item.cloneNode(true));

Mistake 4 — Not using DocumentFragment for large inserts

// Bad — causes a reflow on every append (100 reflows)
for (const item of largeArray) {
  list.appendChild(createElement(item));
}

// Good — one reflow at the end
const frag = document.createDocumentFragment();
for (const item of largeArray) {
  frag.appendChild(createElement(item));
}
list.appendChild(frag);

Mistake 5 — Appending elements before the DOM is loaded

<head>
  <script>
    // Bad — #list doesn't exist yet
    document.querySelector('#list').appendChild(...);
  </script>
</head>
<!-- Fix: move script to bottom of body, or add defer -->

7. Best Practices

  1. Use factory functions to create complex elements — keeps code reusable and readable.
  2. Attach event listeners to elements before inserting them — cleaner than querying after insertion.
  3. Use DocumentFragment when inserting many elements at once — one DOM update instead of many.
  4. Prefer element.remove() over parent.removeChild(element) — shorter and more readable.
  5. Don't modify innerHTML when elements have event listeners — use createElement/appendChild instead.
  6. Clone elements with cloneNode(true) when you need identical copies.
  7. Store references to created elements if you need to modify them later — don't re-query what you just created.
  8. Use append() over appendChild() when inserting multiple children — cleaner syntax.

8. Practice Exercise

  1. Build a "Shopping List" app:
    • Text input + Add button
    • Each item added as an <li> with a checkbox and a Delete button
    • Checking the checkbox crosses out the item text
    • Delete button removes the item
    • Show item count above the list
  2. Add a "Sort A–Z" button that reads all current items, sorts them alphabetically, and re-renders them (clear + re-append using DocumentFragment).

Bonus

  • Save the list to localStorage and restore it on page load.
  • Add drag-and-drop reordering (research the draggable attribute and drag events).

9. Assignment

Build a "Dynamic Recipe Card Generator."

  1. Create a form with: recipe name, preparation time (minutes), difficulty (Easy/Medium/Hard), and a list of ingredients (add/remove dynamically).
  2. The ingredient section should have an "Add Ingredient" button that creates a new text input row (with its own Remove button).
  3. On "Generate Card", build a styled recipe card element using createElement — no innerHTML for user data.
  4. Append the card to a gallery section.
  5. Each card should have a "Delete" button that removes it from the gallery.
  6. Show a "No recipes yet" message when the gallery is empty; hide it when cards are present.

Deliverable: One HTML file. All elements created programmatically — no innerHTML for user-supplied content.

10. Interview Questions

  1. How do you create and add a new element to the DOM?
    document.createElement('tag') creates the element. Set its properties (textContent, className, etc.). Then append it with parentElement.appendChild(el) or el.insertAdjacentElement() etc.
  2. What is the difference between appendChild and append?
    appendChild accepts a single Node. append accepts multiple Nodes and/or strings (which become text nodes). append is newer and more flexible.
  3. How do you remove an element from the DOM?
    Call element.remove() directly on the element. The legacy approach is parent.removeChild(element) — but .remove() is shorter and cleaner.
  4. What is DocumentFragment and why would you use it?
    A lightweight container that lets you build a tree of nodes off-screen. When you insert the fragment, all its children are moved to the DOM in a single operation — one reflow instead of one per element. Use for inserting many elements at once for performance.
  5. What happens if you appendChild the same element twice?
    A DOM node can only exist in one place. Appending it to a second parent moves it — the original location is cleared. To insert a copy, use element.cloneNode(true) first.
  6. When would you use innerHTML vs createElement?
    innerHTML is convenient for static, developer-controlled markup. Use createElement when: the content contains user input (XSS risk with innerHTML), the elements need event listeners attached before insertion, or you need references to the created elements.

11. Additional Resources

  • MDN — Document.createElement()
  • MDN — Node.appendChild()
  • MDN — Element.remove()
  • MDN — DocumentFragment
  • javascript.info — Modifying the document — great visual examples
  • MDN — Node.cloneNode()