DEV Community

sizan mahmud0
sizan mahmud0

Posted on

Stop Using jQuery: Master Vanilla JavaScript DOM Manipulation in 2025

Javascript

The Framework-Free Revolution That Changed How I Build Web Apps

I spent three years relying on jQuery and React for every DOM interaction. Then I challenged myself to build a complete dashboard using only vanilla JavaScript. What I discovered shocked me: modern JavaScript makes DOM manipulation easier, faster, and more powerful than ever before.

Today, I'll show you every DOM manipulation technique you need—from basic element selection to building interactive UIs—with real-world examples you can use immediately.

Why DOM Manipulation Still Matters

"Just use React" is common advice, but there are compelling reasons to master vanilla JavaScript:

Performance: Direct DOM manipulation is 10-100x faster than virtual DOM overhead for simple interactions.

Bundle Size: Zero dependencies means your JavaScript weighs kilobytes instead of megabytes.

Real-World Need: Adding interactivity to WordPress sites, landing pages, or legacy apps where frameworks are overkill.

Framework Foundation: Understanding the DOM makes you better at React, Vue, and Angular.

Part 1: Selecting Elements - Finding Your Targets

Before you can manipulate elements, you need to find them. Modern JavaScript offers powerful selectors.

querySelector & querySelectorAll - The Modern Way

// Select single element (returns first match)
const header = document.querySelector('.header');
const submitButton = document.querySelector('#submit-btn');
const firstParagraph = document.querySelector('article p');

// Select multiple elements (returns NodeList)
const allButtons = document.querySelectorAll('.btn');
const navLinks = document.querySelectorAll('nav a');
const dataItems = document.querySelectorAll('[data-active="true"]');

// Works with complex CSS selectors
const specificItem = document.querySelector('div.container > ul.list li:nth-child(2)');
Enter fullscreen mode Exit fullscreen mode

Pro tip: querySelector uses CSS selectors, so if you can style it with CSS, you can select it with JavaScript!

Legacy Methods (Still Useful)

// Select by ID (faster than querySelector for IDs)
const modal = document.getElementById('modal');

// Select by class name
const cards = document.getElementsByClassName('card');

// Select by tag name
const images = document.getElementsByTagName('img');
Enter fullscreen mode Exit fullscreen mode

Real-world example: Dynamic Navigation Highlighter

// Highlight active navigation link based on scroll position
const sections = document.querySelectorAll('section[id]');
const navLinks = document.querySelectorAll('nav a');

window.addEventListener('scroll', () => {
    const scrollPosition = window.scrollY + 100;

    sections.forEach(section => {
        const sectionTop = section.offsetTop;
        const sectionHeight = section.offsetHeight;
        const sectionId = section.getAttribute('id');

        if (scrollPosition >= sectionTop && scrollPosition < sectionTop + sectionHeight) {
            // Remove active class from all links
            navLinks.forEach(link => link.classList.remove('active'));

            // Add active class to matching link
            const activeLink = document.querySelector(`nav a[href="#${sectionId}"]`);
            if (activeLink) activeLink.classList.add('active');
        }
    });
});
Enter fullscreen mode Exit fullscreen mode

Part 2: Modifying Content - Changing What Users See

Changing Text Content

const heading = document.querySelector('h1');

// Plain text (safe from XSS attacks)
heading.textContent = 'Welcome to Our Site';

// HTML content (use carefully!)
const container = document.querySelector('.content');
container.innerHTML = '<p>New <strong>HTML</strong> content</p>';

// Inner text (respects CSS visibility)
const hiddenElement = document.querySelector('.hidden');
hiddenElement.innerText = 'This respects CSS display:none';
Enter fullscreen mode Exit fullscreen mode

Security Warning: Never use innerHTML with user-generated content! It opens XSS vulnerabilities.

Modifying Attributes

const profileImage = document.querySelector('.profile-img');

// Get attribute
const currentSrc = profileImage.getAttribute('src');

// Set attribute
profileImage.setAttribute('src', 'new-image.jpg');
profileImage.setAttribute('alt', 'Updated profile picture');

// Remove attribute
profileImage.removeAttribute('data-temp');

// Direct property access (cleaner for standard attributes)
profileImage.src = 'another-image.jpg';
profileImage.alt = 'Profile picture';

// Data attributes
const userId = profileImage.dataset.userId; // Gets data-user-id
profileImage.dataset.role = 'admin'; // Sets data-role="admin"
Enter fullscreen mode Exit fullscreen mode

Real-world example: Image Gallery with Lightbox

const galleryImages = document.querySelectorAll('.gallery img');
const lightbox = document.querySelector('.lightbox');
const lightboxImage = document.querySelector('.lightbox img');
const closeButton = document.querySelector('.lightbox .close');

galleryImages.forEach(img => {
    img.addEventListener('click', () => {
        // Set lightbox image source to clicked image
        lightboxImage.src = img.src;
        lightboxImage.alt = img.alt;

        // Show lightbox
        lightbox.classList.add('active');
        document.body.style.overflow = 'hidden'; // Prevent scroll
    });
});

closeButton.addEventListener('click', () => {
    lightbox.classList.remove('active');
    document.body.style.overflow = ''; // Restore scroll
});

// Close on background click
lightbox.addEventListener('click', (e) => {
    if (e.target === lightbox) {
        lightbox.classList.remove('active');
        document.body.style.overflow = '';
    }
});
Enter fullscreen mode Exit fullscreen mode

Part 3: Styling Elements - Dynamic Visual Changes

Inline Styles (Use Sparingly)

const box = document.querySelector('.box');

// Single property
box.style.backgroundColor = '#3498db';
box.style.padding = '20px';
box.style.borderRadius = '8px';

// Multiple properties
Object.assign(box.style, {
    width: '300px',
    height: '200px',
    boxShadow: '0 4px 6px rgba(0,0,0,0.1)',
    transform: 'translateY(-10px)',
    transition: 'all 0.3s ease'
});
Enter fullscreen mode Exit fullscreen mode

Class Manipulation (Preferred Method)

const button = document.querySelector('.btn');

// Add class
button.classList.add('btn-primary');

// Remove class
button.classList.remove('btn-secondary');

// Toggle class (add if absent, remove if present)
button.classList.toggle('active');

// Check if class exists
if (button.classList.contains('disabled')) {
    console.log('Button is disabled');
}

// Replace class
button.classList.replace('btn-small', 'btn-large');
Enter fullscreen mode Exit fullscreen mode

Real-world example: Theme Switcher

const themeToggle = document.querySelector('.theme-toggle');
const body = document.body;

// Load saved theme from localStorage
const savedTheme = localStorage.getItem('theme') || 'light';
body.classList.add(`theme-${savedTheme}`);

themeToggle.addEventListener('click', () => {
    // Toggle between light and dark
    if (body.classList.contains('theme-light')) {
        body.classList.replace('theme-light', 'theme-dark');
        localStorage.setItem('theme', 'dark');
        themeToggle.textContent = '☀️ Light Mode';
    } else {
        body.classList.replace('theme-dark', 'theme-light');
        localStorage.setItem('theme', 'light');
        themeToggle.textContent = '🌙 Dark Mode';
    }
});
Enter fullscreen mode Exit fullscreen mode

Part 4: Creating & Removing Elements - Building Dynamic UIs

Creating New Elements

// Create element
const newCard = document.createElement('div');
newCard.classList.add('card');

// Add content
newCard.innerHTML = `
    <h3>New Card Title</h3>
    <p>Card description goes here</p>
    <button class="btn">Learn More</button>
`;

// Alternative: Building element by element (safer)
const safeCard = document.createElement('div');
safeCard.classList.add('card');

const title = document.createElement('h3');
title.textContent = 'Safe Card Title';

const description = document.createElement('p');
description.textContent = 'This method prevents XSS attacks';

const button = document.createElement('button');
button.classList.add('btn');
button.textContent = 'Click Me';

safeCard.appendChild(title);
safeCard.appendChild(description);
safeCard.appendChild(button);

// Insert into DOM
const container = document.querySelector('.container');
container.appendChild(safeCard);
Enter fullscreen mode Exit fullscreen mode

Insertion Methods

const parent = document.querySelector('.parent');
const referenceElement = document.querySelector('.reference');

// Append (add to end)
parent.appendChild(newElement);

// Prepend (add to beginning)
parent.prepend(newElement);

// Insert before
parent.insertBefore(newElement, referenceElement);

// Modern methods (more flexible)
referenceElement.before(newElement); // Insert before reference
referenceElement.after(newElement);  // Insert after reference
parent.append(element1, element2, 'text'); // Can append multiple
Enter fullscreen mode Exit fullscreen mode

Removing Elements

const elementToRemove = document.querySelector('.old-element');

// Modern method
elementToRemove.remove();

// Legacy method (still works)
elementToRemove.parentNode.removeChild(elementToRemove);

// Remove all children
const container = document.querySelector('.container');
container.innerHTML = ''; // Quick but loses event listeners

// Better: Remove children one by one
while (container.firstChild) {
    container.removeChild(container.firstChild);
}
Enter fullscreen mode Exit fullscreen mode

Real-world example: Todo List App

const todoInput = document.querySelector('#todo-input');
const addButton = document.querySelector('#add-todo');
const todoList = document.querySelector('#todo-list');

function createTodoItem(text) {
    // Create list item
    const li = document.createElement('li');
    li.classList.add('todo-item');

    // Create checkbox
    const checkbox = document.createElement('input');
    checkbox.type = 'checkbox';
    checkbox.addEventListener('change', (e) => {
        li.classList.toggle('completed', e.target.checked);
    });

    // Create text span
    const span = document.createElement('span');
    span.textContent = text;

    // Create delete button
    const deleteBtn = document.createElement('button');
    deleteBtn.textContent = '🗑️';
    deleteBtn.classList.add('delete-btn');
    deleteBtn.addEventListener('click', () => {
        li.remove();
        saveTodos();
    });

    // Assemble elements
    li.appendChild(checkbox);
    li.appendChild(span);
    li.appendChild(deleteBtn);

    return li;
}

addButton.addEventListener('click', () => {
    const text = todoInput.value.trim();
    if (text) {
        const todoItem = createTodoItem(text);
        todoList.appendChild(todoItem);
        todoInput.value = '';
        saveTodos();
    }
});

// Save to localStorage
function saveTodos() {
    const todos = Array.from(todoList.querySelectorAll('li span'))
        .map(span => span.textContent);
    localStorage.setItem('todos', JSON.stringify(todos));
}

// Load from localStorage on page load
function loadTodos() {
    const saved = localStorage.getItem('todos');
    if (saved) {
        JSON.parse(saved).forEach(text => {
            const todoItem = createTodoItem(text);
            todoList.appendChild(todoItem);
        });
    }
}

loadTodos();
Enter fullscreen mode Exit fullscreen mode

Part 5: Event Handling - Making It Interactive

Adding Event Listeners

const button = document.querySelector('.btn');

// Basic event listener
button.addEventListener('click', function() {
    console.log('Button clicked!');
});

// Arrow function (modern way)
button.addEventListener('click', () => {
    console.log('Arrow function click');
});

// Event object
button.addEventListener('click', (event) => {
    console.log('Clicked element:', event.target);
    console.log('Mouse position:', event.clientX, event.clientY);
});

// Multiple listeners on same event
button.addEventListener('click', handler1);
button.addEventListener('click', handler2);

// Remove event listener
button.removeEventListener('click', handler1);
Enter fullscreen mode Exit fullscreen mode

Event Delegation (Performance Boost)

// ❌ Bad: Adding listener to each item (slow for many items)
const items = document.querySelectorAll('.item');
items.forEach(item => {
    item.addEventListener('click', handleClick);
});

// ✅ Good: Single listener on parent (event delegation)
const list = document.querySelector('.list');
list.addEventListener('click', (e) => {
    if (e.target.classList.contains('item')) {
        handleItemClick(e.target);
    }
});
Enter fullscreen mode Exit fullscreen mode

Real-world example: Interactive FAQ Accordion

const faqContainer = document.querySelector('.faq-container');

faqContainer.addEventListener('click', (e) => {
    const question = e.target.closest('.faq-question');

    if (question) {
        const faqItem = question.parentElement;
        const answer = faqItem.querySelector('.faq-answer');
        const isOpen = faqItem.classList.contains('open');

        // Close all other items
        document.querySelectorAll('.faq-item').forEach(item => {
            item.classList.remove('open');
            item.querySelector('.faq-answer').style.maxHeight = null;
        });

        // Toggle current item
        if (!isOpen) {
            faqItem.classList.add('open');
            answer.style.maxHeight = answer.scrollHeight + 'px';
        }
    }
});
Enter fullscreen mode Exit fullscreen mode

Pro Tips for Better DOM Manipulation

1. Cache DOM queries (don't query repeatedly):

// ❌ Bad
document.querySelector('.btn').style.color = 'red';
document.querySelector('.btn').style.padding = '10px';

// ✅ Good
const btn = document.querySelector('.btn');
btn.style.color = 'red';
btn.style.padding = '10px';
Enter fullscreen mode Exit fullscreen mode

2. Use DocumentFragment for bulk inserts:

const fragment = document.createDocumentFragment();

for (let i = 0; i < 1000; i++) {
    const li = document.createElement('li');
    li.textContent = `Item ${i}`;
    fragment.appendChild(li);
}

// Single DOM update (fast!)
list.appendChild(fragment);
Enter fullscreen mode Exit fullscreen mode

3. Prefer classList over className:

// ❌ Bad (overwrites all classes)
element.className = 'new-class';

// ✅ Good (adds to existing)
element.classList.add('new-class');
Enter fullscreen mode Exit fullscreen mode

Conclusion: The Power is in Your Hands

Modern JavaScript has made DOM manipulation elegant and powerful. You don't need jQuery anymore. You might not even need React for many projects.

Master these techniques and you'll build faster, lighter, more maintainable web applications. The DOM is your playground—go create something amazing!


Ready to build without frameworks? 👏 Clap if this changed your perspective!

Want more vanilla JavaScript tutorials? 🔔 Follow me for framework-free web development tips.

Help others discover the power of vanilla JS! 📤 Share this guide with your developer friends.

What's your favorite DOM manipulation technique? Drop it in the comments! 💬


Tags: #JavaScript #WebDevelopment #DOM #VanillaJS #Frontend #Programming #WebDesign #Tutorial

Top comments (1)

Collapse
 
moopet profile image
Ben Sinclair

Does anyone use jQuery in 2025?