A single reference for the small things you used to reach for jQuery to do. Every snippet here is dependency-free, modern vanilla JavaScript that runs in any current browser. Jump to what you need:
- Run code when the DOM is ready
- Hide and show an element
- Check, add and toggle a class
- Change the page title
- Copy content from one element to another
- Build an HTML list from an array
- slideToggle without jQuery
- Make an iframe responsive
- Open a centered popup window
- A tiny confirmation dialog
- Inject CSS rules at runtime
- Load an external stylesheet on demand
- Load a script on the fly
- Read and update URL query parameters
- localStorage with an expiry
- Defer (lazy-load) images
- Fetch content from another URL
- Set a global from inside a function
Run code when the DOM is ready
The replacement for $(document).ready(). This runs the callback immediately if the document has already parsed, and waits otherwise — so it works no matter where the script is placed.
function ready(fn) {
if (document.readyState !== 'loading') {
fn();
} else {
document.addEventListener('DOMContentLoaded', fn);
}
}
ready(() => {
// your code here
});
Hide and show an element
Use the hidden property for a clean semantic toggle. Reach for style.display only when you need a specific display value back.
const box = document.querySelector('#box');
box.hidden = true; // hide
box.hidden = false; // show
box.toggleAttribute('hidden'); // toggle
// When you need display control instead:
box.style.display = 'none';
box.style.display = ''; // revert to the stylesheet value
Check, add and toggle a class
The classList API covers everything hasClass, addClass and toggleClass did.
const el = document.querySelector('.panel');
el.classList.contains('open'); // hasClass
el.classList.add('open'); // addClass
el.classList.remove('open'); // removeClass
el.classList.toggle('open'); // toggleClass
el.classList.toggle('open', isOpen); // force on/off with a condition
Change the page title
One property. A common use is flashing a message when the tab loses focus.
document.title = 'New page title';
// Flash a message when the visitor switches tabs:
const original = document.title;
document.addEventListener('visibilitychange', () => {
document.title = document.hidden ? 'Come back! 👋' : original;
});
Copy content from one element to another
Copy markup with innerHTML, or clone the actual nodes when you want to preserve structure without re-parsing a string.
const source = document.querySelector('#source');
const target = document.querySelector('#target');
// Copy the markup as a string:
target.innerHTML = source.innerHTML;
// Or move/clone the real nodes:
target.replaceChildren(...source.cloneNode(true).childNodes);
Build an HTML list from an array
Two ways: build real nodes (safe with untrusted text), or assemble a string (terser for trusted data).
const items = ['Alpha', 'Beta', 'Gamma'];
// Node-based — safe, no HTML injection:
const ul = document.createElement('ul');
ul.append(...items.map((text) => {
const li = document.createElement('li');
li.textContent = text;
return li;
}));
document.querySelector('#list').replaceWith(ul);
// String-based — fine for trusted, escaped data:
const html = `<ul>${items.map((i) => `<li>${i}</li>`).join('')}</ul>`;
slideToggle without jQuery
If you used jQuery only for a couple of effects, this is the drop-in replacement for slideToggle(). The same three functions also give you slideUp() and slideDown(). It animates height, margin and padding with a CSS transition, then cleans the inline styles up afterwards.
const slideUp = (target, duration = 500) => {
target.style.transitionProperty = 'height, margin, padding';
target.style.transitionDuration = duration + 'ms';
target.style.boxSizing = 'border-box';
target.style.height = target.offsetHeight + 'px';
target.offsetHeight; // force reflow
target.style.overflow = 'hidden';
target.style.height = 0;
target.style.paddingTop = 0;
target.style.paddingBottom = 0;
target.style.marginTop = 0;
target.style.marginBottom = 0;
window.setTimeout(() => {
target.style.display = 'none';
['height','padding-top','padding-bottom','margin-top','margin-bottom',
'overflow','transition-duration','transition-property']
.forEach((p) => target.style.removeProperty(p));
}, duration);
};
const slideDown = (target, duration = 500) => {
target.style.removeProperty('display');
let display = window.getComputedStyle(target).display;
if (display === 'none') display = 'block';
target.style.display = display;
const height = target.offsetHeight;
target.style.overflow = 'hidden';
target.style.height = 0;
target.style.paddingTop = 0;
target.style.paddingBottom = 0;
target.style.marginTop = 0;
target.style.marginBottom = 0;
target.offsetHeight; // force reflow
target.style.boxSizing = 'border-box';
target.style.transitionProperty = 'height, margin, padding';
target.style.transitionDuration = duration + 'ms';
target.style.height = height + 'px';
['padding-top','padding-bottom','margin-top','margin-bottom']
.forEach((p) => target.style.removeProperty(p));
window.setTimeout(() => {
['height','overflow','transition-duration','transition-property']
.forEach((p) => target.style.removeProperty(p));
}, duration);
};
const slideToggle = (target, duration = 500) =>
window.getComputedStyle(target).display === 'none'
? slideDown(target, duration)
: slideUp(target, duration);
// Usage:
document.querySelector('#toggle').addEventListener('click', (e) => {
e.preventDefault();
slideToggle(document.querySelector('#toggleMe'), 300);
});
Make an iframe responsive
The modern answer is pure CSS — no JavaScript and no resize listener.
/* CSS */
.iframe-wrap { aspect-ratio: 16 / 9; }
.iframe-wrap iframe { width: 100%; height: 100%; border: 0; }
If you must size it from JavaScript (mixed ratios, legacy markup), keep it to one resize handler:
function resizeIframes() {
document.querySelectorAll('iframe[data-ratio]').forEach((frame) => {
const [w, h] = frame.dataset.ratio.split(':').map(Number);
frame.style.height = `${frame.clientWidth * (h / w)}px`;
});
}
window.addEventListener('resize', resizeIframes);
resizeIframes();
Open a centered popup window
Centers across multi-monitor setups by accounting for the screen offset.
function openCenteredPopup(url, title, w, h) {
const dualLeft = window.screenLeft ?? screen.left;
const dualTop = window.screenTop ?? screen.top;
const width = window.innerWidth || document.documentElement.clientWidth || screen.width;
const height = window.innerHeight || document.documentElement.clientHeight || screen.height;
const left = (width - w) / 2 + dualLeft;
const top = (height - h) / 2 + dualTop;
return window.open(url, title,
`scrollbars=yes,width=${w},height=${h},top=${top},left=${left}`);
}
// Usage:
openCenteredPopup('https://example.com/share', 'share', 600, 480);
A tiny confirmation dialog
The native <dialog> element gives you a real modal — focus trapping and Escape-to-close included — with no library. This wrapper returns a promise that resolves to true or false.
function confirmDialog(message) {
return new Promise((resolve) => {
const dialog = document.createElement('dialog');
dialog.innerHTML = `
<form method="dialog">
<p>${message}</p>
<menu>
<button value="cancel">Cancel</button>
<button value="confirm">OK</button>
</menu>
</form>`;
document.body.appendChild(dialog);
dialog.addEventListener('close', () => {
resolve(dialog.returnValue === 'confirm');
dialog.remove();
});
dialog.showModal();
});
}
// Usage:
if (await confirmDialog('Delete this item?')) {
// proceed
}
Inject CSS rules at runtime
Add a block of CSS from JavaScript by creating a <style> element, or push a single rule into an existing sheet.
// A block of rules:
const style = document.createElement('style');
style.textContent = `.note { color: #c00; font-weight: 600; }`;
document.head.appendChild(style);
// A single rule into the first stylesheet:
const sheet = document.styleSheets[0];
sheet.insertRule('.note { color: #c00; }', sheet.cssRules.length);
Load an external stylesheet on demand
Append a <link> and resolve when it has loaded — handy for deferring non-critical CSS.
function loadStylesheet(href) {
return new Promise((resolve, reject) => {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = href;
link.onload = resolve;
link.onerror = reject;
document.head.appendChild(link);
});
}
await loadStylesheet('/css/print.css');
Load a script on the fly
Inject a script and get a promise back, so you can await a third-party library before using it.
function loadScript(src) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = src;
script.async = true;
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
}
await loadScript('https://cdn.example.com/widget.js');
// widget is now available
Read and update URL query parameters
URLSearchParams handles parsing and serialization. Pair it with history.replaceState to update the address bar without a reload.
const params = new URLSearchParams(location.search);
params.get('id'); // read
params.has('id'); // check
params.set('page', '2'); // add or update
params.delete('ref'); // remove
// Reflect changes in the URL without reloading:
history.replaceState(null, '', `${location.pathname}?${params}`);
localStorage with an expiry
localStorage never expires on its own. Wrap values with a timestamp to get cookie-style TTLs without cookies.
function setItem(key, value, ttlMs) {
const record = { value, expiry: Date.now() + ttlMs };
localStorage.setItem(key, JSON.stringify(record));
}
function getItem(key) {
const raw = localStorage.getItem(key);
if (!raw) return null;
const { value, expiry } = JSON.parse(raw);
if (Date.now() > expiry) {
localStorage.removeItem(key);
return null;
}
return value;
}
// Usage — remember a dismissed banner for one day:
setItem('bannerDismissed', true, 24 * 60 * 60 * 1000);
Defer (lazy-load) images
Native lazy-loading covers most cases with a single attribute:
<img src="photo.jpg" loading="lazy" alt="…">
For full control (placeholders, custom thresholds), swap a data-src in with an IntersectionObserver:
const io = new IntersectionObserver((entries, observer) => {
entries.forEach((entry) => {
if (!entry.isIntersecting) return;
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img);
});
}, { rootMargin: '200px' });
document.querySelectorAll('img[data-src]').forEach((img) => io.observe(img));
Fetch content from another URL
fetch replaces $.get / $.ajax for loading remote HTML or data.
async function getContent(url) {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.text(); // or response.json()
}
const html = await getContent('/fragments/sidebar.html');
document.querySelector('#sidebar').innerHTML = html;
Cross-origin note: the browser will only let you read the response if the other site sends an Access-Control-Allow-Origin header that permits your domain. For sites that don’t, you need a small server-side proxy on your own domain to fetch on your behalf.
Set a global from inside a function
The old “my variable disappears outside the jQuery callback” problem is just scope. Don’t rely on implicit globals — attach to window explicitly, or better, keep a binding in module scope and avoid the global entirely.
// Explicit global (clear, but pollutes window):
function init() {
window.appConfig = { ready: true };
}
// Preferred — module/outer scope, no global:
let appConfig;
function init() {
appConfig = { ready: true };
}
// appConfig is now readable by any function in the same module
Navigate browser history (back, forward, reload)
I once had a shop client who needed customers to jump back several steps in one click while keeping their session intact. The browser’s history object handles this without any library — no jQuery required.
// Go back one entry (same as the browser's Back button)
history.back();
// Go back three entries in one go
history.go(-3);
// Go forward one entry
history.forward();
// Reload the current page
history.go(0);
You can wire these to a link with href="javascript:history.go(-3)", or attach them to a button’s click handler. history.go(n) accepts a positive or negative integer and moves that many entries through the history list (when the requested entry exists); these methods are supported in all major browsers.