A Volta do Vanilla JavaScript em 2026: Menos Frameworks, Mais Clareza
Olá HaWkers, algo interessante está acontecendo no ecossistema JavaScript. Depois de anos de "guerra dos frameworks", um movimento crescente de desenvolvedores está escolhendo conscientemente usar JavaScript puro em novos projetos. Não por falta de conhecimento, mas por clareza de propósito.
Por que desenvolvedores experientes estão voltando ao básico? E quando Vanilla JS faz mais sentido que um framework?
O Movimento Vanilla
Não é regressão, é evolução.
Por Que Agora
Fatores que impulsionam a mudança:
APIs nativas maduras:
- Fetch API estável e poderosa
- Web Components suportados em todos browsers
- ES Modules nativos funcionando
- CSS Container Queries e :has()
- Dialog, Popover, e outras APIs nativas
Fadiga de frameworks:
- Novo framework toda semana
- Breaking changes constantes
- Curva de aprendizado infinita
- Dependências desatualizadas
Performance importa mais:
- Core Web Vitals afetam SEO
- Usuários em dispositivos limitados
- Custo de JavaScript alto demais
- TTI (Time to Interactive) crítico
O Que Mudou no JavaScript Nativo
A linguagem evoluiu muito.
APIs Modernas
O que antes precisava de biblioteca:
// Antes: jQuery para AJAX
$.ajax({
url: '/api/users',
method: 'GET',
success: function(data) { /* ... */ }
});
// Agora: Fetch nativo
const response = await fetch('/api/users');
const users = await response.json();
// Antes: Lodash para debounce
import { debounce } from 'lodash';
const debouncedFn = debounce(fn, 300);
// Agora: Scheduler API (ou simples implementação)
const debouncedFn = (fn, delay) => {
let timeoutId;
return (...args) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn(...args), delay);
};
};
// Antes: Moment.js para datas
moment('2026-01-15').format('DD/MM/YYYY');
// Agora: Intl.DateTimeFormat nativo
new Intl.DateTimeFormat('pt-BR').format(new Date('2026-01-15'));
// Ou em breve: Temporal APIDOM Manipulation Moderna
Sem jQuery, com poder:
// Seleção de elementos
const button = document.querySelector('.submit-btn');
const items = document.querySelectorAll('.item');
// Event delegation eficiente
document.querySelector('.list').addEventListener('click', (e) => {
if (e.target.matches('.item')) {
handleItemClick(e.target);
}
});
// Manipulação de classes
element.classList.add('active', 'visible');
element.classList.toggle('open');
element.classList.replace('old', 'new');
// Data attributes
element.dataset.userId = '123';
const id = element.dataset.userId;
// Template literals para HTML
const html = `
<div class="card">
<h2>${title}</h2>
<p>${description}</p>
</div>
`;
container.insertAdjacentHTML('beforeend', html);
// Intersection Observer (lazy loading nativo)
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
loadImage(entry.target);
observer.unobserve(entry.target);
}
});
});
document.querySelectorAll('img[data-src]').forEach(img => {
observer.observe(img);
});
Web Components Nativos
Componentes sem framework.
Custom Elements
Criando componentes reutilizáveis:
// Definição do componente
class UserCard extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
static get observedAttributes() {
return ['name', 'avatar', 'role'];
}
connectedCallback() {
this.render();
}
attributeChangedCallback(name, oldValue, newValue) {
if (oldValue !== newValue) {
this.render();
}
}
render() {
const name = this.getAttribute('name') || 'Unknown';
const avatar = this.getAttribute('avatar') || '/default.png';
const role = this.getAttribute('role') || 'User';
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
padding: 1rem;
border-radius: 8px;
background: var(--card-bg, #fff);
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.card {
display: flex;
align-items: center;
gap: 1rem;
}
img {
width: 48px;
height: 48px;
border-radius: 50%;
}
h3 { margin: 0; }
span { color: #666; font-size: 0.9rem; }
</style>
<div class="card">
<img src="${avatar}" alt="${name}">
<div>
<h3>${name}</h3>
<span>${role}</span>
</div>
</div>
`;
}
}
customElements.define('user-card', UserCard);<!-- Uso no HTML -->
<user-card
name="João Silva"
avatar="/avatars/joao.jpg"
role="Desenvolvedor Senior">
</user-card>Slots e Composição
Componentes flexíveis:
class Modal extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
:host {
display: none;
position: fixed;
inset: 0;
background: rgba(0,0,0,0.5);
place-items: center;
}
:host([open]) {
display: grid;
}
.modal {
background: white;
padding: 2rem;
border-radius: 8px;
max-width: 500px;
width: 90%;
}
::slotted([slot="header"]) {
margin-top: 0;
}
</style>
<div class="modal">
<slot name="header"></slot>
<slot></slot>
<slot name="footer"></slot>
</div>
`;
// Fechar ao clicar fora
this.addEventListener('click', (e) => {
if (e.target === this) {
this.close();
}
});
}
open() {
this.setAttribute('open', '');
}
close() {
this.removeAttribute('open');
this.dispatchEvent(new CustomEvent('modal-close'));
}
}
customElements.define('app-modal', Modal);<app-modal id="confirmModal">
<h2 slot="header">Confirmar Ação</h2>
<p>Tem certeza que deseja continuar?</p>
<div slot="footer">
<button onclick="confirmModal.close()">Cancelar</button>
<button onclick="confirm()">Confirmar</button>
</div>
</app-modal>
Estado Sem Framework
Gerenciando estado com JavaScript puro.
Store Simples
Padrão pub/sub minimalista:
// store.js - Estado global simples
function createStore(initialState = {}) {
let state = initialState;
const listeners = new Set();
return {
getState() {
return state;
},
setState(newState) {
state = typeof newState === 'function'
? newState(state)
: { ...state, ...newState };
listeners.forEach(listener => listener(state));
},
subscribe(listener) {
listeners.add(listener);
return () => listeners.delete(listener);
}
};
}
// Uso
const store = createStore({
user: null,
items: [],
loading: false
});
// Componente se inscreve
const unsubscribe = store.subscribe((state) => {
renderUserInfo(state.user);
renderItems(state.items);
});
// Atualizar estado
store.setState({ loading: true });
const items = await fetchItems();
store.setState({ items, loading: false });Proxy Reativo
Reatividade sem biblioteca:
// Proxy para estado reativo
function reactive(target, onChange) {
return new Proxy(target, {
set(obj, prop, value) {
const oldValue = obj[prop];
obj[prop] = value;
if (oldValue !== value) {
onChange(prop, value, oldValue);
}
return true;
},
get(obj, prop) {
const value = obj[prop];
if (typeof value === 'object' && value !== null) {
return reactive(value, onChange);
}
return value;
}
});
}
// Uso
const state = reactive({
count: 0,
user: { name: 'João' }
}, (prop, newValue, oldValue) => {
console.log(`${prop} changed: ${oldValue} → ${newValue}`);
updateUI();
});
state.count++; // Dispara onChange
state.user.name = 'Maria'; // Também funciona em objetos aninhados
Quando Usar Vanilla JS
Escolhendo conscientemente.
Casos Ideais
Onde Vanilla JS brilha:
Landing pages:
- Interatividade limitada
- Performance crítica
- SEO importante
- Conteúdo majoritariamente estático
Widgets embeddable:
- Sem conflito com host
- Bundle mínimo
- Independência total
Bibliotecas e ferramentas:
- Sem assumir framework do usuário
- Máxima compatibilidade
- Menor footprint
Sites de conteúdo:
- Blogs, documentação
- Portfólios
- Sites institucionais
Quando Framework Ainda Faz Sentido
Não é tudo ou nada:
SPAs complexas:
- Muitos estados interdependentes
- Roteamento client-side extenso
- Time grande trabalhando junto
Aplicações em tempo real:
- Dashboards com muitas atualizações
- Chats e colaboração
- Interfaces altamente dinâmicas
Produtividade do time:
- Time já conhece o framework
- Ecossistema maduro necessário
- Padrões estabelecidos
Ferramentas Para Vanilla JS
Desenvolvimento moderno sem framework.
Build Tools
Configuração mínima:
// vite.config.js para projeto vanilla
import { defineConfig } from 'vite';
export default defineConfig({
build: {
rollupOptions: {
input: {
main: 'index.html',
about: 'about.html'
}
}
}
});TypeScript Funciona
Tipagem sem framework:
// types.ts
interface User {
id: string;
name: string;
email: string;
}
interface AppState {
user: User | null;
items: Item[];
loading: boolean;
}
// store.ts
function createStore<T>(initialState: T) {
let state = initialState;
const listeners = new Set<(state: T) => void>();
return {
getState(): T {
return state;
},
setState(newState: Partial<T> | ((prev: T) => T)): void {
state = typeof newState === 'function'
? newState(state)
: { ...state, ...newState };
listeners.forEach(listener => listener(state));
},
subscribe(listener: (state: T) => void): () => void {
listeners.add(listener);
return () => listeners.delete(listener);
}
};
}
const store = createStore<AppState>({
user: null,
items: [],
loading: false
});Comparativo de Bundle Size
Números que importam:
| Abordagem | Bundle Size | First Paint |
|---|---|---|
| Vanilla JS | ~5 KB | ~50ms |
| Preact | ~10 KB | ~80ms |
| Vue 3 | ~35 KB | ~120ms |
| React | ~45 KB | ~150ms |
| Angular | ~100 KB | ~250ms |
💡 Nota: Esses números são aproximados e variam conforme a aplicação. O ponto é que Vanilla JS tem overhead zero de framework.
A volta do Vanilla JavaScript não é sobre rejeitar frameworks - é sobre escolher a ferramenta certa para cada trabalho. Com APIs nativas maduras, Web Components estáveis e a linguagem mais poderosa do que nunca, JavaScript puro é uma escolha legítima e muitas vezes superior.
Se você quer dominar os fundamentos que nunca mudam, recomendo ver outro artigo: ES2026 e Temporal API onde você descobrirá as novas features nativas da linguagem.
Bora pra cima! 🦅
💻 Domine JavaScript de Verdade
O conhecimento que você adquiriu neste artigo é só o começo. Dominar JavaScript puro é a base que todo desenvolvedor precisa.
Invista no Seu Futuro
Preparei material completo para você dominar JavaScript:
Formas de pagamento:
- 1x de R$27,00 sem juros
- ou R$27,00 à vista no Pix

