Key takeaways
- The DOM (Document Object Model) is the browser’s live, tree-shaped representation of a page that JavaScript can read and change.
- Common tasks: select elements, listen to events, update text and classes, and create elements.
- Efficient DOM code matters for responsiveness — batch changes and avoid layout thrashing.
Every interactive feature on this website — the dark-mode toggle, the quick search dropdown, the SEO Library filter, the chatbot — is JavaScript changing the DOM. Understanding the DOM is the key step from “copying snippets” to actually building interfaces.
From HTML to DOM#
When the browser parses HTML, it builds a tree of nodes: document → html → head/body → elements → text. JavaScript works with that tree. If a script changes it, the page updates — but the original HTML source doesn’t.
Selecting elements#
const header = document.querySelector('.site-header'); // first match
const buttons = document.querySelectorAll('[data-share]'); // all matches (NodeList)
const form = document.getElementById('contact-form');
const card = button.closest('.card'); // nearest ancestor
Responding to events#
const toggle = document.querySelector('[data-theme-toggle]');
toggle.addEventListener('click', () => {
const root = document.documentElement;
const next = root.dataset.theme === 'dark' ? 'light' : 'dark';
root.dataset.theme = next;
});
Event delegation — one listener on a parent handling many children — is efficient and works for elements added later:
document.addEventListener('click', (e) => {
const btn = e.target.closest('[data-copy-link]');
if (!btn) return;
navigator.clipboard.writeText(location.href);
});
Changing content and styles#
| Task | Code |
|---|---|
| Change text safely | el.textContent = 'Saved!' |
| Toggle a class | el.classList.toggle('is-open') |
| Set an attribute | btn.setAttribute('aria-expanded', 'true') |
| Show / hide | panel.hidden = false |
| Data attributes | el.dataset.state = 'loading' → data-state="loading" |
| Create and add | const li = document.createElement('li'); list.append(li); |
| Remove | el.remove() |
innerHTML — it can execute injected scripts (XSS). Use textContent, or escape HTML before inserting. The chatbot on this site escapes every message before rendering it.Need a hand with this?
Get a quick, honest review of your website’s SEO.
Performance: avoid layout thrashing#
Reading layout values (offsetHeight, getBoundingClientRect()) after writing styles forces the browser to recalculate layout. Doing this in a loop causes “layout thrashing” and janky scrolling.
- Batch reads first, then writes.
- Use
requestAnimationFramefor visual updates on scroll. - Use passive scroll listeners:
addEventListener('scroll', fn, { passive: true }). - Prefer
IntersectionObserverover scroll handlers to detect visibility (lazy loading, active TOC links). - Keep the DOM reasonably small — very large DOMs slow every interaction (INP).
Accessibility while changing the DOM#
- Update ARIA states (
aria-expanded,aria-pressed) when toggling UI. - Move focus into opened dialogs and back to the trigger when closed.
- Announce dynamic messages with
aria-liveregions (toasts, chat replies, form status).
SEO note#
Content created only by DOM manipulation after load depends on Google rendering your JavaScript. Keep important content in the server HTML and use JavaScript for enhancement — see JavaScript SEO.
DOM FAQ#
Do I still need jQuery?#
No. Modern browser APIs (querySelector, classList, fetch) cover what jQuery used to simplify.
What’s the virtual DOM?#
Frameworks like React keep an in-memory representation and update the real DOM efficiently. It’s an abstraction over the same DOM.
Why doesn’t my script find the element?#
The script probably runs before the element exists. Load it with defer or wait for DOMContentLoaded.