What Is It?
What Is Dynamic Element Creation?
So far you have learned to select existing HTML elements and modify them. Now it is time to learn how to create brand new elements from scratch using JavaScript and add them to the page. You can also remove elements that are already there.
This is how real web apps work. When you post a comment on YouTube, the comment does not already exist in the HTML -- JavaScript creates a new element, fills it with your text, and inserts it into the page. When you delete a message, JavaScript removes that element from the DOM.
// Create a new paragraph
const p = document.createElement("p");
p.textContent = "I was created by JavaScript!";
p.classList.add("message");
// Add it to the page
document.body.appendChild(p);
// Remove it later
p.remove();With createElement, appendChild, and remove, you can build entire interfaces dynamically -- lists, cards, tables, comments, notifications -- all from JavaScript.
Why Does It Matter?
Why Learn to Create and Remove Elements?
1. Real Apps Are Dynamic
Real websites do not have all their content hardcoded in HTML. Data comes from APIs, user input, or databases. JavaScript takes that data and builds the UI on the fly. Every social media feed, e-commerce product grid, and chat application creates elements dynamically.
2. It Completes Your DOM Toolkit
You know how to select elements (Chapter 11) and respond to events (Chapter 12). Creating and removing elements is the third piece. Together, these three skills let you build any interactive feature.
3. It Teaches You About Performance
Creating elements the wrong way can make your page slow. Learning about DocumentFragment and the differences between innerHTML and createElement teaches you to think about performance -- a skill that separates good developers from great ones.
4. Security: innerHTML vs createElement
Using innerHTML with user input is dangerous (XSS attacks). createElement is safe by default because it does not parse HTML strings. Understanding this difference is critical for building secure websites.
Detailed Explanation
Detailed Explanation
1. document.createElement()
This creates a new element in memory. It does not appear on the page until you add it somewhere:
const div = document.createElement("div");
const p = document.createElement("p");
const img = document.createElement("img");
const btn = document.createElement("button");
// At this point, these elements exist in memory but are NOT visible on the pageAfter creating an element, you typically set its content, attributes, and styles before adding it to the page:
const card = document.createElement("div");
card.className = "card"; // Set class
card.id = "student-card"; // Set id
card.textContent = "Aarav's Card"; // Set text
card.style.padding = "20px"; // Set style
card.setAttribute("data-student", "1"); // Set custom attribute2. Appending Elements
There are several ways to add an element to the page:
const parent = document.getElementById("container");
const child = document.createElement("p");
child.textContent = "New paragraph";
// appendChild: adds as the last child
parent.appendChild(child);
// prepend: adds as the first child
parent.prepend(child);
// append: like appendChild but accepts strings too
parent.append(child);
parent.append("Plain text"); // Can append text directly
// insertBefore: insert before a specific element
const reference = document.getElementById("existing");
parent.insertBefore(child, reference); // child goes before reference3. Removing Elements
The modern way to remove an element is simply calling .remove() on it:
const element = document.getElementById("old-item");
element.remove(); // Gone from the page
// Older way (still works): removeChild on the parent
const parent = element.parentNode;
parent.removeChild(element);4. Replacing Elements
replaceWith() replaces an element with a new one:
const oldElement = document.getElementById("old");
const newElement = document.createElement("div");
newElement.textContent = "I replaced the old element!";
oldElement.replaceWith(newElement);
// oldElement is removed, newElement takes its place5. Building Structured Elements
You can nest createElement calls to build complex structures:
// Build a card with a title and description
const card = document.createElement("div");
card.className = "card";
const title = document.createElement("h3");
title.textContent = "Aarav's Project";
const desc = document.createElement("p");
desc.textContent = "A calculator app built with JavaScript";
card.appendChild(title);
card.appendChild(desc);
document.getElementById("container").appendChild(card);6. Creating Elements from Data Arrays
A common pattern is looping through data to create elements for each item:
const students = [
{ name: "Aarav", grade: "A" },
{ name: "Priya", grade: "B+" },
{ name: "Rohan", grade: "A+" }
];
const list = document.getElementById("student-list");
students.forEach(function(student) {
const li = document.createElement("li");
li.textContent = student.name + " - Grade: " + student.grade;
list.appendChild(li);
});7. DocumentFragment for Performance
When adding many elements, each appendChild causes the browser to re-render. A DocumentFragment lets you build a batch of elements off-screen and add them all at once:
const fragment = document.createDocumentFragment();
for (let i = 1; i <= 100; i++) {
const li = document.createElement("li");
li.textContent = "Item " + i;
fragment.appendChild(li); // Adds to fragment (off-screen)
}
document.getElementById("list").appendChild(fragment); // One DOM update!Without a fragment, the browser would re-render 100 times. With a fragment, it re-renders once. For small lists the difference is negligible, but for large lists it matters.
8. innerHTML vs createElement: Security
innerHTML is quick and easy, but it parses strings as HTML. If the string contains user input, it can execute malicious code (XSS):
// DANGEROUS with user input:
const userInput = '<img src=x onerror="alert(document.cookie)">';
container.innerHTML = userInput; // Executes the script!
// SAFE: createElement + textContent
const p = document.createElement("p");
p.textContent = userInput; // Displays as plain text, no execution
container.appendChild(p);Rule of thumb: use innerHTML only with trusted, developer-controlled content. Use createElement + textContent for anything involving user input.
9. cloneNode()
cloneNode() creates a copy of an existing element. Pass true for a deep clone (includes children):
const original = document.getElementById("template-card");
// Shallow clone (just the element, no children)
const shallow = original.cloneNode(false);
// Deep clone (element + all children)
const deep = original.cloneNode(true);
deep.id = "card-copy"; // Change the id to avoid duplicates
document.body.appendChild(deep);
Code Examples
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; padding: 20px; }
.student { padding: 10px; margin: 4px 0; background: #f3f4f6; border-radius: 6px; display: flex; justify-content: space-between; align-items: center; }
.student .name { font-weight: bold; }
.student .grade { color: #a855f7; }
.add-area { margin-bottom: 16px; }
input { padding: 8px; border: 1px solid #ddd; border-radius: 4px; margin-right: 8px; }
button { padding: 8px 16px; background: #a855f7; color: white; border: none; border-radius: 6px; cursor: pointer; }
.delete-btn { background: #ef4444; font-size: 12px; padding: 4px 10px; }
</style>
</head>
<body>
<h2>Class Roster</h2>
<div class="add-area">
<input type="text" id="nameInput" placeholder="Student name">
<input type="text" id="gradeInput" placeholder="Grade">
<button id="addBtn">Add Student</button>
</div>
<div id="roster"></div>
<script>
const roster = document.getElementById("roster");
// Initial students
const students = [
{ name: "Aarav", grade: "A" },
{ name: "Priya", grade: "A+" },
{ name: "Rohan", grade: "B+" }
];
function createStudentElement(student) {
const div = document.createElement("div");
div.className = "student";
const nameSpan = document.createElement("span");
nameSpan.className = "name";
nameSpan.textContent = student.name;
const gradeSpan = document.createElement("span");
gradeSpan.className = "grade";
gradeSpan.textContent = "Grade: " + student.grade;
const deleteBtn = document.createElement("button");
deleteBtn.className = "delete-btn";
deleteBtn.textContent = "Remove";
deleteBtn.addEventListener("click", function() {
div.remove();
});
div.appendChild(nameSpan);
div.appendChild(gradeSpan);
div.appendChild(deleteBtn);
return div;
}
// Render initial students
students.forEach(function(student) {
roster.appendChild(createStudentElement(student));
});
// Add new student
document.getElementById("addBtn").addEventListener("click", function() {
const name = document.getElementById("nameInput").value.trim();
const grade = document.getElementById("gradeInput").value.trim();
if (name && grade) {
roster.appendChild(createStudentElement({ name: name, grade: grade }));
document.getElementById("nameInput").value = "";
document.getElementById("gradeInput").value = "";
}
});
</script>
</body>
</html>createStudentElement function builds a complete student row with name, grade, and a delete button. The delete button removes the element using div.remove(). Initial students are rendered from an array, and new students can be added via the form.<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; padding: 20px; max-width: 500px; }
.comment { padding: 12px; margin: 8px 0; background: #f9fafb; border-left: 3px solid #a855f7; border-radius: 0 8px 8px 0; }
.comment .author { font-weight: bold; color: #a855f7; }
.comment .time { font-size: 12px; color: #9ca3af; }
.comment .text { margin-top: 6px; }
.comment .delete { float: right; background: none; border: none; color: #ef4444; cursor: pointer; font-size: 16px; }
textarea { width: 100%; height: 60px; padding: 10px; border: 1px solid #ddd; border-radius: 8px; resize: vertical; box-sizing: border-box; }
.post-area { margin-bottom: 16px; }
button { padding: 8px 16px; background: #a855f7; color: white; border: none; border-radius: 6px; cursor: pointer; margin-top: 8px; }
#count { color: #6b7280; margin-bottom: 10px; }
</style>
</head>
<body>
<h2>Comments</h2>
<div class="post-area">
<input type="text" id="author" placeholder="Your name" style="padding:8px;border:1px solid #ddd;border-radius:4px;margin-bottom:8px;width:200px;">
<textarea id="commentText" placeholder="Write a comment..."></textarea>
<button id="postBtn">Post Comment</button>
</div>
<div id="count">0 comments</div>
<div id="comments"></div>
<script>
const commentsContainer = document.getElementById("comments");
const countDisplay = document.getElementById("count");
let commentCount = 0;
function updateCount() {
commentCount = commentsContainer.children.length;
countDisplay.textContent = commentCount + " comment" + (commentCount !== 1 ? "s" : "");
}
function addComment(author, text) {
const comment = document.createElement("div");
comment.className = "comment";
const deleteBtn = document.createElement("button");
deleteBtn.className = "delete";
deleteBtn.textContent = "x";
deleteBtn.addEventListener("click", function() {
comment.remove();
updateCount();
});
const authorEl = document.createElement("div");
authorEl.className = "author";
authorEl.textContent = author;
const timeEl = document.createElement("div");
timeEl.className = "time";
timeEl.textContent = "Just now";
const textEl = document.createElement("div");
textEl.className = "text";
textEl.textContent = text;
comment.appendChild(deleteBtn);
comment.appendChild(authorEl);
comment.appendChild(timeEl);
comment.appendChild(textEl);
commentsContainer.prepend(comment); // Newest first
updateCount();
}
document.getElementById("postBtn").addEventListener("click", function() {
const author = document.getElementById("author").value.trim() || "Anonymous";
const text = document.getElementById("commentText").value.trim();
if (text) {
addComment(author, text);
document.getElementById("commentText").value = "";
}
});
</script>
</body>
</html>addComment function creates a complete comment element with author name, timestamp, text, and a delete button. prepend adds new comments at the top (newest first). The delete button removes its comment and updates the count. All user input is set via textContent for XSS safety.<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; padding: 20px; }
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 16px; }
.card { padding: 20px; border: 2px solid #e5e7eb; border-radius: 12px; text-align: center; transition: transform 0.2s, border-color 0.2s; }
.card:hover { transform: translateY(-4px); border-color: #a855f7; }
.card .emoji { font-size: 40px; margin-bottom: 8px; }
.card .title { font-weight: bold; font-size: 18px; margin-bottom: 4px; }
.card .desc { color: #6b7280; font-size: 14px; }
.card .level { display: inline-block; margin-top: 8px; padding: 2px 10px; border-radius: 12px; font-size: 12px; color: white; }
.beginner { background: #10b981; }
.intermediate { background: #f59e0b; }
.advanced { background: #ef4444; }
</style>
</head>
<body>
<h2>Course Catalog</h2>
<div class="grid" id="grid"></div>
<script>
const courses = [
{ title: "HTML Basics", desc: "Build your first webpage", icon: "[H]", level: "beginner" },
{ title: "CSS Layouts", desc: "Flexbox and Grid mastery", icon: "[C]", level: "beginner" },
{ title: "JavaScript DOM", desc: "Make pages interactive", icon: "[J]", level: "intermediate" },
{ title: "React Basics", desc: "Component-based UI", icon: "[R]", level: "intermediate" },
{ title: "Node.js APIs", desc: "Build backend services", icon: "[N]", level: "advanced" },
{ title: "Databases", desc: "SQL and MongoDB", icon: "[D]", level: "advanced" }
];
const grid = document.getElementById("grid");
const fragment = document.createDocumentFragment();
courses.forEach(function(course) {
const card = document.createElement("div");
card.className = "card";
const icon = document.createElement("div");
icon.className = "emoji";
icon.textContent = course.icon;
const title = document.createElement("div");
title.className = "title";
title.textContent = course.title;
const desc = document.createElement("div");
desc.className = "desc";
desc.textContent = course.desc;
const level = document.createElement("span");
level.className = "level " + course.level;
level.textContent = course.level.charAt(0).toUpperCase() + course.level.slice(1);
card.appendChild(icon);
card.appendChild(title);
card.appendChild(desc);
card.appendChild(level);
fragment.appendChild(card);
});
grid.appendChild(fragment); // One DOM update
</script>
</body>
</html>DocumentFragment to batch all cards together and add them to the DOM in a single operation for better performance.<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; padding: 20px; }
.notification { padding: 12px 16px; margin: 6px 0; border-radius: 8px; display: flex; justify-content: space-between; align-items: center; }
.notification.info { background: #dbeafe; border-left: 4px solid #3b82f6; }
.notification.success { background: #d1fae5; border-left: 4px solid #10b981; }
.notification.error { background: #fecaca; border-left: 4px solid #ef4444; }
.notification .close { background: none; border: none; font-size: 18px; cursor: pointer; color: #6b7280; }
button { padding: 8px 16px; margin: 4px; border: none; border-radius: 6px; cursor: pointer; color: white; }
#add-info { background: #3b82f6; }
#add-success { background: #10b981; }
#add-error { background: #ef4444; }
</style>
</head>
<body>
<h2>Notification System</h2>
<button id="add-info">Add Info</button>
<button id="add-success">Add Success</button>
<button id="add-error">Add Error</button>
<div id="notifications"></div>
<!-- Hidden template -->
<template id="notif-template">
<div class="notification">
<span class="message"></span>
<button class="close">x</button>
</div>
</template>
<script>
const container = document.getElementById("notifications");
const template = document.getElementById("notif-template");
let counter = 0;
function addNotification(type, text) {
counter++;
const clone = template.content.cloneNode(true);
const notif = clone.querySelector(".notification");
notif.classList.add(type);
clone.querySelector(".message").textContent = text;
clone.querySelector(".close").addEventListener("click", function() {
notif.remove();
});
container.prepend(clone);
}
document.getElementById("add-info").addEventListener("click", function() {
addNotification("info", "Info #" + counter + ": Aarav submitted homework.");
});
document.getElementById("add-success").addEventListener("click", function() {
addNotification("success", "Success #" + counter + ": Assignment graded!");
});
document.getElementById("add-error").addEventListener("click", function() {
addNotification("error", "Error #" + counter + ": Network connection lost.");
});
</script>
</body>
</html><template> element as a reusable blueprint. template.content.cloneNode(true) creates a deep copy of the template content. We then fill in the text and type class, attach a close handler, and add it to the page. The template itself is never visible.<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; padding: 20px; }
#list { list-style: none; padding: 0; }
#list li { padding: 10px; margin: 4px 0; background: #f3f4f6; border-radius: 6px; }
#list li.highlight { background: #fef3c7; border: 1px solid #f59e0b; }
button { padding: 8px 14px; margin: 4px; cursor: pointer; border: 1px solid #ddd; border-radius: 4px; background: white; }
button:hover { background: #f3f4f6; }
</style>
</head>
<body>
<h2>List Operations</h2>
<ul id="list">
<li>First item</li>
<li id="second">Second item</li>
<li>Third item</li>
</ul>
<button id="prepend-btn">Prepend Item</button>
<button id="insert-btn">Insert Before Second</button>
<button id="replace-btn">Replace Second</button>
<button id="remove-btn">Remove Last</button>
<p id="status"></p>
<script>
const list = document.getElementById("list");
const status = document.getElementById("status");
let count = 0;
document.getElementById("prepend-btn").addEventListener("click", function() {
count++;
const li = document.createElement("li");
li.textContent = "Prepended item #" + count;
li.classList.add("highlight");
list.prepend(li);
status.textContent = "Prepended item at the top.";
});
document.getElementById("insert-btn").addEventListener("click", function() {
const second = document.getElementById("second");
if (second) {
count++;
const li = document.createElement("li");
li.textContent = "Inserted item #" + count;
li.classList.add("highlight");
list.insertBefore(li, second);
status.textContent = "Inserted before 'Second item'.";
} else {
status.textContent = "Second item no longer exists.";
}
});
document.getElementById("replace-btn").addEventListener("click", function() {
const second = document.getElementById("second");
if (second) {
const replacement = document.createElement("li");
replacement.textContent = "Replaced element!";
replacement.classList.add("highlight");
replacement.id = "second"; // Keep the id for future operations
second.replaceWith(replacement);
status.textContent = "Replaced 'Second item'.";
}
});
document.getElementById("remove-btn").addEventListener("click", function() {
if (list.lastElementChild) {
list.lastElementChild.remove();
status.textContent = "Removed the last item.";
} else {
status.textContent = "List is empty.";
}
});
</script>
</body>
</html>prepend adds at the beginning. insertBefore(newEl, referenceEl) places the new element right before the reference. replaceWith swaps one element for another. lastElementChild.remove() removes the last item. All operations update a status message.<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; padding: 20px; }
.demo-box { padding: 16px; margin: 10px 0; border: 2px solid #ddd; border-radius: 8px; min-height: 40px; }
.danger { border-color: #ef4444; }
.safe { border-color: #10b981; }
label { font-weight: bold; display: block; margin-top: 12px; }
input { padding: 8px; width: 300px; border: 1px solid #ddd; border-radius: 4px; }
button { padding: 8px 16px; margin: 4px; cursor: pointer; border: none; border-radius: 6px; color: white; }
.btn-danger { background: #ef4444; }
.btn-safe { background: #10b981; }
</style>
</head>
<body>
<h2>innerHTML vs createElement Security</h2>
<label>Enter some text (try typing HTML tags):</label>
<input type="text" id="userInput" value="Hello <b>World</b>" placeholder="Try HTML tags...">
<br><br>
<button class="btn-danger" id="innerBtn">Render with innerHTML</button>
<button class="btn-safe" id="safeBtn">Render with createElement + textContent</button>
<h3>innerHTML (parses HTML -- dangerous with user input):</h3>
<div class="demo-box danger" id="innerDemo"></div>
<h3>createElement + textContent (safe -- treats as plain text):</h3>
<div class="demo-box safe" id="safeDemo"></div>
<script>
const input = document.getElementById("userInput");
document.getElementById("innerBtn").addEventListener("click", function() {
document.getElementById("innerDemo").innerHTML = input.value;
});
document.getElementById("safeBtn").addEventListener("click", function() {
const container = document.getElementById("safeDemo");
container.textContent = ""; // Clear previous
const p = document.createElement("p");
p.textContent = input.value;
container.appendChild(p);
});
</script>
</body>
</html><b>Bold</b>, innerHTML renders it as bold text (it parses the HTML). createElement + textContent displays the raw string including the tags. With malicious input like <img src=x onerror=alert(1)>, innerHTML would execute the script, while textContent is safe.Common Mistakes
Forgetting to Append the Created Element
const p = document.createElement("p");
p.textContent = "Hello World";
// Forgot to add it to the page!
// The paragraph exists in memory but is invisibleconst p = document.createElement("p");
p.textContent = "Hello World";
document.body.appendChild(p); // Now it appears on the pagecreateElement creates an element in memory. It is NOT visible until you append it to a visible parent using appendChild, prepend, or insertBefore.Using innerHTML with User Input (XSS Vulnerability)
const userComment = document.getElementById("input").value;
document.getElementById("comments").innerHTML += "<p>" + userComment + "</p>";
// If userComment is: <img src=x onerror="alert('hacked')">
// The script EXECUTES!const userComment = document.getElementById("input").value;
const p = document.createElement("p");
p.textContent = userComment; // Safe: treats as plain text
document.getElementById("comments").appendChild(p);innerHTML parses strings as HTML. User input with script tags or event handlers (like onerror) will execute. createElement + textContent is safe because it never parses HTML.Appending the Same Element Twice (Moves Instead of Copying)
const p = document.createElement("p");
p.textContent = "Hello";
document.getElementById("box1").appendChild(p);
document.getElementById("box2").appendChild(p);
// p is only in box2! It was MOVED, not copied.const p = document.createElement("p");
p.textContent = "Hello";
document.getElementById("box1").appendChild(p);
document.getElementById("box2").appendChild(p.cloneNode(true)); // Clone it!cloneNode(true) to create a copy.innerHTML += Destroys Event Listeners
const container = document.getElementById("list");
// Add a button with a listener
const btn = document.createElement("button");
btn.textContent = "Click me";
btn.addEventListener("click", function() { alert("Works!"); });
container.appendChild(btn);
// Later, add more content with innerHTML +=
container.innerHTML += "<p>New paragraph</p>";
// The button's click listener is GONE! innerHTML destroys and rebuilds ALL children.// Use createElement for the new content too
const p = document.createElement("p");
p.textContent = "New paragraph";
container.appendChild(p);
// Now the button's click listener is preservedinnerHTML += reads the current HTML as a string, appends the new string, and re-parses the entire thing. This destroys all existing DOM nodes and their event listeners. Always use appendChild or prepend to add content without destroying existing elements.Summary
- document.createElement('tag') creates a new element in memory. It does not appear on the page until you add it to a parent element.
- appendChild adds an element as the last child. prepend adds it as the first child. insertBefore(newEl, refEl) places it before a specific element.
- element.remove() removes an element from the page. The older way is parentNode.removeChild(element).
- element.replaceWith(newElement) replaces an element with a new one in place.
- After creating an element, set its content (textContent), attributes (className, id, setAttribute), and styles before appending it.
- Loop through data arrays and create elements for each item. This is the pattern used by every modern web application to render dynamic content.
- DocumentFragment lets you batch-create elements off-screen and add them all at once, reducing the number of DOM updates for better performance.
- innerHTML is convenient but dangerous with user input (XSS). Always use createElement + textContent for user-provided data.
- innerHTML += destroys existing event listeners because it rebuilds the entire inner content. Use appendChild instead.
- cloneNode(true) creates a deep copy of an element (including children). cloneNode(false) creates a shallow copy (just the element, no children).
- The HTML template element provides a reusable, invisible blueprint. Use template.content.cloneNode(true) to create instances of it.