The essentials

Quick reference

One focused task per row. Jump to the related section for complete, working examples.

UseSyntaxExamples
Select the first matchconst submit = form.querySelector('button[type="submit"]')View examples
Select a static snapshotconst rows = table.querySelectorAll('tbody > tr')View examples
Escape a selector valueconst item = document.querySelector(`#${CSS.escape(rawId)}`)View examples
Test an elementif (element.matches('[aria-current]')) markActive(element)View examples
Find the nearest matchconst card = button.closest('[data-card]')View examples
Skip non-element siblingsconst nextItem = item.nextElementSiblingView examples
Read live element childrenconst liveItems = list.childrenView examples
Create a filtered tree walkconst walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT)View examples
Create an elementconst item = document.createElement('li')View examples
Set literal textitem.textContent = userValueView examples
Stage nodes in a fragmentconst fragment = document.createDocumentFragment()View examples
Append nodes and textcard.append(title, ' — ', summary)View examples
Move a node to the startlist.prepend(list.lastElementChild)View examples
Insert before a siblingreference.before(newNode)View examples
Replace all childrencontainer.replaceChildren(emptyState)View examples
Replace one nodeplaceholder.replaceWith(content)View examples
Clone a subtreeconst copy = template.content.cloneNode(true)View examples
Import a cross-document copyconst copy = document.importNode(source, true)View examples
Adopt a cross-document nodeconst moved = document.adoptNode(source)View examples
Remove a nodenotice.remove()View examples
Observe child mutationsobserver.observe(list, { childList: true, subtree: true })View examples

DOM work is most reliable when selection scope, node type, ownership, and mutation semantics are explicit. Query once from the narrowest stable root, distinguish static NodeLists from live collections, construct untrusted text without HTML parsing, and remember that inserting an existing node moves it. For large or cross-document changes, use fragments and the dedicated clone, import, and adopt operations rather than depending on serialization side effects.

Step by step

Detailed examples

01

Scope selectors and understand snapshot behavior

querySelector returns the first match or null; querySelectorAll returns a static NodeList whose membership does not change after the query. Invoke either method on the narrowest stable root to avoid unrelated matches and reduce work. Selector strings use CSS syntax, so interpolate external IDs or class fragments only after CSS.escape; escaping makes the selector syntactically safe but does not change which selector structure your code chooses.

Query inside one component and escape its identifier
<section id="inventory">
  <article id="item:42" data-stock="0">Tea</article>
  <article id="item:43" data-stock="3">Coffee</article>
</section>
<script>
  const inventory = document.querySelector('#inventory');
  const unavailable = inventory.querySelectorAll('[data-stock="0"]');
  const rawId = 'item:42';
  const item = inventory.querySelector(`#${CSS.escape(rawId)}`);
  console.log(unavailable.length, item.textContent);
</script>
Back to quick reference ↑
02

Choose element navigation when markup whitespace is irrelevant

Node navigation properties such as parentNode, firstChild, and nextSibling can return documents, text, or comments. Element-specific counterparts such as parentElement, firstElementChild, children, and nextElementSibling skip non-element nodes, which is usually appropriate for UI structure. matches tests one element, while closest begins with the element itself and then walks its inclusive ancestors until it finds a match.

Navigate cards without encountering whitespace Text nodes
<section id="cards">
  <article data-card><button type="button">Choose</button></article>
  <article data-card aria-current="true">Current</article>
</section>
<script>
  const button = document.querySelector('#cards button');
  const card = button.closest('[data-card]');
  const nextCard = card.nextElementSibling;
  console.log(nextCard.matches('[aria-current]')); // true
</script>
Back to quick reference ↑
03

Distinguish live collections from explicit tree cursors

Element.children returns a live HTMLCollection, so its length and indexes reflect later mutations. querySelectorAll is static, which is safer when mutations occur during iteration. TreeWalker provides a stateful cursor for depth-first traversal constrained by a whatToShow mask and optional filter; moving its currentNode does not mutate the tree. Choose a snapshot when stable membership matters and a walker when processing a large heterogeneous subtree incrementally.

Collect non-empty text nodes with TreeWalker
const root = document.querySelector('main');
const walker = document.createTreeWalker(
  root,
  NodeFilter.SHOW_TEXT,
  { acceptNode: node => node.data.trim() ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT }
);
const values = [];
while (walker.nextNode()) values.push(walker.currentNode.data.trim());
console.log(values);
Back to quick reference ↑
04

Construct nodes without parsing untrusted markup

createElement and createTextNode construct explicit node types, while textContent replaces content with literal text. These APIs avoid turning untrusted strings into elements, attributes, URLs, or executable markup. A DocumentFragment can stage a group of nodes; inserting the fragment transfers its children into the destination and leaves the fragment empty. Fragment batching also keeps assembly code separate from the live document.

Render API data as a list of literal text
const values = ['Tea', '<img src=x onerror=alert(1)>', 'Coffee'];
const fragment = document.createDocumentFragment();

for (const value of values) {
  const item = document.createElement('li');
  item.textContent = value;
  fragment.append(item);
}

document.querySelector('#results').replaceChildren(fragment);
Back to quick reference ↑
05

Remember that insertion moves existing nodes

append, prepend, before, after, replaceWith, and replaceChildren accept nodes and strings; strings become Text nodes. A node can occupy only one position in a tree, so inserting an existing node removes it from its old position rather than copying it. replaceChildren clears and repopulates a parent through one API call, while mutation observers still receive records according to the DOM mutation algorithms. Clone explicitly when two independent copies are required.

Move an existing row and replace an empty state
<ul id="queue">
  <li>First</li>
  <li>Second</li>
</ul>
<div id="result"><p>No result</p></div>
<script>
  const queue = document.querySelector('#queue');
  queue.prepend(queue.lastElementChild); // Moves "Second"; it does not clone it.

  const message = document.createElement('p');
  message.textContent = 'Processing started';
  document.querySelector('#result').replaceChildren(message);
</script>
Back to quick reference ↑
06

Clone, import, or adopt according to ownership needs

cloneNode copies attributes and, with deep=true, descendants, but it does not copy listeners registered through addEventListener and can duplicate id values if inserted unchanged. importNode clones a node into the receiving document. adoptNode instead removes the original from its parent, changes its node document, and runs adoption steps defined for affected interfaces. Neither operation inserts the result; place it explicitly after resolving identifiers and component state.

Instantiate a template and assign a unique identifier
<template id="card-template">
  <article class="card"><h2></h2></article>
</template>
<section id="cards"></section>
<script>
  const template = document.querySelector('#card-template');
  const copy = template.content.cloneNode(true);
  const card = copy.querySelector('.card');
  card.id = 'card-1042';
  card.querySelector('h2').textContent = 'Release notes';
  document.querySelector('#cards').append(copy);
</script>
Copy or move nodes from another document
const sourceDocument = document.implementation.createHTMLDocument('Source');
const source = sourceDocument.createElement('p');
source.textContent = 'Cross-document content';

const independentCopy = document.importNode(source, true);
const adoptedOriginal = document.adoptNode(source);

document.querySelector('#destination').append(independentCopy, adoptedOriginal);
Back to quick reference ↑
07

Remove directly and observe changes asynchronously

ChildNode.remove detaches a node if it has a parent and otherwise has no effect. Removing DOM does not automatically cancel unrelated timers, observers, or listeners attached to external targets, so component teardown still needs explicit cleanup. MutationObserver reports matching changes in a queued callback rather than synchronously inside the mutation operation. Observe only the attributes, child lists, character data, and subtree scope the feature actually needs, then disconnect when finished.

Count direct additions and stop observing on teardown
const list = document.querySelector('#notifications');
const observer = new MutationObserver((records) => {
  const added = records.reduce((total, record) => total + record.addedNodes.length, 0);
  console.log(`Added ${added} nodes`);
});

observer.observe(list, { childList: true });
const notice = document.createElement('li');
notice.textContent = 'Queued';
list.append(notice);
list.querySelector('[data-expired]')?.remove();

// Run during component teardown:
// observer.disconnect();
Back to quick reference ↑

Local code tester

Build and reorder a list safely

Add literal user text with DOM construction, then move an existing item without cloning it.

Runs in your browser
Preview

Sources and further reading

References

Authoritative documentation used to verify and expand this cheat sheet.

  1. WHATWGDOM Standard: Nodesdom.spec.whatwg.org
  2. WHATWGDOM Standard: Elementdom.spec.whatwg.org
  3. WHATWGDOM Standard: Traversaldom.spec.whatwg.org
  4. WHATWGDOM Standard: Mutation observersdom.spec.whatwg.org
  5. WHATWGHTML Standard: The template elementhtml.spec.whatwg.org

Help us improve

Found a typo or missing example?

Tell us what would make this cheat sheet clearer, more complete, or more useful.

Share feedback