Still writing $('.element').hide()? That tiny jQuery snippet costs an HTTP request, extra parsing, and a function call that you can replace with a single line of native JavaScript. At Meteora Web, we see it every day in incoming projects: code that loads an 80KB library just to use three methods. Result? Slower pages, heavier bundles, and a waiting user. This guide shows you how to manipulate the DOM without jQuery, efficiently and modernly.
Why ditch jQuery for DOM manipulation?
jQuery was a game-changer when Internet Explorer ruled and the DOM API was a nightmare. Today, all modern browsers natively support querySelector, classList, addEventListener, and fetch. Keeping jQuery means:
- Extra payload: even minified, jQuery is ~30KB compressed. For a site that must load in under two seconds, every kilobyte matters.
- Dead dependencies: security updates, conflicts with other frameworks, unnecessary complexity.
- Performance: native DOM operations are faster because they don't go through an abstraction layer.
An e-commerce client had a carousel using jQuery for slide animations. Replacing it with requestAnimationFrame and classList.toggle reduced touch interaction time by 40%. Not theory — numbers.
Sponsored Protocol
How to select DOM elements without jQuery?
The first thing you learn with jQuery is $('.class'). Native JavaScript gives you two main weapons.
querySelector and querySelectorAll
Use document.querySelector('.class') for the first element, document.querySelectorAll('.class') for all. They accept any CSS selector, just like jQuery.
// jQuery
$('.item').addClass('active');
// Native
document.querySelectorAll('.item').forEach(el => el.classList.add('active'));
Note: querySelectorAll returns a static NodeList — not live. If you add or remove elements after selection, it won't update automatically. For dynamic lists, select each time you need.
getElementById and getElementsByClassName
For maximum performance, document.getElementById('id') is still fastest. getElementsByClassName returns a live HTMLCollection (updates automatically).
// Fast for ID
const header = document.getElementById('header');
// Live for classes
const slides = document.getElementsByClassName('slide');
console.log(slides.length); // updates if you add a slide
How to efficiently modify classes and styles?
jQuery: $('.box').addClass('red'). Native: classList.add('red'). Simple, but details matter.
Sponsored Protocol
classList: add, remove, toggle, contains
const el = document.querySelector('.box');
// Add
el.classList.add('highlighted');
// Remove
el.classList.remove('hidden');
// Toggle
el.classList.toggle('active');
// Check
if (el.classList.contains('active')) {
// do something
}
Watch out: classList does not accept space-separated class strings. To add multiple classes, pass multiple arguments: el.classList.add('a', 'b', 'c');.
Modifying inline styles
jQuery: $('.box').css('background', 'red');. Native:
el.style.background = 'red';
el.style.setProperty('--custom-prop', '10px');
For performance, avoid reading style inside loops. Read once, apply outside the loop. And prefer classes over inline styles: classes are more maintainable and leverage browser caching.
Sponsored Protocol
How to handle events without jQuery?
jQuery: $('.btn').on('click', fn). Native: addEventListener. But there's a trick many forget: event delegation.
Direct addEventListener
document.querySelector('.btn').addEventListener('click', function(e) {
console.log('Clicked!');
});
To remove, removeEventListener requires the same function (not anonymous).
Event delegation for dynamic elements
If you have a changing list (e.g., comments loaded via AJAX), don't attach a listener to each new element. Use delegation:
// jQuery
$('#list').on('click', '.item', fn);
// Native
document.querySelector('#list').addEventListener('click', function(e) {
const item = e.target.closest('.item');
if (item) {
// run action
}
});
Benefits: single listener, works for elements added later. closest is key to traverse up to the right parent.
How to create and insert DOM elements without jQuery?
jQuery: $('<div>').text('Hello').appendTo('.container');. Native: document.createElement and appendChild or insertAdjacentHTML.
createElement + append
const div = document.createElement('div');
div.textContent = 'Hello';
div.classList.add('message');
document.querySelector('.container').appendChild(div);
// More modern: append (multiple nodes)
document.querySelector('.container').append(div, anotherNode, 'text');
insertAdjacentHTML for raw HTML
If you have an HTML string, insertAdjacentHTML is faster than innerHTML because it doesn't destroy existing listeners:
Sponsored Protocol
document.querySelector('.container').insertAdjacentHTML('beforeend', '<div class="new">Content</div>');
Positions: beforebegin, afterbegin, beforeend, afterend.
Common mistakes in native DOM manipulation?
When we take over inherited projects, we always find the same errors. Avoiding them means performance and maintainability.
- Reading and writing DOM in loops: each DOM access is costly. Accumulate changes and apply once. Use
DocumentFragmentfor multiple insertions. - Using innerHTML without sanitization: opens XSS. Prefer
textContentfor text,setAttributefor attributes. - Forgetting to remove listeners: creates memory leaks, especially in SPAs. Use
AbortControllerto remove them in bulk. - Not using delegation: if you have 100 dynamic elements, don't attach 100 listeners. One is enough.
What to do now
- Audit your code: search for
$()orjQueryin your project. Evaluate if you can replace each occurrence with native. - Use a polyfill only if necessary: for old browsers,
classListandquerySelectorare supported from IE10+. If you need IE9, consider a small polyfill instead of full jQuery. - Measure performance: open DevTools, record DOM operations and compare before/after. Numbers will convince you.
- Learn
closestandmatches: they are the replacements for jQuery'sparents()andis().
We at Meteora Web have gradually removed jQuery from all new projects for years. Not for fashion, but because native is more performant, lighter, and easier to maintain. If you're building a site today, there's no reason to load a legacy library. The DOM API is mature. Use it.
Sponsored Protocol
For the modern JavaScript context, check out our Pillar: Modern JavaScript ES2024+.