Command Palette

Search for a command to run...

Hectal
PHASE 8Intermediate ~26 min· topic 6 of 8

Topic 8.6

Creating, Inserting and Removing Elements

In one line

document.createElement makes a new element that lives nowhere until you insert it with append, prepend, before, after or replaceWith; remove takes it out, inserting an existing element moves it, and <template> gives you ready-made markup to clone.

Think of it like this

Building with LEGO. First you take a new brick out of the box (createElement): it exists in your hand, but it isn't part of the model yet. You decide its colour and stickers (set its text, classes and attributes). Then you click it onto the model at a chosen spot: on top, at the bottom, next to another brick (append, prepend, before, after). You can pull a brick off (remove), swap one brick for another (replaceWith), or move a brick: pulling it from one place and clicking it somewhere else. One brick can't be in two places at once.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

createElement
Makes a new, empty element of the tag you name, not yet on the page.
Detached
An element that exists in memory but isn't inserted in the document, so it isn't shown.
append / prepend
Insert nodes or text at the end / at the start inside an element.
before / after
Insert nodes or text next to an element, outside it, as siblings.
replaceWith
Take an element out and put other nodes in its place.
remove
Take an element out of the document.
cloneNode
Make a copy of a node; cloneNode(true) copies everything inside it too.
template
An HTML element whose contents are stored but not shown, used as a stamp to make copies.
DocumentFragment
A light container for nodes that isn't part of the page; inserting it inserts its children.

Step by step

01Create and set up

Make the element and give it everything it needs before it goes on the page.

app.jswhole filejavascript
const li = document.createElement("li");
li.textContent = "Buy milk";
li.classList.add("task");
li.dataset.id = "1";
console.log(li.isConnected); // false: not in the document yet

02Insert at the right spot

Choose the method by where the new node goes relative to an element you already have.

Insert at the right spotdiagram
Rendering diagram…

03Insert many at once

append takes several items. Mixing elements and strings is fine; strings become text.

app.jswhole filejavascript
const p = document.createElement("p");
const strong = document.createElement("strong");
strong.textContent = "Asha";
p.append(strong, " joined the class.");
document.body.append(p);

04Move, copy, replace, remove

Appending an existing element moves it. Clone it if you want two. replaceWith swaps; remove deletes.

app.jswhole filejavascript
done.append(task);                  // moves task from the todo list to the done list
const copy = card.cloneNode(true);  // a second card
copy.id = "card-2";
oldBanner.replaceWith(newBanner);
spinner.remove();

05Stamp out copies from a template

Keep the markup in HTML, clone it for each item, fill it with textContent, and append. No HTML strings in your JavaScript, so no XSS risk.

index.htmlwhole filehtml
<template id="row-tpl">
  <tr><td class="name"></td><td class="score"></td></tr>
</template>
<table><tbody id="rows"></tbody></table>

Try it yourself

  1. 1

    Copy instead of move

    In the first example, change the last list.append(first) to list.append(first.cloneNode(true)). Predict the final list, then run.

  2. 2

    Try all four positions

    In the second example, call box.insertAdjacentHTML with "beforebegin", "afterbegin" and "afterend" too. Predict where each paragraph lands, then check in the preview.

  3. 3

    Add a product

    In the third example, add { name: "Desk", price: 2499 } to the array and give each card a data-name with the product name. Log the dataset.name of every card.

Code & diagrams

append, prepend, before, after, and moving New tab

`append` returns `undefined`. The string passed to `after` became a text node. Appending `first` again moved it to the end; it didn't make a copy.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

false
true
undefined
[ 'First', 'Second', 'Third' ]
<h3> #text 'Total: 3 items'
[ 'Second', 'Third', 'First' ]
remove, replaceWith, cloneNode and insertAdjacentHTML New tab

A removed element still exists while a variable holds it, so `replaceChildren(gone)` could put it back, after emptying the box in the same call.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

false Delete me
[ 'new', 'copy', 'adj' ]
null
1 <p#gone>
Cards from a <template> New tab

The template's card isn't in the document, so `querySelectorAll(".card")` found 0 at first. Each clone was filled and appended; the template itself stayed empty for the next use. `<Deluxe>` is shown as text because it went in through `textContent`.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

#document-fragment 0
2
Chair <Deluxe>
true

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Create an element but never insert it

Write const li = document.createElement("li"); li.textContent = "New"; and nothing else.

terminal
$ the page shows
── what you'll see ──
Nothing new appears, and there is no error.

Break #2

Append the same element to several lists

Make one badge element and call cardA.append(badge); cardB.append(badge);.

terminal
$ the page shows
── what you'll see ──
Only card B shows the badge; card A's badge disappeared.

Break #3

Append the template itself

Write list.append(document.getElementById("row-tpl")); instead of cloning its content.

terminal
$ the page shows
── what you'll see ──
Nothing appears. In DevTools, the <template> element has moved inside the list, still hidden.

Myth vs fact

Myth

append and appendChild are the same.

Fact

append accepts several nodes and strings and returns undefined; appendChild accepts exactly one node (no strings) and returns it. Both add at the end.

Myth

cloneNode(true) makes a perfect copy, including behaviour.

Fact

It copies the element, attributes and descendants, but not listeners added with addEventListener, not the current value of properties that aren't attributes (for most inputs), and it duplicates ids.

Myth

A removed element is deleted immediately.

Fact

remove() detaches it. It lives on while any variable or listener refers to it, and can be inserted again. Holding references to many removed elements is a common memory leak.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Strings given to append, prepend, before, after, replaceWith and replaceChildren are inserted as text nodes, so they are XSS-safe sinks. Only the ...HTML methods and properties parse markup.

  • ▸

    When several nodes are passed to append (or a fragment is inserted), the DOM inserts them in one operation: one mutation record, and one style invalidation. The rendering engine still won't lay out until the next frame or until you read layout (Topic 8.8).

  • ▸

    Moving a node with append is really remove-then-insert: an iframe inside it reloads, a playing video can restart, CSS animations restart and focus is lost. Chromium added parent.moveBefore(node, ref) to move nodes while keeping that state; check support before using it.

  • ▸

    document.importNode(tpl.content, true) is similar to cloneNode(true) but adopts the nodes into the current document, which matters for templates from another document (for example, from DOMParser). For a template in the same page, cloneNode(true) is enough.

Remember this

  1. 1

    document.createElement("li") creates a new element object. At first it is detached: it is not in the document, so it isn't visible and document.querySelector can't find it. Set it up while it's detached: li.textContent = "Milk", li.className = "item", li.dataset.id = "7". document.createTextNode("text") makes a text node, though you rarely need it because the insert methods accept strings.

  2. 2

    The modern insert methods are called on an element and accept any number of nodes or strings (strings become text nodes, never HTML, so they are safe for user data). parent.append(a, b) adds at the end inside the parent; parent.prepend(a) adds at the start inside. el.before(a) and el.after(a) add outside, as siblings before or after el. el.replaceWith(a) swaps el out. parent.replaceChildren(a, b) removes all children and inserts the new ones (with no arguments, it empties the parent).

  3. 3

    The older methods are still everywhere in existing code: parent.appendChild(node) (one node only, no strings, and it returns the node), parent.insertBefore(newNode, referenceNode), parent.removeChild(child) and parent.replaceChild(newChild, oldChild). They do the same jobs with a clumsier shape. append returns undefined, which surprises people who chain from appendChild.

  4. 4

    A node can only be in one place. If you insert an element that's already in the document, it is moved, not copied. To copy, call el.cloneNode(true): true means a deep copy with all descendants; false copies only the element itself. Clones copy attributes but not event listeners added with addEventListener, and the copy keeps the same id, which you must change to keep ids unique.

  5. 5

    el.remove() takes an element out of the document. The object still exists while a variable points to it, so you can insert it again later; once nothing refers to it, the garbage collector frees it. insertAdjacentHTML(position, html) and insertAdjacentElement(position, el) insert at one of four positions: "beforebegin" (before the element), "afterbegin" (inside, first), "beforeend" (inside, last), "afterend" (after the element). insertAdjacentHTML parses HTML, so it follows the same XSS rules as innerHTML (Topic 8.3).

  6. 6

    The <template> element holds HTML that the browser parses but doesn't show or run: images don't load, scripts don't run, and its contents aren't in the document tree. template.content is a DocumentFragment (a lightweight container that is not part of the page; Topic 8.8 uses it for speed). To use it, clone the content (template.content.cloneNode(true)), fill in the clone with querySelector and textContent, and append it. This keeps your markup in HTML, where it's easy to read, and fills it with data safely.

Explain it without notes

01

Walk through creating a list item with text and a class and adding it as the first item of a list.

02

What happens when you append an element that is already in the document? How do you get a copy instead?

03

What is the <template> element for, and how do you use it?

04

Compare append with appendChild, and remove with removeChild.

Practice

01

Write addTask(text) that appends an <li class="task"> with the given text to #tasks and returns the new element. Add two tasks and log the list's texts.

02

Write moveToDone(li) that moves a task from #todo to the start of #done, and log both lists' lengths afterwards.

03

Using a <template> with a <tr>, render the rows [["Asha", 91], ["Ravi", 78]] into a <tbody> and log the number of rows.

Trade-offs

  • ↔

    createElement code is verbose but safe and gives you references to every element; <template> keeps markup readable in HTML; insertAdjacentHTML with your own fixed strings is shortest but must never receive untrusted data.

  • ↔

    Rebuilding a whole list with replaceChildren is simple and always correct; updating only the changed items is faster for long lists and keeps focus and scroll, but needs more code (this is what frameworks automate).

Done when you can

  • I can create an element, set its text, classes and data, and insert it.

  • I can choose between append, prepend, before, after, replaceWith and replaceChildren.

  • I know inserting an existing element moves it, and I can copy with cloneNode(true).

  • I can remove elements and empty containers.

  • I can render repeated markup from a <template>.

  • I can read older code that uses appendChild, insertBefore and removeChild.