đ¨ Lesson 16: Creating & Removing Elements
You can select elements, modify them, and listen for events. Now it's time to build the DOM itself â creating elements from scratch, inserting them exactly where you want, and removing them when they're no longer needed.
đ¯ Learning Objectives
By the end of this lesson, you will be able to:
- Create new HTML elements with
document.createElement - Add text, classes, attributes, and children to new elements
- Insert elements into the DOM with
append,prepend,before, andafter - Remove elements with
remove()andreplaceWith() - Clone elements with
cloneNode() - Understand the performance trade-offs of
innerHTMLvs.createElement - Use
DocumentFragmentfor efficient batch insertions
Estimated Time: 50 minutes
Project: Build a dynamic notification system
đ In This Lesson
Creating Elements
The document.createElement method creates a new HTML element in memory. At this point the element exists only in JavaScript â it's not visible on the page until you insert it into the DOM.
// Create a new paragraph element
const paragraph = document.createElement("p");
// It exists in memory but is NOT on the page yet
console.log(paragraph); // <p></p>
Building Up an Element
After creating an element, configure it â add text, classes, attributes, styles â before inserting it into the page.
// Create a card element step by step
const card = document.createElement("div");
card.classList.add("card", "featured");
card.id = "new-card";
card.dataset.category = "news";
// Add text content
const title = document.createElement("h3");
title.textContent = "Breaking News";
const body = document.createElement("p");
body.textContent = "Something exciting just happened!";
// Build the tree: add children to the card
card.appendChild(title);
card.appendChild(body);
// card is now:
// <div class="card featured" id="new-card" data-category="news">
// <h3>Breaking News</h3>
// <p>Something exciting just happened!</p>
// </div>
Helper Function Pattern
When you create elements frequently, a small helper keeps your code clean and readable.
function createEl(tag, { text, classes, attrs } = {}) {
const el = document.createElement(tag);
if (text) el.textContent = text;
if (classes) el.classList.add(...classes);
if (attrs) {
for (const [key, val] of Object.entries(attrs)) {
el.setAttribute(key, val);
}
}
return el;
}
// Usage â much cleaner!
const heading = createEl("h2", { text: "Hello!", classes: ["title", "primary"] });
const link = createEl("a", { text: "Visit", attrs: { href: "https://example.com", target: "_blank" } });
(text, classes,
attributes)"] B --> C["Add Children
(append, prepend)"] C --> D["Insert into DOM
(now visible!)"] style A fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e293b style B fill:#fef3c7,stroke:#f59e0b,stroke-width:2px,color:#1e293b style C fill:#ecfdf5,stroke:#22c55e,stroke-width:2px,color:#1e293b style D fill:#fce7f3,stroke:#ec4899,stroke-width:2px,color:#1e293b
Inserting Elements into the DOM
Once you've created and configured an element, you need to put it somewhere on the page. There are several insertion methods, each placing the element in a different position relative to a reference element.
append() & prepend()
These add elements as children of the target â at the end or the beginning.
const list = document.querySelector("#my-list");
const newItem = document.createElement("li");
newItem.textContent = "New item";
// Add as the LAST child
list.append(newItem);
// Add as the FIRST child
const firstItem = document.createElement("li");
firstItem.textContent = "I'm first now!";
list.prepend(firstItem);
// append and prepend can also accept plain strings
list.append("Some text at the end");
before() & after()
These insert elements as siblings â directly before or after the reference element.
const middleItem = document.querySelector("#item-3");
const beforeItem = document.createElement("li");
beforeItem.textContent = "Inserted before item 3";
middleItem.before(beforeItem);
const afterItem = document.createElement("li");
afterItem.textContent = "Inserted after item 3";
middleItem.after(afterItem);
Inserting Multiple Elements at Once
append, prepend, before, and after all accept multiple arguments â elements and strings mixed together.
const container = document.querySelector(".container");
const heading = document.createElement("h2");
heading.textContent = "New Section";
const paragraph = document.createElement("p");
paragraph.textContent = "Here's the content.";
// Insert both at once
container.append(heading, paragraph);
| Method | Inserts Where | Relationship |
|---|---|---|
parent.append(el) | End of parent | As last child |
parent.prepend(el) | Start of parent | As first child |
sibling.before(el) | Before the sibling | As previous sibling |
sibling.after(el) | After the sibling | As next sibling |
parent.appendChild(el) | End of parent | As last child (older API) |
đĄ append() vs. appendChild()
appendChild is the older method â it only takes one element at a time and doesn't accept strings. append is newer, more flexible, and can take multiple elements and strings. Prefer append in new code.
// appendChild â older, still common
list.appendChild(newItem); // One element only
// append â modern, more flexible
list.append(item1, item2, "text"); // Multiple items + strings
Moving Elements
A DOM element can only exist in one place at a time. If you append an element that's already in the DOM, it moves â it doesn't create a copy.
const sidebar = document.querySelector(".sidebar");
const main = document.querySelector(".main");
// This heading is currently in the sidebar
const heading = sidebar.querySelector("h2");
// Appending it to main MOVES it â it's removed from the sidebar
main.append(heading);
Removing & Replacing Elements
remove() â The Modern Way
The simplest way to remove an element â just call remove() on it.
const card = document.querySelector(".old-card");
card.remove(); // Gone from the DOM
// Common pattern: remove after a click
const deleteBtn = document.querySelector(".delete-btn");
deleteBtn.addEventListener("click", () => {
deleteBtn.closest(".card").remove();
});
replaceWith() â Swap One Element for Another
const oldHeading = document.querySelector("h2");
const newHeading = document.createElement("h2");
newHeading.textContent = "Updated Title";
newHeading.classList.add("highlight");
// Replace the old heading with the new one
oldHeading.replaceWith(newHeading);
Removing All Children
Need to clear a container? There are a few approaches.
const list = document.querySelector("#task-list");
// Option 1: Set innerHTML to empty (simple but slower)
list.innerHTML = "";
// Option 2: replaceChildren with no arguments (modern, clean)
list.replaceChildren();
// Option 3: Loop and remove (verbose but explicit)
while (list.firstChild) {
list.firstChild.remove();
}
â ī¸ The Older removeChild() Pattern
Before remove() existed, you had to go through the parent element. You'll still see this in older code and tutorials.
// Old way â requires a reference to the parent
const parent = child.parentNode;
parent.removeChild(child);
// Modern way â much simpler
child.remove();
Cloning Elements
Since appending an element moves it, you need cloneNode() if you want a copy.
const original = document.querySelector(".card-template");
// Shallow clone â copies only the element itself, not its children
const shallowCopy = original.cloneNode(false);
console.log(shallowCopy.innerHTML); // "" (empty)
// Deep clone â copies the element AND all its descendants
const deepCopy = original.cloneNode(true);
console.log(deepCopy.innerHTML); // Full content copied
Practical: Card from a Template
// HTML: a hidden template card
// <div class="card-template" style="display:none">
// <h3 class="card-title"></h3>
// <p class="card-body"></p>
// </div>
function createCard(title, body) {
const template = document.querySelector(".card-template");
const card = template.cloneNode(true); // Deep clone
// Customize the clone
card.classList.remove("card-template");
card.classList.add("card");
card.style.display = ""; // Make it visible
card.querySelector(".card-title").textContent = title;
card.querySelector(".card-body").textContent = body;
return card;
}
const newCard = createCard("Welcome!", "This card was cloned from a template.");
document.querySelector(".card-grid").append(newCard);
đĄ HTML <template> Element
HTML has a dedicated <template> element for exactly this purpose. Its content isn't rendered on the page and won't have styles applied until you clone and insert it.
<template id="card-template">
<div class="card">
<h3 class="card-title"></h3>
<p class="card-body"></p>
</div>
</template>
const template = document.querySelector("#card-template");
// .content gives you the DocumentFragment inside the template
const clone = template.content.cloneNode(true);
clone.querySelector(".card-title").textContent = "Hello!";
document.querySelector(".card-grid").append(clone);
innerHTML vs. createElement
You've seen two ways to add content: building elements with createElement or writing HTML strings with innerHTML. Each has trade-offs.
innerHTML â Quick but Risky
const list = document.querySelector("#task-list");
// Quick and easy for static content you control
list.innerHTML = `
<li class="task">Buy groceries</li>
<li class="task">Walk the dog</li>
<li class="task">Write code</li>
`;
// â ī¸ Replaces ALL existing content â previous children are destroyed
// â ī¸ Event listeners on old children are lost
// â DANGEROUS with user input (XSS vulnerability)
createElement â Safe and Precise
// More code, but safe and precise
function addTask(text) {
const li = document.createElement("li");
li.classList.add("task");
li.textContent = text; // textContent is XSS-safe
document.querySelector("#task-list").append(li);
// Existing children and their event listeners are preserved
}
addTask("Buy groceries");
addTask("Walk the dog");
addTask("Write code");
| Factor | innerHTML | createElement |
|---|---|---|
| Speed to write | Fast â just a string | More code |
| Safety with user input | Dangerous (XSS risk) | Safe (textContent) |
| Existing content | Destroyed & replaced | Preserved |
| Event listeners | Lost on old elements | Preserved on siblings |
| Performance (many items) | One reflow | One reflow per append* |
| Best for | Static HTML you control | Dynamic, interactive content |
* Use DocumentFragment (next section) to batch createElement calls into one reflow.
â Rules of Thumb
- Use
createElement+textContentwhen content includes any user input - Use
innerHTMLonly for static HTML you fully control (templates, initial markup) - Never concatenate user input into an
innerHTMLstring - If you need
innerHTML's convenience with safety, consider the<template>+cloneNodepattern
DocumentFragment & Batch Inserts
Every time you insert an element into the DOM, the browser may recalculate layout (a "reflow"). If you're adding 100 items one at a time, that's potentially 100 reflows. A DocumentFragment lets you build a batch of elements off-screen and insert them all at once â one reflow.
// â Slow: 100 individual DOM insertions
const list = document.querySelector("#my-list");
for (let i = 0; i < 100; i++) {
const li = document.createElement("li");
li.textContent = `Item ${i + 1}`;
list.append(li); // Reflow on each append
}
// â
Fast: Build in a fragment, insert once
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
const li = document.createElement("li");
li.textContent = `Item ${i + 1}`;
fragment.append(li); // No reflow â fragment is off-screen
}
list.append(fragment); // One reflow for all 100 items
How Fragments Work
A DocumentFragment is a lightweight container that holds DOM nodes but isn't part of the visible page. When you append a fragment to the DOM, the fragment itself disappears â only its children are inserted.
const fragment = document.createDocumentFragment();
const h2 = document.createElement("h2");
h2.textContent = "Section Title";
const p = document.createElement("p");
p.textContent = "Section content goes here.";
fragment.append(h2, p);
// Inserting the fragment adds h2 and p (not the fragment wrapper)
document.querySelector("main").append(fragment);
// fragment is now empty â its children moved to the DOM
console.log(fragment.childNodes.length); // 0
in memory"] B --> C["Append elements
to fragment"] C --> D["Append fragment
to DOM"] D --> E["One reflow
All visible!"] style A fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e293b style B fill:#fef3c7,stroke:#f59e0b,stroke-width:2px,color:#1e293b style C fill:#ecfdf5,stroke:#22c55e,stroke-width:2px,color:#1e293b style D fill:#fce7f3,stroke:#ec4899,stroke-width:2px,color:#1e293b style E fill:#f3e8ff,stroke:#a855f7,stroke-width:2px,color:#1e293b
đĄ When Do You Need Fragments?
For a handful of elements (5â10), the performance difference is negligible. Use fragments when you're inserting dozens or hundreds of elements, rendering data from an API, or building complex UI sections. If you're adding one or two elements, just append them directly.
Hands-on Exercise
đī¸ Exercise: Dynamic Notification System
Objective: Build a notification system that creates toast-style alerts, auto-dismisses them after a timer, and lets users close them manually. This combines createElement, event delegation, insertion, removal, and timers.
<!-- Save this as notifications.html and open in a browser -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Notification System</title>
<style>
body { font-family: sans-serif; padding: 2rem; background: #f1f5f9; }
.controls { display: flex; gap: 0.5rem; margin-bottom: 2rem; flex-wrap: wrap; }
.controls button {
padding: 0.5rem 1rem; border: none; border-radius: 6px;
color: white; cursor: pointer; font-size: 0.9rem;
}
.btn-success { background: #22c55e; }
.btn-warning { background: #f59e0b; }
.btn-error { background: #ef4444; }
.btn-info { background: #3b82f6; }
.btn-clear { background: #64748b; }
#notification-area {
position: fixed; top: 1rem; right: 1rem;
width: 320px; display: flex; flex-direction: column; gap: 0.5rem;
z-index: 1000;
}
.notification {
padding: 1rem; border-radius: 8px; color: white;
display: flex; justify-content: space-between; align-items: start;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
animation: slideIn 0.3s ease;
}
.notification.success { background: #22c55e; }
.notification.warning { background: #f59e0b; color: #1e293b; }
.notification.error { background: #ef4444; }
.notification.info { background: #3b82f6; }
.notification .close-btn {
background: none; border: none; color: inherit;
font-size: 1.2rem; cursor: pointer; padding: 0; margin-left: 0.5rem;
opacity: 0.8;
}
.notification .close-btn:hover { opacity: 1; }
.notification-text { flex: 1; }
.counter { color: #64748b; }
@keyframes slideIn {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
</style>
</head>
<body>
<h1>đ Notification System</h1>
<div class="controls">
<button class="btn-success" data-type="success">â
Success</button>
<button class="btn-warning" data-type="warning">â ī¸ Warning</button>
<button class="btn-error" data-type="error">â Error</button>
<button class="btn-info" data-type="info">âšī¸ Info</button>
<button class="btn-clear">đī¸ Clear All</button>
</div>
<p class="counter">Notifications: <span id="count">0</span></p>
<div id="notification-area"></div>
<script>
// TODO: Complete each task
// 1. Write a showNotification(type, message) function:
// - Create a <div class="notification {type}">
// - Inside it, create a <span class="notification-text"> with the message
// - Create a <button class="close-btn"> with "Ã" as text
// - Append text span and close button to the notification div
// - Prepend the notification to #notification-area
// - Auto-remove after 5 seconds using setTimeout
// - Update the count
// 2. Use event delegation on .controls to handle button clicks:
// - If the button has a data-type attribute, call showNotification
// with that type and an appropriate message
// - If the "Clear All" button is clicked, remove all notifications
// 3. Use event delegation on #notification-area to handle close clicks:
// - If a .close-btn is clicked, remove its parent .notification
// - Update the count
// 4. Write an updateCount() function that counts .notification
// elements and updates #count
// 5. Bonus: Use a DocumentFragment to add 5 notifications at once
// when a keyboard shortcut (e.g., Ctrl+Shift+N) is pressed
</script>
</body>
</html>
đĄ Hint
1: Use document.createElement("div"), add classes with classList.add("notification", type), and create child elements for the text and button. Use prepend to add new notifications at the top.
2: Use event.target.dataset.type to get the notification type from the clicked button. For "Clear All," use replaceChildren() on the notification area.
3: Use event.target.closest(".notification").remove() when a close button is clicked.
4: Count with notificationArea.querySelectorAll(".notification").length.
5: Create a DocumentFragment, loop 5 times to create notification divs, append them to the fragment, then prepend the fragment to the notification area.
â Solution
const notificationArea = document.querySelector("#notification-area");
const controls = document.querySelector(".controls");
const countDisplay = document.querySelector("#count");
const messages = {
success: "Operation completed successfully!",
warning: "Please check your input.",
error: "Something went wrong. Try again.",
info: "Here's some useful information."
};
// 4. Update the notification count
function updateCount() {
countDisplay.textContent =
notificationArea.querySelectorAll(".notification").length;
}
// 1. Show a notification
function showNotification(type, message) {
const notification = document.createElement("div");
notification.classList.add("notification", type);
const text = document.createElement("span");
text.classList.add("notification-text");
text.textContent = message;
const closeBtn = document.createElement("button");
closeBtn.classList.add("close-btn");
closeBtn.textContent = "Ã";
notification.append(text, closeBtn);
notificationArea.prepend(notification);
updateCount();
// Auto-remove after 5 seconds
setTimeout(() => {
notification.remove();
updateCount();
}, 5000);
}
// 2. Event delegation on controls
controls.addEventListener("click", (event) => {
const btn = event.target.closest("button");
if (!btn) return;
const type = btn.dataset.type;
if (type) {
showNotification(type, messages[type]);
} else if (btn.classList.contains("btn-clear")) {
notificationArea.replaceChildren();
updateCount();
}
});
// 3. Event delegation for close buttons
notificationArea.addEventListener("click", (event) => {
if (event.target.matches(".close-btn")) {
event.target.closest(".notification").remove();
updateCount();
}
});
// 5. Bonus: Batch notifications with Ctrl+Shift+N
document.addEventListener("keydown", (event) => {
if (event.ctrlKey && event.shiftKey && event.key === "N") {
event.preventDefault();
const types = ["success", "warning", "error", "info", "success"];
const fragment = document.createDocumentFragment();
types.forEach(type => {
const notification = document.createElement("div");
notification.classList.add("notification", type);
const text = document.createElement("span");
text.classList.add("notification-text");
text.textContent = messages[type] || "Batch notification";
const closeBtn = document.createElement("button");
closeBtn.classList.add("close-btn");
closeBtn.textContent = "Ã";
notification.append(text, closeBtn);
fragment.append(notification);
// Auto-remove each after 5 seconds
setTimeout(() => {
notification.remove();
updateCount();
}, 5000);
});
notificationArea.prepend(fragment);
updateCount();
}
});
đ¯ Quick Quiz
Question 1: What does document.createElement("div") return?
Question 2: What happens if you append an element that's already in the DOM?
Question 3: What is the modern way to remove an element from the DOM?
Question 4: What does cloneNode(true) do compared to cloneNode(false)?
Question 5: Why use a DocumentFragment when adding many elements?
Summary
đ Key Takeaways
document.createElement(tag)creates an element in memory â configure it before insertingappendandprependadd children;beforeandafteradd siblings- Appending an existing DOM element moves it â use
cloneNode(true)for a copy remove()deletes an element;replaceWith()swaps it for another- Clear all children with
replaceChildren()orinnerHTML = "" - Use
createElement+textContentfor user-provided content (safe from XSS) - Use
innerHTMLonly for static HTML you fully control DocumentFragmentbatches multiple insertions into a single DOM update for better performance- The
<template>element is the HTML-native way to define reusable element structures
đ Module 4 Complete!
You've finished the entire DOM module! You now know how to:
- Lesson 13: Understand what the DOM is and how the browser builds it
- Lesson 14: Select elements and modify their text, classes, styles, and attributes
- Lesson 15: Respond to user interactions with events and event delegation
- Lesson 16: Create, insert, clone, and remove elements dynamically
These four skills are the foundation of everything interactive on the web. The next module puts them all together to build real features.
đ Additional Resources
- MDN â createElement()
- MDN â append()
- MDN â cloneNode()
- MDN â DocumentFragment
- MDN â <template> Element
- javascript.info â Modifying the Document
đ What's Next?
Module 5 is where everything comes together. You'll build real interactive features â forms with client-side validation, timers and animations, persistent data with Local Storage, and fetching live data from APIs. First up: Forms & Validation.