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.
§ 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.
<!-- 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.
- Add your first task above!
// ── 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]);
§ 03 — Replacing PHP search queries
Live Search & Filter
Instant search and category filtering with zero server requests. Filter, sort, and highlight results in real time — matching what PHP + SQL LIKE queries produce.
// Live search — like PHP: WHERE name LIKE '%query%' function filterProducts() { const q = searchInput.value.toLowerCase(); const cat = activeCat; const results = products.filter(p => { const matchSearch = p.name.toLowerCase().includes(q) || p.desc.toLowerCase().includes(q); const matchCat = cat === 'all' || p.category === cat; return matchSearch && matchCat; }); // Highlight matching text function highlight(text, query) { if (!query) return text; const re = new RegExp(`(${query})`, 'gi'); return text.replace(re, '<mark>$1</mark>'); } // Sort results results.sort((a, b) => { if (sortBy === 'price-asc') return a.price - b.price; if (sortBy === 'price-desc') return b.price - a.price; if (sortBy === 'name-asc') return a.name.localeCompare(b.name); }); renderGrid(results); // re-render DOM }
§ 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.
🛒
Order Summary
§ 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.
// 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.
§ 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.
// ── 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.
// ── 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().
// 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.
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.
// ── 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.
§ 12 — UI Components
Tabs & Accordion
Tabbed interfaces and collapsible accordions — common PHP template patterns built entirely with HTML5 and CSS transitions.
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.
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.
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.
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.
// 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.
Click any item to read its data attributes:
<!-- 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.
| ID | Name | Phone | Action | |
|---|---|---|---|---|
| Database empty — add a contact above | ||||
// ── 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.
<!-- 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.
Fibonacci computed in a simulated background thread — UI stays responsive.
// ── 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);