Online admissions open for the SEO, PHP web development and networking & cybersecurity courses. Enquire for the next batch.

The JavaScript DOM: Selecting, Changing and Creating Page Elements

5 min read Updated By the SkillNewton team

What is the DOM in JavaScript?

The DOM (Document Object Model) is the browser's live, in-memory representation of a web page as a tree of nodes. Each HTML element, piece of text and comment becomes a node object that JavaScript can read and change. When your code modifies the DOM, for example by changing text or adding an element, the browser updates what visitors see on screen.

The DOM tree: nodes, elements and the document

When the browser parses HTML, it builds a tree. The document object sits at the top, <html> is its root element, and every nested tag becomes a child node. Text inside elements becomes text nodes. Relationships are described as parent, child and sibling, just like a family tree.

<ul id="menu">
  <li class="active">Home</li>
  <li>Courses</li>
</ul>

<!-- Tree:
document
 └ html
    └ body
       └ ul#menu
          ├ li.active ─ "Home"
          └ li ─ "Courses"  -->

The DOM is not the HTML file. It is a live object model built from it, so changes your script makes appear on screen but are never saved back to the file. Viewing the page source shows the original HTML; the Elements panel in dev tools shows the current DOM.

Selecting elements and reading or changing content

document.querySelector() takes any CSS selector and returns the first match, or null. querySelectorAll() returns a static NodeList of every match, which you can loop over with forEach. getElementById() is a quick lookup by id.

const menu  = document.getElementById('menu');
const first = document.querySelector('#menu li');
const items = document.querySelectorAll('#menu li');

items.forEach(li => console.log(li.textContent));

first.textContent = 'Home page';          // plain text, safe
first.classList.add('highlight');
first.classList.toggle('active');
menu.setAttribute('aria-label', 'Main');

textContent versus innerHTML

textContent reads and writes plain text; any angle brackets are shown literally. innerHTML parses its value as HTML. If that value includes anything a user typed or a server returned, an attacker can inject markup that runs script, an attack called cross-site scripting (XSS). Use textContent for data, and create elements with DOM methods when you need structure.

classList, attributes and dataset

Changing classes with classList.add(), remove() and toggle() keeps styling in CSS where it belongs. getAttribute() and setAttribute() work for any attribute. Custom data-* attributes appear on element.dataset, with hyphenated names converted to camelCase, so data-product-id becomes dataset.productId.

Creating, inserting and removing elements

const li = document.createElement('li');
li.textContent = 'Contact';
li.dataset.page = 'contact';
menu.append(li);          // add at the end
menu.prepend(li.cloneNode(true));
li.remove();              // take it out again

// Batch inserts with a DocumentFragment
const cities = ['Pune', 'Jaipur', 'Kochi'];
const frag = document.createDocumentFragment();
for (const city of cities) {
  const option = document.createElement('li');
  option.textContent = city;
  frag.append(option);
}
menu.append(frag);        // one insertion into the live page

A DocumentFragment is a lightweight container that is not part of the page. Building many elements inside it and appending it once means the live document changes in a single step, which keeps your code tidy and avoids repeated layout work.

Events and event delegation

addEventListener(type, handler) runs a function when something happens, such as a click, input or submit. The handler receives an event object; event.target is the element that triggered it and event.preventDefault() stops default behaviour such as a form reloading the page.

Most events bubble up through the element's ancestors. Event delegation uses this: attach one listener to a parent and check event.target.closest() to find which child was clicked. It works for elements added later and avoids attaching hundreds of listeners.

Loading scripts with defer

Script that touches the DOM must run after the elements exist. Put your script in the <head> with the defer attribute: it downloads in parallel and runs, in order, once the HTML is parsed. Module scripts (type="module") are deferred automatically.

A working to-do list, built step by step

<!-- index.html -->
<form id="todo-form">
  <label for="task">New task</label>
  <input id="task" name="task" required>
  <button type="submit">Add</button>
</form>
<ul id="todo-list"></ul>
<script src="todo.js" defer></script>
// todo.js
const form  = document.querySelector('#todo-form');
const input = document.querySelector('#task');
const list  = document.querySelector('#todo-list');

form.addEventListener('submit', event => {
  event.preventDefault();
  const text = input.value.trim();
  if (!text) return;

  const li = document.createElement('li');
  const label = document.createElement('span');
  label.textContent = text;                 // never innerHTML for user input
  const del = document.createElement('button');
  del.type = 'button';
  del.textContent = 'Delete';
  del.dataset.action = 'delete';
  li.append(label, ' ', del);
  list.append(li);

  form.reset();
  input.focus();
});

// One delegated listener handles every current and future item
list.addEventListener('click', event => {
  const li = event.target.closest('li');
  if (!li) return;
  if (event.target.dataset.action === 'delete') {
    li.remove();
  } else {
    li.classList.toggle('done');
  }
});
  1. Write the HTML with a labelled input inside a form, so the Enter key and screen readers work without extra code.
  2. Load the script with defer so the elements exist when it runs.
  3. Select the form, input and list once and store them in constants.
  4. Handle submit, prevent the page reload and ignore empty input.
  5. Create the list item with createElement and textContent, then append it.
  6. Use one delegated click listener on the list to delete items or toggle a done class.

Good DOM habits

  • Select elements once and reuse the reference instead of querying inside loops.
  • Toggle classes and let CSS handle appearance, rather than setting many inline styles.
  • Check for null before using the result of querySelector().
  • Use real <button> elements for clickable controls so they work with the keyboard.
  • Treat any data from users or API responses as untrusted text.

Practical exercise: extend the to-do list

Start from the to-do list above and the language basics in our JavaScript introduction.

  1. Show a count of remaining tasks that updates after every add, delete and toggle.
  2. Add a "Clear completed" button that removes every item with the done class.
  3. Save the list to localStorage as JSON and rebuild it with a DocumentFragment when the page loads.
  4. Type <img src=x onerror=alert(1)> as a task and confirm it appears as plain text.

You are done when the list survives a page refresh, the counter is always correct, keyboard users can add and delete tasks, and the test input shows as text rather than running. Projects like this connect to server-side PHP in SkillNewton's PHP foundation course.

Common mistakes

  • Running DOM code from a script in the <head> without defer, so querySelector() returns null.
  • Inserting user input with innerHTML, which opens the page to cross-site scripting.
  • Attaching a separate listener to every list item instead of one delegated listener on the parent.
  • Expecting querySelectorAll() to return an array and calling array methods such as map on it directly.
  • Using a clickable <div> instead of a button, which keyboard users cannot reach.

Frequently asked questions

Is the DOM part of JavaScript?

No. The DOM is a web standard implemented by browsers, and JavaScript is the language used to work with it. Server-side JavaScript such as Node.js has no DOM unless a library provides one.

What is the difference between a NodeList and an array?

A NodeList supports forEach and indexing but lacks most array methods. Convert it with Array.from() or the spread syntax when you need map or filter.

When should I use innerHTML at all?

Only for markup you wrote yourself and fully control, such as a fixed template string with no user data in it. If any part comes from a visitor, a URL or a server response, build the elements with createElement and textContent instead.

Do I need a framework to manipulate the DOM?

No. Plain DOM methods handle forms, menus, tabs and lists well. Frameworks help with large applications, but learning the DOM first makes them much easier to understand.

Want to Learn This Properly?

The PHP Web Development: Foundation & Practical Implementation (Online) turns guides like this into practised skills, with projects on real websites.

WhatsApp Call Us Now

Free counselling

Send an Enquiry

Tell us which course you are interested in. We reply by email or phone with the next batch and how to enrol.

We reply by email or phone within working hours. No spam.