JavaScript createElement, append, prepend and remove Explained
Create, insert, move, clone, replace and remove DOM elements with createElement, append, prepend, appendChild, remove, removeChild and replaceChild.
What you will learn
Until now you have found and changed elements that already existed in your HTML. Real websites also build new content on the fly: a new to-do item, a chat message, a notification, a row from an API. In this lesson you will learn how to create elements with createElement, put them on the page with append, prepend, appendChild, before, after and insertBefore, copy them with cloneNode, swap them with replaceChild, and delete them with remove and removeChild. You will also learn how to add many items quickly and safely.
The idea in three steps
Creating an element always follows the same three steps. Think of it as building a LEGO piece:
- Create the element (it exists only in memory, not on the page yet).
- Fill it: set its text, classes and attributes.
- Insert it into the page. Only now will the user see it.
const li = document.createElement("li"); // 1. create
li.textContent = "Item 4"; // 2. fill
document.getElementById("list").append(li); // 3. insert
A very common beginner bug is to forget step 3. The element is created, but you never see it.
The sample page
All examples use this page, also loaded in Practice in Editor:
<h1>Creating Elements</h1>
<ul id="list">
<li>Item 1</li>
<li id="second">Item 2</li>
<li>Item 3</li>
</ul>
<div id="container"></div>
.card {
border: 1px solid #cbd5e1;
border-radius: 8px;
padding: 12px;
margin: 8px 0;
max-width: 260px;
}
.highlight {
background: #fef9c3;
}
Start with:
const list = document.getElementById("list");
const container = document.getElementById("container");
Creating nodes
document.createElement(tagName)
Makes a new element. The tag name is written without angle brackets.
const item = document.createElement("li");
const heading = document.createElement("h3");
const button = document.createElement("button");
console.log(item.tagName); // "LI"
console.log(item.isConnected); // false (it is not on the page yet)
Filling the new element
Use everything you learned in the previous lessons:
const link = document.createElement("a");
link.textContent = "Visit MyCodeNest"; // text (safe)
link.setAttribute("href", "https://mycodenest.in"); // attribute
link.classList.add("btn", "btn-primary"); // classes
link.dataset.source = "lesson"; // data-source="lesson"
link.style.fontWeight = "bold"; // inline style
Many standard attributes also work as properties: link.href = "...", img.src = "...", img.alt = "...", input.type = "email", input.placeholder = "Email".
document.createTextNode(text)
Creates a plain text node. You need it less often, because textContent and append("text") cover most cases.
const text = document.createTextNode("Hello ");
const strong = document.createElement("strong");
strong.textContent = "World";
const p = document.createElement("p");
p.append(text, strong); // <p>Hello <strong>World</strong></p>
Inserting nodes
append(): add at the end
append puts one or more things at the end of an element. It accepts elements and strings, and many at once. A string becomes a text node (it is never read as HTML, so it is safe).
const item4 = document.createElement("li");
item4.textContent = "Item 4";
list.append(item4); // an element
list.append("Just some text"); // a string becomes text
list.append(item4, "text", document.createElement("hr")); // several at once
prepend(): add at the start
Same as append, but the new things go to the beginning.
const item0 = document.createElement("li");
item0.textContent = "Item 0";
list.prepend(item0); // Item 0 is now the first child
appendChild(): the older way
appendChild is the classic method. It does the same job as append for one element, with two differences:
append() |
appendChild() |
|
|---|---|---|
| Accepts strings? | Yes | No (nodes only) |
| How many things? | Many | Exactly one |
| Returns | Nothing | The node you added |
const li = document.createElement("li");
const added = list.appendChild(li);
console.log(added === li); // true
You will see appendChild everywhere in older code, so know it. In new code, append is usually more convenient.
before() and after(): insert next to an element
These two work from the point of view of an existing element, so you do not need its parent.
const second = document.getElementById("second");
const above = document.createElement("li");
above.textContent = "Before Item 2";
const below = document.createElement("li");
below.textContent = "After Item 2";
second.before(above); // new sibling just above
second.after(below); // new sibling just below
insertBefore(): the older way
parent.insertBefore(newNode, referenceNode) puts newNode just before referenceNode, which must be a child of parent.
list.insertBefore(above, second);
// If the reference is null, the node goes at the end (like appendChild)
list.insertBefore(below, null);
insertAdjacentElement() and insertAdjacentText()
These are useful when you want to name the position exactly. The four positions are the same as insertAdjacentHTML from the last lesson:
<!-- beforebegin -->
<ul id="list">
<!-- afterbegin -->
...children...
<!-- beforeend -->
</ul>
<!-- afterend -->
list.insertAdjacentElement("beforeend", item4); // same as append
list.insertAdjacentElement("afterend", document.createElement("hr")); // after the whole list
Which insert method should I use?
| I want to add… | Use |
|---|---|
| At the end of a parent | parent.append(el) |
| At the start of a parent | parent.prepend(el) |
| Just before an element | el.before(newEl) |
| Just after an element | el.after(newEl) |
| At an exact position (any of four) | insertAdjacentElement |
| Old code | appendChild, insertBefore |
Inserting a node moves it
An element can only be in one place. If you insert an element that is already on the page, it is moved, not copied:
const first = list.firstElementChild;
list.append(first); // the first item jumps to the end
This is handy for reordering a list, but it surprises people who expected a copy. To make a copy, use cloneNode.
Copying: cloneNode()
const copy = card.cloneNode(true); // deep: copies the element AND everything inside
const shallow = card.cloneNode(false); // only the element itself, without children
cloneNode(true)copies attributes, classes, and all children.- It does not copy event listeners added with
addEventListener. - It copies
idtoo, so two elements may share an id. Change or remove the id on the copy.
const card = document.createElement("div");
card.className = "card";
card.innerHTML = "<h3>Wireless Mouse</h3><p>₹499</p>";
container.append(card);
const copy = card.cloneNode(true);
copy.querySelector("h3").textContent = "Keyboard";
copy.querySelector("p").textContent = "₹899";
container.append(copy);
Replacing elements
replaceWith()
Called on the element you want to remove. It puts the new one in its place.
const newItem = document.createElement("li");
newItem.textContent = "Item 2 was replaced";
document.getElementById("second").replaceWith(newItem);
replaceChild(): the older way
parent.replaceChild(newChild, oldChild). Remember the order: new first, old second. It returns the old node.
const oldItem = list.children[2];
const note = document.createElement("li");
note.textContent = "Item 3 was replaced";
const removed = list.replaceChild(note, oldItem);
console.log(removed.textContent); // "Item 3"
replaceChildren(): replace everything inside
Removes all the current children and puts the new ones in. With no arguments, it simply empties the element.
list.replaceChildren(item4, item0); // the list now has only these two
list.replaceChildren(); // the list is empty
Removing elements
remove()
Called on the element itself. This is the easiest way, and you do not need the parent.
item0.remove();
If the element is not on the page, remove() does nothing and gives no error.
removeChild(): the older way
Called on the parent, with the child as the argument. It returns the removed child.
const removed = list.removeChild(item4);
console.log(removed.textContent); // "Item 4"
It throws an error (NotFoundError) if the node is not a child of that parent. A common pattern to remove an element without knowing its parent is:
element.parentElement.removeChild(element); // the same as element.remove()
Removing many elements
document.querySelectorAll(".old-item").forEach(function (el) {
el.remove();
});
querySelectorAll gives a static list, so this loop is safe. Remember from the traversal lesson that looping over a live list like children and removing at the same time skips items. To clear a whole parent, use replaceChildren() or textContent = "".
A removed element is not destroyed
When you remove an element, it leaves the page, but it still exists in memory while you hold a variable that points to it. You can add it back later:
const removed = list.firstElementChild;
removed.remove();
console.log(removed.isConnected); // false
list.append(removed); // back on the page
console.log(removed.isConnected); // true
When nothing refers to the removed element any more, the browser cleans it up automatically (including its event listeners).
Adding many items quickly: DocumentFragment
Every time you insert into the page, the browser may have to recalculate the layout. For 5 items this is fine. For 500 items, it can be slow. A DocumentFragment is an invisible box in memory. You fill the box, then add the whole box to the page once.
const fragment = document.createDocumentFragment();
["HTML", "CSS", "JavaScript", "React"].forEach(function (name) {
const li = document.createElement("li");
li.textContent = name;
fragment.append(li); // no page update yet
});
list.append(fragment); // one insert, all four items appear
console.log(fragment.children.length); // 0 (the fragment is empty after inserting)
The fragment itself disappears. Only its children are inserted. Another good way to do the same job is to build an array and spread it:
const items = ["HTML", "CSS", "JavaScript"].map(function (name) {
const li = document.createElement("li");
li.textContent = name;
return li;
});
list.append(...items);
createElement vs innerHTML
You learned innerHTML in the last lesson. Both can build content. Here they are side by side:
// innerHTML: short, but risky with user data
list.innerHTML += `<li>${userText}</li>`;
// createElement: more lines, but safe and keeps existing items untouched
const li = document.createElement("li");
li.textContent = userText;
list.append(li);
createElement + append |
innerHTML |
|
|---|---|---|
| Safe with user text? | Yes (textContent) |
No (XSS risk) |
| Keeps old children and their listeners? | Yes | += rebuilds everything |
| Easy to attach a listener to the new item? | Yes, you have the variable | You must select it again |
| Short for big, fixed HTML? | No | Yes |
A good rule: use createElement when the content includes user or server text or needs listeners. Use innerHTML (or a <template>) for large, fixed HTML that you wrote yourself.
Using a <template>
A <template> element holds HTML that is not shown on the page. You copy it with cloneNode(true) each time you need a new card. This keeps the HTML in your HTML file, and the JavaScript tiny.
<template id="card-template">
<div class="card">
<h3 class="name"></h3>
<p class="price"></p>
</div>
</template>
const template = document.getElementById("card-template");
function makeCard(name, price) {
const card = template.content.cloneNode(true); // copy the template's content
card.querySelector(".name").textContent = name; // fill with textContent (safe)
card.querySelector(".price").textContent = "₹" + price;
return card;
}
container.append(makeCard("Mouse", 499), makeCard("Keyboard", 899));
Real use cases
1. To-do list: add and delete
This is the classic first DOM project. The delete button uses closest("li") from the traversal lesson. It also uses addEventListener and preventDefault, which you will study in the events lessons. For now, read them as: “when this happens, run this code”, and “do not reload the page”.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>To-do List</title>
<style>
body { font-family: sans-serif; max-width: 420px; margin: 20px auto; }
form { display: flex; gap: 8px; }
input { flex: 1; padding: 8px; font-size: 16px; }
button { padding: 8px 12px; cursor: pointer; }
ul { list-style: none; padding: 0; }
li { display: flex; justify-content: space-between; align-items: center; padding: 10px; border-bottom: 1px solid #e5e7eb; }
.delete { background: #dc2626; color: white; border: none; border-radius: 4px; }
.empty { color: #6b7280; text-align: center; }
</style>
</head>
<body>
<h2>My Tasks</h2>
<form id="form">
<input id="task" type="text" placeholder="Add a task" required>
<button type="submit">Add</button>
</form>
<ul id="todo"></ul>
<p class="empty" id="empty">No tasks yet.</p>
<script>
const form = document.getElementById("form");
const input = document.getElementById("task");
const todo = document.getElementById("todo");
const empty = document.getElementById("empty");
function updateEmptyMessage() {
empty.hidden = todo.children.length > 0;
}
form.addEventListener("submit", function (event) {
event.preventDefault(); // stop the page from reloading
const text = input.value.trim();
if (text === "") return;
const li = document.createElement("li");
const span = document.createElement("span");
span.textContent = text; // safe: user text via textContent
const del = document.createElement("button");
del.type = "button";
del.className = "delete";
del.textContent = "Delete";
del.addEventListener("click", function () {
li.remove(); // remove this whole item
updateEmptyMessage();
});
li.append(span, del);
todo.append(li);
input.value = "";
input.focus();
updateEmptyMessage();
});
</script>
</body>
</html>
Notice how simple the delete button is. Because we created li and del in the same function, the click handler can use li directly, with no searching needed.
2. Notification toast
Toasts are small messages that appear and disappear by themselves. The element is created, added, and removed again with setTimeout (a later lesson).
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Toast</title>
<style>
body { font-family: sans-serif; padding: 20px; }
button { padding: 10px 16px; font-size: 16px; cursor: pointer; }
#toasts { position: fixed; top: 16px; right: 16px; display: flex; flex-direction: column; gap: 8px; }
.toast { background: #111827; color: white; padding: 12px 16px; border-radius: 6px; }
.toast.success { background: #15803d; }
.toast.error { background: #b91c1c; }
</style>
</head>
<body>
<button id="save">Save</button>
<button id="fail">Fail</button>
<div id="toasts"></div>
<script>
const toasts = document.getElementById("toasts");
function showToast(message, type) {
const toast = document.createElement("div");
toast.className = "toast " + type;
toast.textContent = message;
toasts.append(toast);
setTimeout(function () {
toast.remove();
}, 3000);
}
document.getElementById("save").addEventListener("click", function () {
showToast("Saved successfully!", "success");
});
document.getElementById("fail").addEventListener("click", function () {
showToast("Something went wrong.", "error");
});
</script>
</body>
</html>
3. Show data from an API as a table
Data usually arrives as an array of objects. Loop over it, build a row for each one, and add all rows with one fragment.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Table</title>
<style>
body { font-family: sans-serif; padding: 20px; }
table { border-collapse: collapse; width: 100%; max-width: 420px; }
th, td { border: 1px solid #d1d5db; padding: 8px; text-align: left; }
th { background: #f3f4f6; }
</style>
</head>
<body>
<table>
<thead>
<tr><th>Name</th><th>Marks</th></tr>
</thead>
<tbody id="rows"></tbody>
</table>
<script>
const students = [
{ name: "Riya", marks: 82 },
{ name: "Karan", marks: 67 },
{ name: "Neha", marks: 91 }
];
const tbody = document.getElementById("rows");
const fragment = document.createDocumentFragment();
students.forEach(function (student) {
const row = document.createElement("tr");
const nameCell = document.createElement("td");
nameCell.textContent = student.name;
const marksCell = document.createElement("td");
marksCell.textContent = student.marks;
row.append(nameCell, marksCell);
fragment.append(row);
});
tbody.append(fragment);
</script>
</body>
</html>
4. Safe comments list (newest on top)
prepend is perfect for feeds where the newest item comes first. User text goes in with textContent, so even <b> or other tags are shown as plain text and not run.
const comments = document.getElementById("comments");
function addComment(author, text) {
const box = document.createElement("div");
box.className = "comment";
const name = document.createElement("strong");
name.textContent = author;
const body = document.createElement("p");
body.textContent = text;
box.append(name, body);
comments.prepend(box); // newest first
}
addComment("Riya", "Great lesson!");
addComment("Karan", "<b>Thanks</b> for sharing"); // shown as plain text
5. Clear and re-draw a list
Search results and filters usually clear the old list and draw a new one:
function showResults(items) {
results.replaceChildren(); // clear the old results
if (items.length === 0) {
const none = document.createElement("p");
none.textContent = "No results found.";
results.append(none);
return;
}
items.forEach(function (text) {
const li = document.createElement("li");
li.textContent = text;
results.append(li);
});
}
Common mistakes
- Creating but never inserting.
document.createElement("li")alone shows nothing. Add it withappend. - Expecting an insert to copy. Inserting an element that is already on the page moves it. Use
cloneNode(true)for a copy. - Passing a string to
appendChild. It accepts only nodes. Useappend("text"), orcreateTextNode. - Mixing up
replaceChildarguments. It isreplaceChild(new, old). - Using
innerHTML +=for every new item. It rebuilds everything and removes listeners. Useappend. - Putting user text in
innerHTML. UsetextContentto avoid XSS. - Using
removeChildon the wrong parent. It throwsNotFoundError. Preferelement.remove(). - Cloning and forgetting duplicate ids. Two elements with the same
idbreakgetElementById. Change or remove the id on the clone. - Expecting cloned elements to keep their click handlers.
cloneNodedoes not copy listeners. Add them again (or use event delegation, covered later). - Removing items from a live list in a loop. Use
querySelectorAll, or copy the list withArray.from()first. - Adding hundreds of items one by one. Use a
DocumentFragmentorappend(...items).
Practice
- Create an
liwith the text"Item 4"and add it to the end of the list withappendChild. Then add"Item 5"withappend. - Add
"Item 0"at the start of the list withprepend. - Use
after()to insert a highlighted item right after#second, andbefore()to insert one right before it. - Make a card (a
divwith anh3and ap) using onlycreateElement,textContentandappend. Add it to#container. - Clone the card with
cloneNode(true), change its text, and add the clone. Then trycloneNode(false)and see what is missing. - Move the last list item to the top of the list. Did you use
cloneNode? Why not? - Replace the third item with a new one using
replaceChild, and then replace another item usingreplaceWith. - Remove the first item with
remove()and the last item withremoveChild(). Which one was easier? - Build a list of 100 numbers (
Item 1toItem 100) using aDocumentFragment. - Build the to-do app. Then add a “Clear all” button that removes every task with one line of code.
- Challenge: with a
<template>, show three product cards (name, price, “Add to cart” button) from an array of objects. Make each “Add to cart” button change its own text to"Added"when clicked.
Recap
- Creating content is three steps: create (
createElement), fill (textContent, attributes, classes), and insert (append,prepend,before,after…). appendandprependaccept many elements and text strings.appendChildandinsertBeforeare the older, single-node versions.before(),after(),replaceWith()andremove()work directly on an element, with no need to find its parent.- Inserting an element that is already on the page moves it. Use
cloneNode(true)to copy (listeners are not copied, and watch out for duplicate ids). replaceChild(new, old)andremoveChild(child)are called on the parent and return the old node.replaceChildren()swaps or clears all children at once.- A removed element is not deleted from memory while you still have a variable for it, so you can add it back.
- For many items, use a
DocumentFragmentorappend(...items)so the page updates once. - Use
createElement+textContentfor user or server data. UseinnerHTMLor<template>only for HTML you wrote yourself. - Next you will learn events: how to make your page react to clicks, typing, forms and the keyboard.