Vanilla JavaScript and Native DOM APIs as an Alternative to React and Other Frameworks

Contents
Vanilla JavaScript means using JavaScript and browser APIs without an additional UI library. For a small calculator, status display or independent page widget, this can reduce dependencies and downloaded code. For an application with shared state, routing and many coordinated screens, a framework can supply useful structure. The decision depends on the interface and maintenance needs, not a universal performance ranking.
Rendering costs need measurement
React calculates the desired interface during rendering and commits the necessary DOM changes. Browser layout and painting remain browser tasks. React, Angular and Vue do not share one identical update mechanism, and hydration is relevant when attaching client behavior to server-rendered HTML; not every application hydrates.
Large scripts and long synchronous tasks can delay interaction in either architecture. Direct DOM access can also trigger expensive layout work, especially when layout reads and writes alternate. Removing a library does not remove the application’s own work.
LCP describes when the largest eligible visible content element is rendered; server response, images, fonts, CSS and JavaScript can all matter. INP describes interaction responsiveness during the page visit. Smaller bundles can help, but neither dependency count nor use of CSS Grid guarantees good values. Comparison needs equivalent features, representative devices, laboratory diagnostics and field measurements.

Native building blocks
- Semantic HTML: native buttons, form fields and disclosure elements provide a useful starting point before custom scripting.
- Custom elements: reusable elements can react to connection and attribute changes. Shadow DOM optionally isolates an internal tree and styles; a custom element does not require Shadow DOM.
- Templates: a normal
<template>stores an inert fragment for cloning. Cloning and inserting a large tree still take time. - Event delegation: a container can handle suitable bubbling events from descendants. Non-bubbling events and shadow boundaries need separate treatment.
These features belong to HTML and DOM web standards. ECMAScript defines the JavaScript language, not the whole browser component model.
A status badge with explicit state handling
The following light-DOM component owns its child content and accepts only idle, ready and error. Unknown values fall back to idle. Status changes update the visible text after insertion, while reconnection keeps the existing nodes.
class CustomStatusBadge extends HTMLElement {
static observedAttributes = ['data-status'];
connectedCallback() {
if (!this.badge) {
this.badge = document.createElement('span');
this.dot = document.createElement('span');
this.dot.className = 'dot';
this.dot.setAttribute('aria-hidden', 'true');
this.label = document.createElement('span');
this.label.className = 'label';
this.badge.append(this.dot, this.label);
this.replaceChildren(this.badge);
}
this.render();
}
attributeChangedCallback() {
if (this.badge) this.render();
}
render() {
const raw = this.getAttribute('data-status');
const status = ['idle', 'ready', 'error'].includes(raw) ? raw : 'idle';
this.badge.className = 'badge badge-' + status;
this.label.textContent = 'Status: ' + status.toUpperCase();
}
}
customElements.define('status-badge', CustomStatusBadge);
The class is registered once, preferably from an external module. Example markup is <status-badge data-status="ready">Status: READY</status-badge>; the fallback text remains available before the module runs. Styling for .badge, .dot and the status classes is application-specific. The English example labels require localization in a multilingual interface.
DOM construction avoids an HTML parsing sink, and textContent inserts text. The allowlist also prevents arbitrary status values from adding unrelated CSS classes. This is a local component rule, not a complete application security policy: URLs, rich HTML and other output contexts require appropriate handling. The decorative dot is hidden from assistive technology; the text conveys the state without color. Automatic announcement of changes needs a deliberately designed live region and testing.
Questions and answers
When does a framework become worthwhile?
Shared state across many views, complex navigation, team conventions and established accessible components can justify a library. Maintenance effort and correctness matter alongside loading cost. Independent widgets often need less infrastructure; a large application does not automatically become simpler by removing dependencies.
Does Shadow DOM submit an internal input with an outer form?
An input inside a shadow tree is not automatically associated with the outer form. A form-associated custom element can use ElementInternals and setFormValue. Validation, reset behavior, labels and keyboard operation still require implementation and testing. Native controls remain a valuable option.
Does framework escaping solve every injection problem?
No. Ordinary text interpolation and explicit raw-HTML APIs have different behavior. The badge uses text insertion and fixed classes; arbitrary untrusted HTML must not be treated as ordinary text interpolation.
Sources
- React: Render and Commit
- React: hydrateRoot – Hydrating server-rendered HTML
- Angular: Component Lifecycle
- Google web.dev: Interaction to Next Paint (INP)
- Google web.dev: Largest Contentful Paint (LCP)
- WHATWG HTML Standard: Custom elements
- WHATWG HTML Standard: The template element
- WHATWG DOM Standard: Node.textContent
- MDN Web Docs: Web technology reference
- Google web.dev: Avoid large, complex layouts and layout thrashing
- React: Dangerously setting the inner HTML