Why I Still Write Vanilla JavaScript in 2026
Modern frameworks solve real problems, but reaching for vanilla JS first has made me a better engineer and delivered faster, more maintainable projects.

The Framework Fatigue is Real
I spent years riding the framework carousel. Angular gave way to React, which spawned Next.js, while Vue competed for mindshare and Svelte promised to compile it all away. Each promised to solve the problems of its predecessor. Each added another layer of abstraction, another build step, another CLI tool to learn.
Then I started a greenfield project last year with a simple requirement: display real-time data updates and let users filter a list. My first instinct was to npx create-react-app. Instead, I opened a blank HTML file and wrote vanilla JavaScript. The project shipped in a third of the time I estimated, with zero dependencies and a 12KB JavaScript bundle.
That experience changed how I approach web development.
What Vanilla JavaScript Actually Means
When I say "vanilla JavaScript," I don't mean avoiding all modern features. I write ES6+ without hesitation. I use modules, async/await, and destructuring. I transpile with Babel when I need to support older browsers.
What I avoid is the framework runtime. No virtual DOM reconciliation. No reactive state management library. No component lifecycle methods to memorize. Just the browser APIs that have been stable for years and will remain stable for years more.
Here's a real example from that project—a filtered list that updates in real time:
class FilterableList {
constructor(container, items) {
this.container = container;
this.items = items;
this.filtered = items;
this.render();
}
filter(predicate) {
this.filtered = this.items.filter(predicate);
this.render();
}
render() {
this.container.innerHTML = this.filtered
.map(item => `<li data-id="${item.id}">${item.name}</li>`)
.join('');
}
update(newItems) {
this.items = newItems;
this.filter(item => this.filtered.find(f => f.id === item.id));
}
}
const list = new FilterableList(
document.getElementById('list'),
await fetch('/api/items').then(r => r.json())
);
document.getElementById('search').addEventListener('input', (e) => {
list.filter(item => item.name.includes(e.target.value));
});
This isn't elegant framework code, but it's explicit. When a bug appears, I know exactly where to look. When requirements change, I modify the class directly. There's no magic, no hidden re-renders, no memo hooks to optimize.
The Bundle Size Argument
Every framework advocate claims their framework is "only X kilobytes gzipped." React is 42KB. Vue is 34KB. Svelte compiles away, but still adds runtime code for reactivity. These numbers don't include the ecosystem: state management, routing, form libraries, animation frameworks.
My recent projects average 15-30KB of JavaScript total, including all features. One production app serves 50,000 monthly users with 18KB of JavaScript and zero framework code. It uses Web Components for reusable UI, the Fetch API for data, and CSS for 90% of animations.
The performance difference is measurable. First contentful paint consistently under 0.8s on 3G. Time to interactive under 1.2s. Lighthouse scores in the high 90s without optimization gymnastics.
When Frameworks Make Sense
I'm not arguing against frameworks categorically. I use Next.js for content-heavy sites where SSR and routing complexity justify the overhead. I reach for React when building truly interactive applications with dozens of interdependent UI states.
The key is proportionality. A landing page doesn't need React. A form with client-side validation doesn't need Vue. A real-time dashboard with five widgets doesn't need Angular.
Frameworks excel when:
- You have a large team needing consistent patterns
- The application has complex state shared across many components
- You need server-side rendering with hydration
- You're building something genuinely app-like (think Figma, not a blog)
For everything else, vanilla JavaScript with modern browser APIs handles the job with less code, fewer bugs, and better performance.
The Browser Platform Has Grown Up
The landscape that spawned frameworks has changed. In 2014, we needed React because innerHTML was slow and DOM manipulation was painful. jQuery bridged browser inconsistencies. Backbone gave us structure when JavaScript had no modules.
Today's browsers ship with:
- Native modules (
import/export) - Fetch API for HTTP requests
- Web Components for encapsulated, reusable UI
- CSS Grid and Flexbox for layouts that used to need JavaScript
- Intersection Observer for lazy loading
- CSS animations that outperform JavaScript
- Request Animation Frame for smooth updates
Here's a Web Component that I use across multiple projects:
class LoadingButton extends HTMLElement {
connectedCallback() {
this.button = this.querySelector('button');
this.button.addEventListener('click', this.handleClick.bind(this));
}
async handleClick(e) {
e.preventDefault();
if (this.hasAttribute('loading')) return;
this.setAttribute('loading', '');
this.button.disabled = true;
try {
await this.onAction();
} finally {
this.removeAttribute('loading');
this.button.disabled = false;
}
}
async onAction() {
// Override in usage
}
}
customElements.define('loading-button', LoadingButton);
No build step required. Works in every modern browser. Reusable across projects without npm.
Developer Experience Matters Less Than You Think
The argument I hear most is developer experience. Hot module reloading, component dev tools, TypeScript integration—frameworks provide these out of the box.
But developer experience is a means, not an end. I optimize for:
- Time to fix bugs (faster with less abstraction)
- Onboarding speed (vanilla JS has no learning curve)
- Long-term maintenance (no framework upgrades, no breaking changes)
- Runtime performance (users don't care about my dev experience)
I still use TypeScript for type safety. I still use Vite for hot reloading during development. I still write tests. None of these require a UI framework.
The Path Forward
I'm not advocating a return to jQuery and <script> tags. I'm suggesting we've overcorrected. We bundled framework overhead into every project by default, when most projects don't need it.
Start with vanilla JavaScript. Add complexity only when the problem demands it. You might be surprised how far the browser platform takes you on its own.