For many jobs once handled by jQuery, modern browser APIs are now the lowest-complexity default: selectors, events, classes, fetch, traversal, and DOM updates work directly in current browsers.
01 — From jQuery to Modern Browser APIs
jQuery → Native JavaScript
$('.item')$('#save')$(el).on('click', fn)$(el).addClass('active')$(el).removeClass('active')$(el).toggleClass('active')$(el).hasClass('active')$(el).closest('.card')$(el).find('.child')$(el).attr('data-id')$(el).attr('data-id', '42')$(el).text('Saved')$(el).html(markup)$.ajax(...) / $.get(...)$(el).remove()Native DOM Best Practices
Native Example: Toggle
const button = document.querySelector('#menuButton');
const menu = document.querySelector('#menu');
button.addEventListener('click', () => {
const open = button.getAttribute('aria-expanded') === 'true';
button.setAttribute('aria-expanded', String(!open));
menu.hidden = open;
});Native Example: GET JSON
const response = await fetch('/api/users');
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const users = await response.json();MDN References
Use these when native JavaScript is possible but a small declarative or convenience layer makes repeated interaction easier. They solve different problems; they are not interchangeable.
02 — Lightweight jQuery Alternatives
Alpine.js
npm install alpinejs or include Alpine with a deferred script tag.x-data defines local state; x-on/@ handles events; x-show, x-bind, and x-text bind UI to state.<div x-data="{ open: false }">
<button @click="open = !open">Menu</button>
<nav x-show="open">...</nav>
</div>htmx
npm install htmx.org, self-host the file, or load it from a CDN.<button hx-get="/users" hx-target="#results" hx-swap="innerHTML">
Load users
</button>
<div id="results"></div>Stimulus
<div data-controller="hello">
<input data-hello-target="name">
<button data-action="click->hello#greet">Greet</button>
</div>Cash
npm install cash-dom or use its distributed browser build.When to Choose Which
Native JSAlpine.jshtmxStimulusCashOfficial Downloads & Docs
React, Vue, Svelte, and Lit are not drop-in jQuery replacements. They are better choices when the UI itself has significant state, reusable components, or application-scale interaction.
03 — Component & Application Alternatives
React
function Counter() {
const [count, setCount] = React.useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}Vue
<script setup>
import { ref } from 'vue';
const count = ref(0);
</script>
<template><button @click="count++">{{ count }}</button></template>Svelte
<script>
let count = $state(0);
</script>
<button onclick={() => count++}>{count}</button>Lit
npm i litimport {LitElement, html} from 'lit';
class MyCounter extends LitElement {
static properties = { count: {type: Number} };
constructor(){ super(); this.count = 0; }
render(){ return html`<button @click=${() => this.count++}>${this.count}</button>`; }
}
customElements.define('my-counter', MyCounter);Architecture Choice
Native / Alpine / Stimulus / htmxReact / Vue / SvelteLitAvoid framework-by-defaultVite as Modern Build Tooling
npm create vite@latestnpm run devnpm run buildOfficial Docs
Replacing jQuery is usually an architecture decision, not a syntax exercise. Move simple behaviors to the platform; move complex state to a coherent component or server-driven model.
04 — Migration & Best Implementation Patterns
Migration Decision Tree
Simple selectors/events/classes?Small local state?Server returns HTML?Complex client state/components?Reusable cross-framework component?Large jQuery legacy?Replace Patterns, Not Just Calls
Event Delegation Example
$(document).on('click', '[data-remove]', function () {
$(this).closest('.item').remove();
});document.addEventListener('click', (event) => {
const button = event.target.closest('[data-remove]');
if (!button) return;
button.closest('.item')?.remove();
});Fetch POST Example
const response = await fetch('/api/items', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({name: 'Example'})
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const item = await response.json();Dependency Selection Rules
Accessibility & Resilience
No-JS / failure modeUse browser automation for repeatable regression coverage. On macOS, Playwright is a strong default because one tool can exercise Chromium, Firefox, and WebKit plus desktop/mobile device profiles.
05 — Cross-Browser & Cross-Device Automation
Playwright — Recommended Default
npm init playwright@latestnpx playwright installnpx playwright testnpx playwright test --project=webkitCypress
npm install cypress --save-devnpx cypress opennpx cypress runSelenium WebDriver
WebdriverIO
npm create wdio@latest ./Testing Pyramid for Front-End
Manual responsive passComponent/unit testsE2E automationSimulator/VMPhysical deviceOfficial Downloads & Docs
Responsive modes are fast approximations; simulators and browser engines are closer; virtual machines add OS coverage; physical devices remain the final authority for device-specific behavior.
06 — macOS Device, Simulator & VM Testing
Safari Responsive Design Mode
Xcode Simulator — Free, Not Open Source
Firefox Responsive Design Mode
Cmd + Option + MUTM — Open-Source VM Choice for Mac
VirtualBox — Secondary Open VM Option
Real Device over Local Network
Recommended macOS Stack
Daily responsive workAutomated browser regressionApple platform validationWindows/Linux OS coverageRelease confidence