When to Go Simple: The Vanilla HTML Case

Module 04: Web Architecture Patterns | Expansion Guide

Back to Module 04

The Problem

You're building a company landing page. Five static pages, a contact form, some animations. Your instinct: reach for React or Next.js. Why? Because that's what you know, that's what's on your resume, that's what everyone uses. AI scaffolds a Next.js project in seconds. You now have: a build process, 47 dependencies, 300MB node_modules, hydration to debug, and a 250KB bundle for what should be 20KB of HTML.

The problem: Framework overhead makes sense for complex apps but kills simple sites. AI makes both equally easy to build, so you never feel the cost until deployment.

Vanilla HTML/CSS/JS used to be harder because you'd reinvent basic functionality. AI changes that equation - it writes the vanilla code just as fast as framework code, but the vanilla version ships lighter and runs faster.

The Core Insight

With AI assistance, vanilla implementations are fast to build and maintain. For content-heavy sites with light interactivity, vanilla beats frameworks on every metric that matters: load time, bundle size, maintenance burden.

Modern web standards give you: CSS Grid, Flexbox, CSS Variables, async/await, Fetch API, IntersectionObserver, Web Components. AI knows all of these. You don't need React for layouts or jQuery for AJAX anymore.

When Vanilla Wins

Use Case 1: Marketing Sites

Profile: 5-20 pages, mostly static content, contact form, some animations.

Why vanilla:

What you gain:

// Prompt for AI:
"Create a marketing landing page with vanilla HTML/CSS/JS.

Requirements:
- Hero section with animated gradient background
- Features grid (3 columns, responsive)
- Testimonials carousel (no library, use IntersectionObserver)
- Contact form posting to FormSubmit.co
- Smooth scroll navigation

Design:
- Mobile-first with CSS Grid/Flexbox
- CSS custom properties for theming
- No frameworks, no build step
- Single HTML file acceptable (or minimal splitting)

Performance targets:
- < 50KB total HTML/CSS/JS
- Load in < 1 second on 3G
- 100 Lighthouse score"

Use Case 2: Documentation Sites (Simple)

Profile: Docs written in markdown, need search, responsive, fast.

Why vanilla (with a twist):

Actually, use a docs generator (Docusaurus, Mkdocs), but understand vanilla is the fallback for custom docs that don't fit a template:

// For custom documentation UI:
"Create a documentation page template using vanilla HTML/CSS/JS.

Features:
- Left sidebar navigation (auto-generated from headings)
- Sticky table of contents on right
- Code syntax highlighting (use Prism.js, only dependency)
- Copy button for code blocks
- Dark mode toggle (saves to localStorage)
- Search with Fuse.js for fuzzy matching

Structure:
- Main content is markdown converted to HTML
- Progressive enhancement: works without JS
- No build step required (but can run through markdown parser)"

Use Case 3: Prototypes and MVPs

Why vanilla for early stage:

You don't know if this product will work. Build the simplest version to validate:

// Prototype prompt:
"Build a minimal viable product for a [product idea] using vanilla HTML/CSS/JS.

MVP Features:
- [Core feature 1]
- [Core feature 2]
- [Core feature 3]

Constraints:
- Single page application (literally one HTML file if possible)
- All data in localStorage (no backend yet)
- Can demo to users immediately
- Easy to iterate - no build/deploy cycle

If this validates, we'll migrate to [real stack] later."

The PickBits.AI Case Study

This site is vanilla HTML/CSS/JS. Why? It's a content site with minimal interactivity. AI generated all pages following a consistent style. Deployment is drag-and-drop to S3. Load time is under 1 second globally. Maintenance is trivial. A React version would offer zero user benefits and significant developer costs.

Modern Vanilla Patterns

State Management Without React

// AI can generate clean vanilla state:
"Create a vanilla JS state management pattern using Proxy.

Requirements:
- Observable state changes
- React-like state updates
- Auto-update DOM when state changes
- No framework dependencies

Example usage:
const state = createState({ count: 0 })
state.count++ // DOM auto-updates"

AI output:

function createState(initialState) {
  const listeners = new Set()

  const handler = {
    set(target, property, value) {
      target[property] = value
      listeners.forEach(listener => listener(target))
      return true
    }
  }

  const state = new Proxy(initialState, handler)

  state.subscribe = (listener) => {
    listeners.add(listener)
    return () => listeners.delete(listener)
  }

  return state
}

// Usage:
const state = createState({ count: 0 })
state.subscribe(state => {
  document.querySelector('#count').textContent = state.count
})

document.querySelector('#increment').onclick = () => {
  state.count++
}

Component Pattern Without Framework

// Web Components for reusability:
"Create a reusable modal component using Web Components.

Requirements:
- Custom element: 
- Shadow DOM for style encapsulation
- Slot for content
- Accessible (ARIA, keyboard nav)
- No build step

Usage:

  

Title

Content here

"

Routing Without SPA Framework

// Hash-based routing for single-page feel:
"Create a vanilla JS router using URL hash.

Requirements:
- Route definitions like: { '/': HomePage, '/about': AboutPage }
- Handle browser back/forward
- Update nav highlighting
- No page reload
- Fallback to 404 page

Keep it under 50 lines."

When Vanilla Doesn't Work

Be honest about limitations. Reach for a framework when you hit these:

Sign It's Time for a Framework Why Vanilla Hurts
Component tree more than 3 levels deep Prop drilling and state become messy
Frequent data mutations requiring DOM updates You're reimplementing React's reconciliation
Team collaboration on complex UI Hard to maintain conventions without framework structure
Real-time data synchronization State management gets complex fast
Large codebase (>5000 lines of JS) Lack of structure creates spaghetti

The Progressive Enhancement Path

Start vanilla, add complexity incrementally:

  1. Pure HTML/CSS: Works with JS disabled, content is accessible
  2. + Vanilla JS: Enhance with interactions, save to localStorage
  3. + Alpine.js: Add reactivity without build step (good middle ground)
  4. + htmx: Add dynamic server interactions without much JS
  5. + React/Vue: Only when you've outgrown simpler options

Alpine.js: The Vanilla+ Sweet Spot

Alpine.js gives you Vue-like reactivity in HTML attributes. No build step, tiny footprint (15KB). AI knows Alpine well. Great middle ground between vanilla and full framework.

Deployment Advantages

Vanilla sites deploy anywhere cheaply:

Compare to Next.js SSR needing a Node server, or SPA needing special handling for client-side routing.

The "Just Add React" Trap

Easiest decision: add React for one interactive component. Hardest decision later: remove React when you realize it was overkill. Start vanilla, resist framework until pain is real, not anticipated.

Quick Reference

Choose Vanilla When:

Choose Framework When:

Vanilla Stack Recommendation:

HTML5 semantic elements
CSS Grid + Flexbox for layouts
CSS Custom Properties for theming
Vanilla JS (ES6+) for interactions
Web Components for reusable UI
Fetch API for async requests
IntersectionObserver for lazy loading
localStorage for client state
FormSubmit.co or similar for forms
Deploy to: S3, Netlify, GitHub Pages

Prompt Template for Vanilla:

"Create [feature] using vanilla HTML/CSS/JavaScript.

Requirements:
- No frameworks or build tools
- Modern browser features only (no IE11)
- Progressive enhancement (works without JS)
- Accessible (ARIA, semantic HTML, keyboard nav)
- Mobile-first responsive design

Constraints:
- Single file or minimal file structure
- Total size < 50KB (excluding images)
- No external dependencies except [specific tool if needed]

Follow web standards and best practices."