The essentials
Quick reference
One focused task per row. Jump to the related section for complete, working examples.
| Use | Syntax | Examples |
|---|---|---|
| Select the first match | const submit = form.querySelector('button[type="submit"]') | View examples |
| Select a static snapshot | const rows = table.querySelectorAll('tbody > tr') | View examples |
| Escape a selector value | const item = document.querySelector(`#${CSS.escape(rawId)}`) | View examples |
| Test an element | if (element.matches('[aria-current]')) markActive(element) | View examples |
| Find the nearest match | const card = button.closest('[data-card]') | View examples |
| Skip non-element siblings | const nextItem = item.nextElementSibling | View examples |
| Read live element children | const liveItems = list.children | View examples |
| Create a filtered tree walk | const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT) | View examples |
| Create an element | const item = document.createElement('li') | View examples |
| Set literal text | item.textContent = userValue | View examples |
| Stage nodes in a fragment | const fragment = document.createDocumentFragment() | View examples |
| Append nodes and text | card.append(title, ' — ', summary) | View examples |
| Move a node to the start | list.prepend(list.lastElementChild) | View examples |
| Insert before a sibling | reference.before(newNode) | View examples |
| Replace all children | container.replaceChildren(emptyState) | View examples |
| Replace one node | placeholder.replaceWith(content) | View examples |
| Clone a subtree | const copy = template.content.cloneNode(true) | View examples |
| Import a cross-document copy | const copy = document.importNode(source, true) | View examples |
| Adopt a cross-document node | const moved = document.adoptNode(source) | View examples |
| Remove a node | notice.remove() | View examples |
| Observe child mutations | observer.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
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.
<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> 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.
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); 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.
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); 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.
<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> 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.
<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> 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); 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.
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(); Local code tester
Build and reorder a list safely
Add literal user text with DOM construction, then move an existing item without cloning it.
Sources and further reading
References
Authoritative documentation used to verify and expand this cheat sheet.
Help us improve
Found a typo or missing example?
Tell us what would make this cheat sheet clearer, more complete, or more useful.



