HTML5 Web Platform

Dynamic
Sites Without
a Server

Everything PHP gave you — forms, sessions, databases, user auth, search, file uploads, real-time updates — reimplemented in pure HTML5 APIs. Each section below is a fully working application demo, not just a code example.

localStorage CRUD Fetch API File Reader SPA Routing ● 17 Working Apps

§ 01 — Replacing PHP form validation

Forms & Validation

HTML5's Constraint Validation API gives you required, pattern, minlength, email/url/number types with native validation — plus setCustomValidity() for custom error messages, all without server round-trips.

LIVE — Full Form with Real-time Validation
At least 2 characters
At least 2 characters
Please enter a valid email
Enter a password
Enter a valid URL starting with https://
Must be between 18 and 120
form-validation.html + js
<!-- HTML5 built-in constraint attributes -->
<input type="email"    required>
<input type="url"      required>
<input type="number"   min="18"  max="120">
<input type="text"     pattern="[A-Za-z]{3,}"
               minlength="3"  maxlength="50">
<input type="tel"      pattern="[\d\-\+\s]{7,15}">

// Constraint Validation API
const inp = document.querySelector('#email');

// Check validity
inp.validity.valid          // boolean
inp.validity.valueMissing   // required but empty
inp.validity.typeMismatch   // bad type (email, url)
inp.validity.patternMismatch// failed pattern=
inp.validity.tooShort       // below minlength
inp.validity.rangeUnderflow // below min
inp.validationMessage       // browser error string

// Custom error message
inp.setCustomValidity('Username is taken!');
inp.setCustomValidity('');   // clear error
inp.reportValidity();        // show bubble

// Form submit handler (prevent default & handle)
form.addEventListener('submit', e => {
  e.preventDefault();
  if (!form.checkValidity()) { showErrors(); return; }
  processForm(new FormData(form));
});

§ 02 — Replacing PHP + MySQL CRUD

CRUD — Todo App

A full Create, Read, Update, Delete app using localStorage as the database. Data persists across browser sessions — just like a PHP+MySQL app, but entirely client-side.

LIVE APP — Data saves to localStorage
  • Add your first task above!
Total: 0
Active: 0
Done: 0
localStorage-crud.js
// ── localStorage as a Database ────────────────
// Save (like INSERT/UPDATE)
function save(items) {
  localStorage.setItem('todos', JSON.stringify(items));
}
// Load (like SELECT *)
function load() {
  return JSON.parse(localStorage.getItem('todos') || '[]');
}

// CREATE — add new item
function addTodo(text, priority) {
  const todos = load();
  todos.push({
    id:   Date.now(),   // unique ID
    text, priority,
    done: false,
    created: new Date().toISOString()
  });
  save(todos);
}

// UPDATE — toggle done
function toggleDone(id) {
  const todos = load().map(t =>
    t.id === id ? { ...t, done: !t.done } : t
  );
  save(todos);
}

// DELETE — remove by id
function deleteTodo(id) {
  save(load().filter(t => t.id !== id));
}

// READ + FILTER (like WHERE clause)
const active = load().filter(t => !t.done);
const byPri  = load().sort((a,b) =>
  {'high':0,'med':1,'low':2}[a.priority] -
  {'high':0,'med':1,'low':2}[b.priority]);

§ 04 — Replacing PHP session cart

Shopping Cart

A complete shopping cart with quantity controls, subtotals, and localStorage persistence — exactly like a PHP session-based cart, but surviving page refreshes without a server.

LIVE — Fully Functional Cart
Your cart is empty.
🛒

Order Summary

Subtotal$0.00
ShippingFree
Tax (8%)$0.00
Total$0.00

§ 05 — Replacing PHP sessions/cookies

Login & Register

A complete user authentication system — register, login, session persistence via localStorage, and logout — mirroring PHP $_SESSION without any server code.

LIVE — Auth System
Demo: test@test.com / password123
auth-system.js
// Register (PHP: INSERT INTO users)
function register(name, email, pass) {
  const users = JSON.parse(
    localStorage.getItem('users') || '[]');

  if (users.find(u => u.email === email))
    throw new Error('Email already taken');

  // Hash with SubtleCrypto (or use bcrypt)
  users.push({ id: Date.now(), name, email,
    password: btoa(pass) }); // encode (not secure)
  localStorage.setItem('users',
    JSON.stringify(users));
}

// Login (PHP: SELECT * WHERE email=?)
function login(email, password) {
  const users = JSON.parse(
    localStorage.getItem('users') || '[]');
  const user = users.find(u =>
    u.email === email &&
    atob(u.password) === password);

  if (!user) throw new Error('Invalid credentials');

  // Like PHP session_start() + $_SESSION
  sessionStorage.setItem('session',
    JSON.stringify({ id: user.id, name: user.name }));
}

// Check auth (PHP: if ($_SESSION['user']))
function getSession() {
  return JSON.parse(
    sessionStorage.getItem('session') || 'null');
}

§ 06 — Replacing PHP comment tables

Comment System

A full blog comment system with post, like, delete, and localStorage persistence — the client-side equivalent of a PHP+MySQL comment table.

LIVE — Post & Like Comments
No comments yet — be the first!

§ 07 — HTML5 Drag and Drop API

Drag & Drop Kanban

A project management board using the native HTML5 Drag and Drop API — no libraries needed. Card positions persist in localStorage.

LIVE — Drag cards between columns
To Do0
In Progress0
Done0
drag-drop.js
// ── HTML5 Drag & Drop API ──────────────────
// 1. Make element draggable
<div draggable="true"
     ondragstart="onDragStart(event)">Card</div>

// 2. Drag start — store the card ID
function onDragStart(e) {
  e.dataTransfer.setData('text/plain', e.target.id);
  e.target.classList.add('dragging');
}

// 3. Allow drop on containers
function onDragOver(e) {
  e.preventDefault();   // MUST prevent default!
  e.dataTransfer.dropEffect = 'move';
}

// 4. Handle drop
function onDrop(e, column) {
  e.preventDefault();
  const id   = e.dataTransfer.getData('text/plain');
  const card = document.getElementById(id);
  column.appendChild(card);   // move the DOM node
}

// 5. Drag end — cleanup
function onDragEnd(e) {
  e.target.classList.remove('dragging');
  saveBoard();    // persist positions to localStorage
}

§ 08 — Replacing PHP curl / file_get_contents

Fetch API & REST

Make GET, POST, PUT, DELETE requests to any JSON API. The Fetch API replaces PHP's curl and form-action patterns with a clean async/await interface.

LIVE — Real HTTP Requests
Response from jsonplaceholder.typicode.com
Click a button to make a live HTTP request...
fetch-api.js
// ── GET (PHP: file_get_contents($url)) ────────
const data = await fetch('/api/users')
  .then(r => { if (!r.ok) throw r.status; return r.json(); });

// ── POST (PHP: $_POST + INSERT INTO) ──────────
const res = await fetch('/api/posts', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ title: 'Hello', body: 'World' })
});
const created = await res.json();  // { id: 101, ... }

// ── PUT (PHP: UPDATE SET ... WHERE id=?) ───────
await fetch(`/api/posts/${id}`, {
  method: 'PUT',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ title: 'Updated' })
});

// ── DELETE (PHP: DELETE FROM WHERE id=?) ───────
await fetch(`/api/posts/${id}`, { method: 'DELETE' });

// ── AbortController (cancel/timeout) ───────────
const ctrl = new AbortController();
setTimeout(() => ctrl.abort(), 5000);  // 5s timeout
await fetch(url, { signal: ctrl.signal });

// ── FormData (PHP: multipart form POST) ────────
const fd = new FormData();
fd.append('name', 'Alice');
fd.append('avatar', fileInput.files[0]);
await fetch('/upload', { method: 'POST', body: fd });
// Content-Type set automatically (multipart)

§ 09 — Replacing PHP file upload processing

File Reader API

Read files directly in the browser before upload — preview images, parse CSV/JSON, process text files. The client-side version of PHP's move_uploaded_file() and file_get_contents().

LIVE — Drag & Drop File Processor
📂
Drop a file here or click to browse
Images, text, CSV, JSON — max 5MB
file-reader.js
// PHP equivalent: $_FILES['file']['tmp_name']
const file = fileInput.files[0];
const reader = new FileReader();

// Read as DataURL (for image preview)
reader.readAsDataURL(file);
reader.onload = e => { img.src = e.target.result; };

// Read as text (txt, csv, json, html...)
reader.readAsText(file, 'UTF-8');
reader.onload = e => {
  const text = e.target.result;
  const rows = text.split('\n').map(r => r.split(','));
};

// Read as ArrayBuffer (binary files)
reader.readAsArrayBuffer(file);

// Progress events
reader.onprogress = e => {
  const pct = Math.round(e.loaded / e.total * 100);
};
reader.onerror = e => console.error('Read failed');

// File metadata (like PHP $_FILES)
file.name      // "photo.jpg"
file.size      // bytes
file.type      // "image/jpeg"
file.lastModified  // timestamp

// Drag & drop zone
zone.addEventListener('dragover', e => e.preventDefault());
zone.addEventListener('drop', e => {
  e.preventDefault();
  readFile(e.dataTransfer.files[0]);
});

§ 10 — Replacing PHP routing / page loading

SPA Router — History API

Build a multi-page app with clean URLs and browser back/forward navigation — without any page reloads or server routing. Uses pushState to mirror PHP's page-by-page URLs.

LIVE — Single Page Application (try back/forward)

Welcome Home

This is rendered client-side — no server request. Click the nav links above to navigate between pages.

About Us

Built with pure HTML5 History API. The URL bar above (and your real browser URL) updates when you navigate.

Blog

Each "page" is a hidden div revealed by JavaScript — exactly like PHP's include() pattern, but without HTTP requests.

Contact

Forms here can submit via Fetch API to a PHP backend or a serverless endpoint — best of both worlds.

spa-router.js
// ── Simple SPA Router ──────────────────────────
const routes = {
  '/':        () => show('home'),
  '/about':   () => show('about'),
  '/blog':    () => show('blog'),
  '/blog/:id':id => showPost(id),
};

// Navigate — update URL + render
function navigate(path) {
  history.pushState({ path }, '', path);
  render(path);
}

// Handle browser back/forward
window.addEventListener('popstate', e => {
  render(e.state?.path || '/');
});

// Render function (like PHP's page switcher)
function render(path) {
  // match dynamic segments /blog/:id
  const match = Object.keys(routes)
    .find(r => pathMatches(path, r));
  if (match) routes[match](path);
  else show('404');
}

// Intercept ALL link clicks
document.addEventListener('click', e => {
  const a = e.target.closest('a[href]');
  if (a && a.href.startsWith(location.origin)) {
    e.preventDefault();
    navigate(a.pathname);
  }
});

§ 11 — Replacing PHP flash messages

Modals & Notifications

Toast notifications and confirmation dialogs replace PHP's session-flash messages. All CSS-animated, accessible, and zero-dependency.

LIVE — All Notification Types

§ 12 — UI Components

Tabs & Accordion

Tabbed interfaces and collapsible accordions — common PHP template patterns built entirely with HTML5 and CSS transitions.

LIVE — Tab Component

Product Overview

This tab panel is shown/hidden with JavaScript and CSS. No PHP page reload needed.

Key Features

  • Zero page reloads
  • localStorage state
  • Pure CSS animations

Pricing

Free forever. This is a demo, after all.

LIVE — Accordion
What is HTML5?

HTML5 is the latest standard for web markup, adding semantic elements, multimedia support, and powerful JavaScript APIs that previously required server-side code or plugins.

Do I need PHP?

Not for most things! localStorage, Fetch API, IndexedDB, and Web Workers handle what PHP used to do. Add a serverless function (Vercel, Netlify) for anything needing secrets.

Is it persistent?

localStorage persists until the user clears browser data. sessionStorage lasts until the tab closes. IndexedDB can store gigabytes of structured data.

§ 13 — Replacing PHP IP geolocation

Geolocation API

Get the user's GPS coordinates directly — far more accurate than PHP's IP-based geolocation. Works in HTTPS contexts with user permission.

LIVE — Get My Location
Click "Get Location" to request GPS access...
geolocation.js
// One-time location
navigator.geolocation.getCurrentPosition(
  pos => {
    pos.coords.latitude    // 33.749
    pos.coords.longitude   // -84.388
    pos.coords.accuracy    // metres
    pos.coords.altitude
    pos.coords.speed
    pos.timestamp
  },
  err => console.error(err),
  {
    enableHighAccuracy: true,
    timeout: 5000,
    maximumAge: 0
  }
);

// Continuous watch (like live tracking)
const id = navigator.geolocation
  .watchPosition(pos => updateMap(pos));

// Stop watching
navigator.geolocation.clearWatch(id);

§ 14 — HTML5 data-* attributes

Data Attributes

data-* attributes embed custom metadata directly in HTML — a clean alternative to PHP's inline template variables and a replacement for hidden inputs.

LIVE — data-* in action

Click any item to read its data attributes:

📦 Product A
👤 Alice (Admin)
📝 Blog Post
Click an item above to inspect its data-* attributes
data-attributes.html
<!-- Embed PHP-style template data in HTML -->
<div
  data-id="101"
  data-price="29.99"
  data-category="electronics"
  data-in-stock="true">
  Product Name
</div>

// Read via .dataset (camelCase)
el.dataset.id        // "101"
el.dataset.price     // "29.99"
el.dataset.category  // "electronics"
el.dataset.inStock   // "true" (data-in-stock → inStock)

// Write
el.dataset.price = "19.99";

// CSS access
[data-category="electronics"] { color: cyan; }
div::after { content: attr(data-price); }

// Select by data attribute
document.querySelectorAll('[data-category="tech"]')
document.querySelector('[data-id="101"]')

§ 15 — Replacing PHP + MySQL

IndexedDB

A full client-side relational-style database with indexes, transactions, and cursor queries. Stores gigabytes of structured data. Use the idb wrapper for a cleaner async API.

LIVE — IndexedDB Contacts Database
IDNameEmailPhoneAction
Database empty — add a contact above
indexeddb.js
// ── OPEN / CREATE DATABASE ───────────────────
const req = indexedDB.open('MyApp', 1);

req.onupgradeneeded = e => {
  const db = e.target.result;
  // CREATE TABLE contacts (...)
  const store = db.createObjectStore('contacts',
    { keyPath: 'id', autoIncrement: true });
  store.createIndex('email', 'email', { unique: true });
};

// INSERT INTO contacts VALUES (...)
async function add(contact) {
  const db = await openDB();
  return db.transaction('contacts', 'readwrite')
    .objectStore('contacts').add(contact);
}

// SELECT * FROM contacts
async function getAll() {
  const db = await openDB();
  return new Promise(resolve => {
    const req = db.transaction('contacts')
      .objectStore('contacts').getAll();
    req.onsuccess = () => resolve(req.result);
  });
}

// DELETE FROM contacts WHERE id = ?
async function remove(id) {
  const db = await openDB();
  db.transaction('contacts', 'readwrite')
    .objectStore('contacts').delete(id);
}

§ 16 — Replacing PHP templates / includes

Template Element

The <template> element holds inert HTML that isn't rendered until cloned and inserted by JavaScript — the client-side version of PHP's include() and template files.

LIVE — Clone and Render Templates
template-element.html
<!-- Inert until cloned — like PHP template files -->
<template id="card-tmpl">
  <div class="card">
    <h3 class="name"></h3>
    <p  class="role"></p>
  </div>
</template>

// Clone and populate (like PHP include + template vars)
function renderCard(user) {
  const tmpl = document.getElementById('card-tmpl');

  // .content is a DocumentFragment
  const clone = tmpl.content.cloneNode(true);

  // Fill in the blanks (PHP: $name, $role)
  clone.querySelector('.name').textContent = user.name;
  clone.querySelector('.role').textContent = user.role;

  document.getElementById('output')
    .appendChild(clone);
}

// Template literals as alternative
const html = users.map(u => `
  <div class="card">
    <h3>${u.name}</h3>
    <p>${u.role}</p>
  </div>
`).join('');
container.innerHTML = html;

§ 17 — Real-time & background processing

Web Workers & WebSocket

Web Workers run JavaScript in a background thread (like PHP's exec). WebSockets give bidirectional real-time communication — replacing PHP long-polling or server-sent events.

LIVE — Background Worker Simulation

Fibonacci computed in a simulated background thread — UI stays responsive.

worker.js + websocket.js
// ── WEB WORKER ──────────────────────────────
// main.js
const worker = new Worker('worker.js');

worker.postMessage({ n: 40 });  // send data

worker.onmessage = e => {
  console.log('Result:', e.data.result);
};

// worker.js (separate file, no DOM access)
self.onmessage = e => {
  const fib = n => n < 2 ? n : fib(n-1) + fib(n-2);
  self.postMessage({ result: fib(e.data.n) });
};

// ── WEBSOCKET ──────────────────────────────
const ws = new WebSocket('wss://chat.example.com');

ws.onopen    = () => ws.send(JSON.stringify(
  { type: 'join', room: 'general' }));
ws.onmessage = e => render(JSON.parse(e.data));
ws.onclose   = () => reconnect();
ws.onerror   = e => console.error(e);