DOM Manipulation
The DOM (Document Object Model) is the browser's in-memory representation of your HTML. JavaScript manipulates it to create dynamic UIs.
Selecting Elements
// Single element — returns first match or null
const header = document.querySelector<HTMLElement>('header');
const form = document.querySelector<HTMLFormElement>('#signup-form');
const input = document.querySelector<HTMLInputElement>('.email-input');
// Multiple elements — returns a static NodeList
const buttons = document.querySelectorAll<HTMLButtonElement>('.btn');
const cards = document.querySelectorAll<HTMLDivElement>('.card');
// Iterate NodeList
buttons.forEach(btn => btn.disabled = true);
[...buttons].map(btn => btn.textContent); // spread to array for array methods
// Relative selectors (scoped to a parent)
const container = document.querySelector<HTMLElement>('#app')!;
const title = container.querySelector<HTMLHeadingElement>('h1');
TypeScript + DOM
Always provide the HTML element type generic (e.g. <HTMLInputElement>) so TypeScript knows which properties are available (.value, .checked, etc.).
Reading and Writing Content
const el = document.querySelector<HTMLElement>('#title')!;
// Text content (safe — no HTML injection risk)
el.textContent = 'New Title';
const text = el.textContent;
// Inner HTML (use carefully — XSS risk if inserting user data)
el.innerHTML = '<strong>Bold text</strong>';
// Outer HTML — includes the element itself
console.log(el.outerHTML);
// Safe HTML with template (use instead of innerHTML for user data)
const name = userInput; // could be malicious
const span = document.createElement('span');
span.textContent = name; // safe — textContent never interprets HTML
el.appendChild(span);
Classes and Attributes
const btn = document.querySelector<HTMLButtonElement>('.btn')!;
// classList
btn.classList.add('active');
btn.classList.remove('active');
btn.classList.toggle('active');
btn.classList.contains('active'); // boolean
btn.classList.replace('old', 'new');
// Multiple classes
btn.classList.add('active', 'focused', 'visible');
// Attributes
btn.setAttribute('aria-pressed', 'true');
btn.getAttribute('aria-pressed'); // 'true'
btn.removeAttribute('disabled');
btn.hasAttribute('disabled'); // boolean
// Data attributes
const card = document.querySelector<HTMLElement>('[data-id]')!;
card.dataset.id; // get data-id
card.dataset.userId = '42'; // set data-user-id
Styles
const box = document.querySelector<HTMLDivElement>('#box')!;
// Inline styles (use sparingly — prefer classes)
box.style.display = 'flex';
box.style.backgroundColor = '#3b82f6';
box.style.setProperty('--custom-prop', 'value'); // CSS custom properties
// Get computed styles (what the browser actually applies)
const styles = getComputedStyle(box);
styles.getPropertyValue('color');
styles.getPropertyValue('--custom-prop');
// Better: toggle classes
box.classList.toggle('hidden'); // CSS: .hidden { display: none }
box.classList.toggle('visible');
Creating and Inserting Elements
// Create
const div = document.createElement('div');
div.className = 'card';
div.textContent = 'Hello';
// Insert methods
parent.appendChild(child); // append as last child
parent.prepend(child); // insert as first child
parent.insertBefore(newEl, referenceEl); // before a specific child
referenceEl.before(newEl); // sibling before
referenceEl.after(newEl); // sibling after
parent.replaceChild(newEl, oldEl);
// insertAdjacentHTML — precise position without replacing
el.insertAdjacentHTML('beforebegin', html); // before the element
el.insertAdjacentHTML('afterbegin', html); // first child
el.insertAdjacentHTML('beforeend', html); // last child
el.insertAdjacentHTML('afterend', html); // after the element
// DocumentFragment — batch inserts for performance
const fragment = document.createDocumentFragment();
items.forEach(item => {
const li = document.createElement('li');
li.textContent = item.name;
fragment.appendChild(li);
});
list.appendChild(fragment); // single DOM update
The Template Pattern
Use <template> elements in HTML to clone complex structures:
<template id="card-template">
<article class="card">
<h3 class="card__title"></h3>
<p class="card__body"></p>
<button class="card__btn">Read more</button>
</article>
</template>
function renderCard(post: Post): HTMLElement {
const template = document.querySelector<HTMLTemplateElement>('#card-template')!;
const clone = template.content.cloneNode(true) as DocumentFragment;
clone.querySelector<HTMLHeadingElement>('.card__title')!.textContent = post.title;
clone.querySelector<HTMLParagraphElement>('.card__body')!.textContent = post.excerpt;
const btn = clone.querySelector<HTMLButtonElement>('.card__btn')!;
btn.addEventListener('click', () => openPost(post.id));
return clone.querySelector('.card') as HTMLElement;
}
// Render all posts
const grid = document.querySelector<HTMLDivElement>('#post-grid')!;
const fragment = document.createDocumentFragment();
posts.forEach(post => fragment.appendChild(renderCard(post)));
grid.appendChild(fragment);
Removing Elements
// Remove an element
element.remove();
// Remove all children
while (parent.firstChild) {
parent.removeChild(parent.firstChild);
}
// Or (simpler but replaces with empty string — lose event listeners)
parent.innerHTML = '';