Skip to main content

Building the page with JavaScript

So far you’ve modified existing elements. But often you need to create new ones — render a list of users from an API, add a notification, or build a table from data. This is where document.createElement() comes in.

document.createElement()

Create a new element, configure it, then add it to the page:
// 1. Create the element
const card = document.createElement("div");

// 2. Configure it
card.className = "user-card";
card.textContent = "Sarah Chen — Admin";

// 3. Add it to the page
document.querySelector("#user-list").appendChild(card);
The element doesn’t appear on the page until you add it to the DOM with appendChild or similar methods.

A more complete example

// Create a user card with multiple child elements
const card = document.createElement("div");
card.className = "user-card";

const name = document.createElement("h3");
name.textContent = "Sarah Chen";

const email = document.createElement("p");
email.textContent = "sarah@example.com";
email.className = "email";

const deleteBtn = document.createElement("button");
deleteBtn.textContent = "Delete";
deleteBtn.className = "btn-danger";

// Build the tree
card.appendChild(name);
card.appendChild(email);
card.appendChild(deleteBtn);

// Add to the page
document.querySelector("#user-list").appendChild(card);
This produces:
<div class="user-card">
  <h3>Sarah Chen</h3>
  <p class="email">sarah@example.com</p>
  <button class="btn-danger">Delete</button>
</div>

Adding elements to the page

Several methods for inserting elements, each with a different position:
const parent = document.querySelector("#container");
const newElement = document.createElement("p");
newElement.textContent = "New paragraph";

// Add to the end (most common)
parent.appendChild(newElement);

// Add to the beginning
parent.prepend(newElement);

// Add to the end (modern alternative to appendChild)
parent.append(newElement);

// Insert before a specific child
const reference = document.querySelector("#reference-element");
parent.insertBefore(newElement, reference);

// Insert relative to an element
reference.before(newElement);   // Before the reference
reference.after(newElement);    // After the reference
MethodPositionReturns
parent.appendChild(el)End of parentThe appended element
parent.append(el)End of parentundefined
parent.prepend(el)Start of parentundefined
el.before(newEl)Before elundefined
el.after(newEl)After elundefined
append() and prepend() are the modern alternatives. They also accept strings: parent.append("Hello") adds a text node. appendChild only accepts elements.

Removing elements

// Modern — call .remove() on the element
const card = document.querySelector(".user-card");
card.remove();

// Older — remove through parent
const parent = card.parentElement;
parent.removeChild(card);

// Remove all children
const container = document.querySelector("#container");
container.innerHTML = ""; // Quick way to clear everything

Rendering a list from data

This is the most common use case — take data (from an API, for example) and build the DOM:
const users = [
  { id: 1, name: "Sarah Chen", role: "Admin" },
  { id: 2, name: "John Park", role: "Editor" },
  { id: 3, name: "Alice Rivera", role: "Viewer" },
];

const list = document.querySelector("#user-list");

users.forEach(user => {
  const li = document.createElement("li");
  li.textContent = `${user.name}${user.role}`;
  li.dataset.userId = user.id;
  list.appendChild(li);
});

With innerHTML (simpler for complex HTML)

const users = [
  { id: 1, name: "Sarah Chen", role: "Admin" },
  { id: 2, name: "John Park", role: "Editor" },
  { id: 3, name: "Alice Rivera", role: "Viewer" },
];

const list = document.querySelector("#user-list");

list.innerHTML = users.map(user => `
  <li data-user-id="${user.id}">
    <strong>${user.name}</strong>
    <span class="role">${user.role}</span>
  </li>
`).join("");
innerHTML with template literals is convenient but vulnerable to XSS if the data contains user input. For data from your own API, it’s fine. For user-generated content, use createElement + textContent.

Document fragments (batch insertions)

When adding many elements, each appendChild triggers a page repaint. Use a DocumentFragment to batch insertions:
const users = await fetch("/api/users").then(r => r.json());
const fragment = document.createDocumentFragment();

users.forEach(user => {
  const li = document.createElement("li");
  li.textContent = user.name;
  fragment.appendChild(li); // Add to fragment (no repaint)
});

document.querySelector("#user-list").appendChild(fragment);
// Single repaint when the fragment is added to the DOM
For small lists (under 100 items), the performance difference is negligible. Use fragments when rendering large datasets or when you notice visible flickering during rendering.

How React replaces this

Everything in this lesson — createElement, appendChild, innerHTML — is what React automates. Compare:
// Vanilla JavaScript
const users = [{ name: "Sarah" }, { name: "John" }];
const list = document.querySelector("#user-list");
list.innerHTML = users.map(u => `<li>${u.name}</li>`).join("");
// React — same result, declarative approach
function UserList({ users }) {
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}
React handles creating, updating, and removing DOM elements for you. You describe what the UI should look like; React figures out how to make it happen.
Understanding createElement helps you appreciate what React does under the hood. You won’t use manual DOM creation in React projects, but you’ll understand error messages and debugging better.

What’s next?

You can create and add elements to the page. Now let’s make them interactive — responding to clicks, typing, and other user actions.

Event listeners

Respond to user interactions like clicks and keyboard input