Working with the DOM
Selecting elements, reading and writing content safely, creating nodes, and why innerHTML deserves caution.
Finding elements
document.querySelector('.card'); // first match
const all = document.querySelectorAll('.card'); // static NodeList
all.forEach(el => el.classList.add('seen'));
// cache a reference rather than re-querying in loops
const form = document.querySelector('#signup');querySelectorAll returns a static NodeList; getElementsByClassName returns a live HTMLCollection that updates as the DOM changes — a common source of confusion while looping.
Reading and writing
| Property | Sets |
|---|---|
textContent | Text only — safest choice |
innerHTML | Parsed HTML — powerful, XSS risk |
value | Form control's current value |
classList | Add/remove/toggle classes |
setAttribute | Any attribute |
el.textContent = userInput; // rendered as plain text
el.setAttribute('aria-expanded', 'false');
el.dataset.userId = '42'; // data-user-id
// style: prefer classes over inline styles
el.classList.toggle('is-open', open);
el.style.setProperty('--accent', '#4f46e5');⚠️
Never assign untrusted input to
innerHTML — el.innerHTML = name is an XSS hole as soon as name contains a script-bearing tag. Use textContent, or sanitize with a trusted library.Creating and inserting
const li = document.createElement('li');
li.className = 'row';
li.textContent = ' item ';
list.append(li); // or prepend / before / after
// efficient bulk insert
const frag = document.createDocumentFragment();
items.forEach(i => frag.append(makeRow(i)));
list.append(frag); // single reflow
li.remove();Batch DOM writes inside a fragment (or build one HTML string once) rather than appending in a loop — every insertion can trigger layout.
Timing your code
- Put
<script>near the end of the body, or usedeferso the script runs after parsing. type='module'is deferred by default — no need to add defer.asyncexecutes as soon as it downloads; order is not guaranteed.
<script src='/app.js' defer></script>
<script type='module' src='/app.js'></script>FAQ
Why is querySelector returning null?
Your script ran before the element existed. Defer the script, or wait for
DOMContentLoaded.How do I wait for images/fonts too?
window.addEventListener('load', …) fires after all subresources finish.Related
Last refreshed 2026-09-17.