Back to Frontend
Evergreen··8 min read

The DOM API — A Beginner-Friendly Guide

Learn how the browser turns HTML into objects you can control with JavaScript. By the end, you'll build a working todo app from scratch.

javascriptdomhtmlbeginner
Share

What Is the DOM?

When a browser loads an HTML page, it converts every tag into a JavaScript object. These objects form a tree structure called the Document Object Model (DOM).

Think of it like this:

  • Your HTML file is a blueprint.
  • The DOM is the actual building the browser constructs from that blueprint.
  • JavaScript is the tool you use to remodel the building after it's built.
<html>
  <body>
    <h1>Hello</h1>
    <p>World</p>
  </body>
</html>

The browser turns this into a tree:

document
 └── html
      └── body
           ├── h1 → "Hello"
           └── p  → "World"

Every box in the tree is a node. Tags become element nodes, text inside them becomes text nodes.


Selecting Elements

Before you can change anything, you need to grab the element. The DOM gives you a few ways to do this.

getElementById

Finds one element by its id attribute.

<h1 id="title">Hello</h1>
const title = document.getElementById("title");
console.log(title.textContent); // "Hello"

querySelector

Finds the first element that matches a CSS selector. This is the most flexible option.

const title = document.querySelector("#title");      // by id
const firstParagraph = document.querySelector("p");   // by tag
const btn = document.querySelector(".my-button");     // by class

querySelectorAll

Returns all matching elements as a NodeList (similar to an array).

const allParagraphs = document.querySelectorAll("p");
 
allParagraphs.forEach((p) => {
  console.log(p.textContent);
});

Rule of thumb: Use querySelector / querySelectorAll for almost everything. They accept any CSS selector, so they're the most versatile.


Changing Content

textContent

Gets or sets the plain text inside an element.

const title = document.querySelector("#title");
title.textContent = "New title!";

innerHTML

Gets or sets the HTML inside an element. Be careful — setting innerHTML with user-provided strings can introduce security vulnerabilities (XSS).

const container = document.querySelector("#container");
container.innerHTML = "<strong>Bold text</strong>";

Tip: Prefer textContent when you're just dealing with plain text. Use innerHTML only when you genuinely need to insert HTML markup and you trust the source.


Changing Styles

Every element has a .style property that lets you set inline CSS.

const title = document.querySelector("#title");
title.style.color = "tomato";
title.style.fontSize = "2rem";

Notice that CSS property names like font-size become camelCase (fontSize) in JavaScript.

Adding/Removing CSS Classes

A better approach is to toggle CSS classes instead of setting inline styles.

const box = document.querySelector(".box");
 
box.classList.add("active");      // adds the "active" class
box.classList.remove("active");   // removes it
box.classList.toggle("active");   // adds if missing, removes if present
box.classList.contains("active"); // returns true or false

Creating and Removing Elements

Creating a New Element

// 1. Create the element
const newParagraph = document.createElement("p");
 
// 2. Give it content
newParagraph.textContent = "I was created with JavaScript!";
 
// 3. Add it to the page
document.body.appendChild(newParagraph);

Inserting Before Another Element

const list = document.querySelector("ul");
const newItem = document.createElement("li");
newItem.textContent = "Inserted item";
 
// Insert before the first child
list.insertBefore(newItem, list.firstChild);

Removing an Element

const item = document.querySelector("#old-item");
item.remove();

Handling Events

Events let you react to things the user does — clicks, typing, hovering, etc.

addEventListener

const button = document.querySelector("#my-button");
 
button.addEventListener("click", () => {
  alert("Button was clicked!");
});

Common Events

EventWhen It Fires
clickUser clicks the element
inputUser types in an input field
submitA form is submitted
keydownUser presses a key
mouseoverMouse moves over the element

The Event Object

Every event handler receives an event object with useful info.

const input = document.querySelector("#search");
 
input.addEventListener("input", (event) => {
  console.log(event.target.value); // the current text in the input
});

Preventing Default Behavior

Some elements have built-in behavior. For example, submitting a form reloads the page. You can stop that with preventDefault().

const form = document.querySelector("form");
 
form.addEventListener("submit", (event) => {
  event.preventDefault(); // stops the page from reloading
  console.log("Form submitted without reload!");
});

Attributes

You can read and write HTML attributes with JavaScript.

const link = document.querySelector("a");
 
// Read
console.log(link.getAttribute("href"));
 
// Write
link.setAttribute("href", "https://example.com");
 
// Remove
link.removeAttribute("target");

For common attributes like id, className, src, and value, you can also access them directly:

const img = document.querySelector("img");
img.src = "new-image.png";
img.alt = "A new image";

Traversing the DOM

Sometimes you need to move between related elements.

const item = document.querySelector("#current");
 
item.parentElement;           // the parent element
item.children;                // all child elements
item.firstElementChild;       // first child element
item.lastElementChild;        // last child element
item.nextElementSibling;      // the next sibling element
item.previousElementSibling;  // the previous sibling element

Building a Simple Todo App

Now let's put everything together. Here's a complete, working todo app using only HTML, CSS, and vanilla JavaScript.

What It Does

  • Type a task and press Enter or click "Add"
  • Each task appears in a list
  • Click a task to mark it as done (strikethrough)
  • Click the "x" button to delete a task

The Code

Create a single file called todo.html and paste this in:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Simple Todo App</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }
 
    body {
      font-family: system-ui, -apple-system, sans-serif;
      background: #1a1a2e;
      color: #eee;
      display: flex;
      justify-content: center;
      padding-top: 60px;
      min-height: 100vh;
    }
 
    .app {
      width: 100%;
      max-width: 420px;
      padding: 0 16px;
    }
 
    h1 {
      font-size: 1.8rem;
      margin-bottom: 24px;
      color: #e2b714;
    }
 
    .input-row {
      display: flex;
      gap: 8px;
      margin-bottom: 24px;
    }
 
    .input-row input {
      flex: 1;
      padding: 10px 14px;
      border: 1px solid #333;
      border-radius: 8px;
      background: #16213e;
      color: #eee;
      font-size: 1rem;
      outline: none;
    }
 
    .input-row input:focus {
      border-color: #e2b714;
    }
 
    .input-row button {
      padding: 10px 20px;
      border: none;
      border-radius: 8px;
      background: #e2b714;
      color: #1a1a2e;
      font-weight: 600;
      font-size: 1rem;
      cursor: pointer;
    }
 
    .input-row button:hover {
      background: #f0c830;
    }
 
    ul {
      list-style: none;
    }
 
    li {
      display: flex;
      align-items: center;
      justify-content: space-between;
      padding: 12px 14px;
      margin-bottom: 8px;
      background: #16213e;
      border-radius: 8px;
      border: 1px solid #333;
      cursor: pointer;
      transition: opacity 0.2s;
    }
 
    li.done .task-text {
      text-decoration: line-through;
      opacity: 0.5;
    }
 
    li .delete-btn {
      background: none;
      border: none;
      color: #e74c3c;
      font-size: 1.2rem;
      cursor: pointer;
      padding: 0 4px;
      line-height: 1;
    }
 
    li .delete-btn:hover {
      color: #ff6b6b;
    }
 
    .empty {
      text-align: center;
      color: #666;
      margin-top: 32px;
    }
  </style>
</head>
<body>
  <div class="app">
    <h1>Todo App</h1>
 
    <form class="input-row" id="todo-form">
      <input type="text" id="todo-input" placeholder="What needs to be done?" />
      <button type="submit">Add</button>
    </form>
 
    <ul id="todo-list"></ul>
    <p class="empty" id="empty-message">No tasks yet. Add one above!</p>
  </div>
 
  <script>
    // 1. Select the elements we need
    const form = document.querySelector("#todo-form");
    const input = document.querySelector("#todo-input");
    const list = document.querySelector("#todo-list");
    const emptyMessage = document.querySelector("#empty-message");
 
    // 2. Keep track of todos in an array
    let todos = [];
 
    // 3. Render the todo list to the DOM
    function render() {
      // Clear the current list
      list.innerHTML = "";
 
      // Show or hide the empty message
      emptyMessage.style.display = todos.length === 0 ? "block" : "none";
 
      // Create an <li> for each todo
      todos.forEach((todo, index) => {
        // Create the <li> element
        const li = document.createElement("li");
        if (todo.done) {
          li.classList.add("done");
        }
 
        // Create the text span
        const textSpan = document.createElement("span");
        textSpan.classList.add("task-text");
        textSpan.textContent = todo.text;
 
        // Clicking the text toggles done
        textSpan.addEventListener("click", () => {
          todos[index].done = !todos[index].done;
          render();
        });
 
        // Create the delete button
        const deleteBtn = document.createElement("button");
        deleteBtn.classList.add("delete-btn");
        deleteBtn.textContent = "✕";
        deleteBtn.addEventListener("click", (event) => {
          event.stopPropagation(); // don't trigger the li click
          todos.splice(index, 1);
          render();
        });
 
        // Assemble the <li>
        li.appendChild(textSpan);
        li.appendChild(deleteBtn);
        list.appendChild(li);
      });
    }
 
    // 4. Handle form submission
    form.addEventListener("submit", (event) => {
      event.preventDefault(); // prevent page reload
 
      const text = input.value.trim();
      if (text === "") return; // ignore empty input
 
      // Add the new todo to our array
      todos.push({ text: text, done: false });
 
      // Clear the input field
      input.value = "";
 
      // Re-render the list
      render();
    });
 
    // 5. Initial render
    render();
  </script>
</body>
</html>

How It Works — Step by Step

  1. Select elements — We grab the form, input, list, and empty message using querySelector.

  2. Store data in an array — The todos array holds objects like { text: "Buy groceries", done: false }. This is our "source of truth".

  3. Render function — Every time something changes, we call render(). It clears the list, loops through the todos array, and creates fresh <li> elements for each one. This pattern of "data changes → re-render the UI" is the same idea behind frameworks like React.

  4. Handle form submit — We listen for the submit event, prevent the default page reload, grab the input value, push it into our array, and re-render.

  5. Toggle done — Clicking the task text flips todo.done and re-renders. The CSS class .done applies the strikethrough style.

  6. Delete — Clicking the "x" button removes the todo from the array with splice() and re-renders. We use event.stopPropagation() so the click doesn't also toggle done.

Try It

Save the code as todo.html and open it in your browser. That's it — no build tools, no frameworks, no npm install. Just a file and a browser.


Summary

Here's a cheat sheet of everything we covered:

TaskMethod
Select one elementdocument.querySelector(selector)
Select all matchingdocument.querySelectorAll(selector)
Change textelement.textContent = "new text"
Change HTMLelement.innerHTML = "<b>bold</b>"
Change styleelement.style.color = "red"
Add/remove classelement.classList.add("name")
Create elementdocument.createElement("tag")
Add to pageparent.appendChild(child)
Remove from pageelement.remove()
Listen for eventselement.addEventListener("click", fn)
Prevent defaultevent.preventDefault()
Read attributeelement.getAttribute("href")
Set attributeelement.setAttribute("href", "url")

The DOM API is the foundation of every web application. Even if you use React or Vue, understanding the DOM helps you debug faster and write better code.

You might also like