Complete JavaScript Course Module 8: Intermediate: The DOM (Document Object Model)

Creating, Adding and Removing Elements

  • Beginner
  • Lesson 8.5
  • 5 min read
  • 36 of 76 in the course

In short

JavaScript can build new parts of a page on the fly. In this lesson you will learn how to create elements with JavaScript, add them to the DOM, and remove or replace them. How to create an element with createElement To make a new element, use document.createElement() with a tag name.

JavaScript can build new parts of a page on the fly. In this lesson you will learn how to create elements with JavaScript, add them to the DOM, and remove or replace them.

How to create an element with createElement

To make a new element, use document.createElement() with a tag name. The new element exists only in memory at first, a bit like a sticker you have printed but not yet stuck on the page. You set its text, classes and attributes, and then you place it in the DOM.

<!DOCTYPE html>
<html lang="en">
  <body>
    <ul id="shopping">
      <li>Milk</li>
    </ul>

    <script>
      const list = document.querySelector("#shopping");

      const item = document.createElement("li");
      item.textContent = "Eggs";
      item.classList.add("new");

      list.append(item);

      console.log(list.children.length);
      // Output: 2
    </script>
  </body>
</html>

createElement("li") makes an empty list item. We give it text and a class, then append puts it at the end of the list. The page now shows "Milk" and "Eggs". list.children is the collection of child elements, so its length is 2.

append, prepend, before and after

There are four modern methods for placing elements. Think of the target element as a box:

MethodWhere the new element goes
target.append(el)Inside the box, at the end
target.prepend(el)Inside the box, at the start
target.before(el)Outside the box, just before it
target.after(el)Outside the box, just after it

All four also accept plain strings, which are added as safe text, and you can pass several items at once: list.append(a, b, "text").

To take an element away, call element.remove(). To swap it for another one, call oldElement.replaceWith(newElement).

<!DOCTYPE html>
<html lang="en">
  <body>
    <h2 id="title">Today's tasks</h2>
    <ul id="tasks">
      <li id="t1">Write report</li>
      <li id="t2">Call the bank</li>
    </ul>

    <script>
      const tasks = document.querySelector("#tasks");
      const title = document.querySelector("#title");

      const urgent = document.createElement("li");
      urgent.textContent = "Pay rent (urgent)";
      tasks.prepend(urgent);

      const note = document.createElement("p");
      note.textContent = "3 tasks left";
      tasks.after(note);

      document.querySelector("#t2").remove();

      const done = document.createElement("li");
      done.textContent = "Write report - done";
      document.querySelector("#t1").replaceWith(done);

      title.before("Monday");

      const texts = Array.from(tasks.children).map((li) => li.textContent);
      console.log(texts);
    </script>
  </body>
</html>
["Pay rent (urgent)", "Write report - done"]

Step by step: "Pay rent" goes to the top of the list. A paragraph appears below the list. "Call the bank" is removed. "Write report" is replaced with a new item. Finally the plain text "Monday" appears above the heading.

Note: You may see older methods like appendChild() and insertBefore() in tutorials. They still work, but append, prepend, before and after are shorter and accept several items at once.

Building a list from an array of objects

Real apps often get data as an array of objects, for example products from a server. You loop over the array and create one element per object. Adding elements one at a time can make the browser redraw many times, so we first collect them in a DocumentFragment. A fragment is an invisible container. When you append the fragment, all its children move into the page in one step.

<!DOCTYPE html>
<html lang="en">
  <head>
    <style>
      .product { display: flex; justify-content: space-between; max-width: 300px; }
      .sold-out { color: #999; text-decoration: line-through; }
    </style>
  </head>
  <body>
    <h2>Products</h2>
    <ul id="products"></ul>

    <script>
      const products = [
        { name: "Notebook", price: 4.5, inStock: true },
        { name: "Pen set", price: 7, inStock: false },
        { name: "Backpack", price: 39.99, inStock: true },
      ];

      const list = document.querySelector("#products");
      const fragment = document.createDocumentFragment();

      for (const { name, price, inStock } of products) {
        const li = document.createElement("li");
        li.classList.add("product");
        if (!inStock) li.classList.add("sold-out");

        const nameSpan = document.createElement("span");
        nameSpan.textContent = name;

        const priceSpan = document.createElement("span");
        priceSpan.textContent = `$${price.toFixed(2)}`;

        li.append(nameSpan, priceSpan);
        fragment.append(li);
      }

      list.append(fragment);
      console.log(list.children.length);
      // Output: 3
    </script>
  </body>
</html>

You will see three products with their prices. "Pen set" is grey and crossed out because it is not in stock. We used destructuring in the for...of loop to pull out name, price and inStock, and toFixed(2) to always show two decimals.

What about template literals and innerHTML?

You might be tempted to build the list by setting innerHTML to a template literal, with something like <li>${name}</li> inside the backticks. This is short, but if name comes from a user or a server, it opens the door to XSS, as you saw in the previous lesson. If you use this style, only insert values you fully control, such as numbers you calculated. With createElement and textContent, the data is always treated as text, so it is always safe.

Tip: When in doubt, use createElement plus textContent. It is a few more lines, but you never have to worry about unsafe input.

The template element

HTML has a <template> element for markup you want to copy many times. Its content is not shown on the page. In JavaScript you clone it with template.content.cloneNode(true), fill in the text, and append the copy.

<!DOCTYPE html>
<html lang="en">
  <body>
    <template id="user-card">
      <div class="card">
        <h3 class="name"></h3>
        <p class="role"></p>
      </div>
    </template>
    <div id="team"></div>

    <script>
      const team = [
        { name: "Aisha", role: "Designer" },
        { name: "Tom", role: "Developer" },
      ];
      const template = document.querySelector("#user-card");
      const container = document.querySelector("#team");

      for (const person of team) {
        const card = template.content.cloneNode(true);
        card.querySelector(".name").textContent = person.name;
        card.querySelector(".role").textContent = person.role;
        container.append(card);
      }
      console.log(container.querySelectorAll(".card").length);
      // Output: 2
    </script>
  </body>
</html>

The page shows two cards, one for Aisha and one for Tom. The structure lives in HTML, and JavaScript only fills in the text, safely with textContent.

Practice exercise

Make an array of three blog posts, each with title, author and minutes (reading time). Build an <article> for each one with an <h2> for the title and a <p> like "By Lena - 5 min read". Add them all to a <main> element using a DocumentFragment. Then remove the second article and add a paragraph after <main> that says how many articles are left.

Hint: main.children[1].remove() removes the second child.

Key takeaways

  • document.createElement("tag") creates an element in memory; it appears only after you place it.
  • append, prepend, before and after place elements; remove and replaceWith take them away or swap them.
  • Build lists from arrays with a loop, and use a DocumentFragment to add them in one step.
  • Prefer createElement with textContent over innerHTML when data might come from users.
  • The <template> element holds reusable markup that you clone with content.cloneNode(true).