Allow multiple <main> if only one visible (using hidden)
All modern browsers
Menu and menuitem DEPRECATED
HTML5.1
Context menus (removed in HTML5.2)
Deprecated, use custom
details and summary
HTML5.1
Native disclosure widget (accordion/collapse)
All modern browsers
dialog element
HTML5.2
Native modal and non-modal dialogs
Chrome 37+, Firefox 98+, Safari 15.4+
iframe allowpaymentrequest
HTML5.2
Allow Payment Request API in iframe
Chrome, Edge
Multiple values for rel
HTML5.1
<link rel="icon stylesheet"> space-separated
All browsers
Subresource Integrity (SRI)
HTML5.1
integrity attribute for CDN security
All modern browsers
picture element
HTML5.1
Art direction and responsive images
All modern browsers
srcset attribute
HTML5.1
Responsive image sources
All modern browsers
download attribute
HTML5.1
Force download instead of navigation
All browsers (cross-origin limited)
Example: HTML5.1 and HTML5.2 features
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Modern HTML Features</title> <!-- Subresource Integrity (HTML5.1) --> <link rel="stylesheet" href="https://cdn.example.com/styles.css" integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/ux..." crossorigin="anonymous"> <script src="https://cdn.example.com/script.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpb..." crossorigin="anonymous"></script></head><body> <!-- Multiple main elements (only one visible) - HTML5.1 --> <main id="page1"> <h1>Page 1</h1> <p>Content for page 1</p> </main> <main id="page2" hidden> <h1>Page 2</h1> <p>Content for page 2</p> </main> <!-- details/summary disclosure widget - HTML5.1 --> <details> <summary>Click to expand</summary> <p>Hidden content that can be toggled</p> <ul> <li>Item 1</li> <li>Item 2</li> </ul> </details> <!-- dialog element - HTML5.2 --> <dialog id="myDialog"> <h2>Dialog Title</h2> <p>This is a native HTML dialog</p> <button onclick="document.getElementById('myDialog').close()">Close</button> </dialog> <button onclick="document.getElementById('myDialog').showModal()">Open Dialog</button> <!-- picture element with art direction - HTML5.1 --> <picture> <source media="(min-width: 1200px)" srcset="large.webp" type="image/webp"> <source media="(min-width: 768px)" srcset="medium.webp" type="image/webp"> <source srcset="small.webp" type="image/webp"> <img src="fallback.jpg" alt="Responsive image"> </picture> <!-- Download attribute - HTML5.1 --> <a href="/files/document.pdf" download="my-document.pdf">Download PDF</a> <!-- iframe with allowpaymentrequest - HTML5.2 --> <iframe src="https://payment.example.com" allowpaymentrequest sandbox="allow-scripts allow-same-origin"> </iframe></body></html>
HTML Evolution: HTML5.1 (Nov 2016) focused on accessibility and developer experience. HTML5.2
(Dec 2017) added dialog, removed obsolete features. HTML Living Standard (WHATWG) now guides development with
continuous updates rather than versioned releases.
Browser Support: Popover API is new (2023). Use feature detection:
if ('popover' in HTMLElement.prototype). Provide fallback for older browsers using polyfills or
JavaScript-based alternatives.
3. Declarative Shadow DOM
Feature
Syntax
Description
Browser Support
shadowrootmode NEW
<template shadowrootmode="open">
Declarative shadow DOM in HTML (no JavaScript needed)
Chrome 90+, Edge 91+, Safari 16.4+
shadowrootmode="open"
Open shadow root
Shadow root accessible via element.shadowRoot
JavaScript can access shadow DOM
shadowrootmode="closed"
Closed shadow root
Shadow root not accessible from outside
Maximum encapsulation
shadowrootdelegatesfocus
shadowrootdelegatesfocus
Delegate focus to first focusable element
Improves keyboard navigation
getHTML() NEW
element.getHTML({serializableShadowRoots: true})
Serialize element including shadow DOM
Chrome 125+
Example: Declarative Shadow DOM
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Declarative Shadow DOM</title></head><body> <!-- Declarative shadow DOM component (no JavaScript!) --> <my-card> <template shadowrootmode="open"> <style> /* Encapsulated styles - won't leak out */ :host { display: block; border: 1px solid #ccc; border-radius: 8px; padding: 1rem; background: #f9f9f9; } h2 { color: #007acc; margin: 0 0 0.5rem 0; } ::slotted(p) { color: #333; line-height: 1.6; } </style> <div class="card"> <h2><slot name="title">Default Title</slot></h2> <div class="content"> <slot>Default content</slot> </div> </div> </template> <!-- Light DOM content projected into slots --> <span slot="title">My Custom Title</span> <p>This is the card content.</p> <p>Multiple paragraphs work too.</p> </my-card> <!-- Another instance with different content --> <my-card> <template shadowrootmode="open"> <style> :host { display: block; border: 2px solid #28a745; padding: 1rem; } h2 { color: #28a745; } </style> <h2><slot name="title"></slot></h2> <slot></slot> </template> <span slot="title">Success Card</span> <p>Operation completed successfully!</p> </my-card> <!-- Closed shadow root (maximum encapsulation) --> <secure-component> <template shadowrootmode="closed"> <style> .private { background: #ff0000; } </style> <div class="private">This shadow DOM cannot be accessed from outside</div> </template> </secure-component> <!-- With focus delegation --> <custom-form> <template shadowrootmode="open" shadowrootdelegatesfocus> <style> :host(:focus-within) { outline: 2px solid #007acc; outline-offset: 2px; } </style> <form> <input type="text" placeholder="Focus delegated here"> <button>Submit</button> </form> </template> </custom-form> <script> // Feature detection and polyfill if (!HTMLTemplateElement.prototype.hasOwnProperty('shadowRootMode')) { console.log('Declarative Shadow DOM not supported, using polyfill'); // Polyfill: Convert declarative to imperative document.querySelectorAll('template[shadowrootmode]').forEach(template => { const mode = template.getAttribute('shadowrootmode'); const shadowRoot = template.parentNode.attachShadow({ mode }); shadowRoot.appendChild(template.content); template.remove(); }); } // Access open shadow root const card = document.querySelector('my-card'); console.log('Shadow root:', card.shadowRoot); // accessible if "open" // Serialize including shadow DOM (Chrome 125+) if (card.getHTML) { const html = card.getHTML({ serializableShadowRoots: true }); console.log('Serialized:', html); } </script></body></html>
Benefits of Declarative Shadow DOM:
SSR-friendly (works without JavaScript)
No Flash of Unstyled Content (FOUC)
Better for performance and SEO
Simpler than imperative attachShadow()
Progressive enhancement ready
When to Use:
Server-rendered Web Components
Style encapsulation without JS
Reusable UI patterns
Design system components
Email-safe components (limited)
4. Import Maps and ES Modules
Feature
Syntax
Description
Browser Support
Import Maps NEW
<script type="importmap">
Control module resolution without bundler
Chrome 89+, Edge 89+, Safari 16.4+
imports mapping
"imports": {"lib": "./lib.js"}
Map bare module specifiers to URLs
Simplifies import statements
scopes mapping
"scopes": {"/admin/": {...}}
Scope-specific module resolution
Different versions per path
type="module"
<script type="module" src="app.js">
ES6 module script
All modern browsers
Dynamic import()
import('./module.js').then(...)
Lazy load modules at runtime
All modern browsers
import.meta
import.meta.url
Module metadata (URL, resolve)
All modern browsers
Top-level await
const data = await fetch(...)
Await at module top level
Chrome 89+, Firefox 89+, Safari 15+
Example: Import Maps and ES Modules
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Import Maps Demo</title> <!-- Define import map BEFORE any module scripts --> <script type="importmap"> { "imports": { "lodash": "https://cdn.jsdelivr.net/npm/lodash-es@4.17.21/lodash.js", "react": "https://esm.sh/react@18", "react-dom": "https://esm.sh/react-dom@18", "utils/": "./src/utils/", "components/": "./src/components/", "@app/config": "./config.js" }, "scopes": { "/admin/": { "utils/": "./admin/utils/" } } } </script></head><body> <div id="app"></div> <!-- Module script using import map --> <script type="module"> // Use mapped imports (no ./ or full URL needed) import { debounce } from 'lodash'; import React from 'react'; import { render } from 'react-dom'; import config from '@app/config'; // Use path mappings import { formatDate } from 'utils/formatters.js'; import Button from 'components/Button.js'; console.log('Config:', config); console.log('Debounce:', debounce); // Dynamic import (code splitting) const loadModule = async () => { const module = await import('utils/heavy-module.js'); module.initialize(); }; document.querySelector('#load-btn')?.addEventListener('click', loadModule); </script> <!-- Top-level await example --> <script type="module"> // Fetch data at module top level const response = await fetch('/api/config'); const config = await response.json(); console.log('Config loaded:', config); // Module waits for this to complete before executing dependent modules </script> <!-- import.meta usage --> <script type="module"> console.log('Current module URL:', import.meta.url); // Resolve relative URLs const imageUrl = new URL('./images/logo.png', import.meta.url); console.log('Image URL:', imageUrl.href); // Feature detection if (import.meta.resolve) { const resolved = import.meta.resolve('lodash'); console.log('Lodash resolves to:', resolved); } </script> <!-- Module with exports --> <script type="module"> // Named exports export const API_URL = 'https://api.example.com'; export function fetchData() { /* ... */ } // Default export export default class App { /* ... */ } </script> <!-- Import from inline module --> <script type="module"> // Can't directly import from inline modules in same document // Use external files or dynamic import const module = await import('./app.js'); module.default.init(); </script> <!-- Polyfill for older browsers --> <script> if (!HTMLScriptElement.supports || !HTMLScriptElement.supports('importmap')) { // Load es-module-shims polyfill const script = document.createElement('script'); script.src = 'https://ga.jspm.io/npm:es-module-shims@1.8.0/dist/es-module-shims.js'; script.async = true; document.head.appendChild(script); } </script></body></html>
Import Type
Before Import Maps
With Import Maps
Benefit
CDN Module
import {...} from 'https://cdn.../lib.js'
import {...} from 'lib'
Cleaner, centralized URL management
Local Module
import {...} from './src/utils/helper.js'
import {...} from 'utils/helper.js'
Path aliasing, easier refactoring
Version Control
Update all import statements
Update import map once
Centralized dependency management
Environment
Build-time replacement
Runtime mapping
No build step needed
5. Web Streams and Readable Streams
API
Description
Use Case
Browser Support
ReadableStream
Stream of data chunks that can be read progressively
Large file processing, infinite scrolls
All modern browsers
WritableStream
Destination for streaming data
File writes, network uploads
All modern browsers
TransformStream
Transform data while streaming
Compression, encryption, parsing
All modern browsers
Response.body (Fetch)
ReadableStream from fetch response
Progressive rendering, download progress
All modern browsers
async iteration
for await...of loop over streams
Simplified stream consumption
All modern browsers
TextDecoderStream
Decode byte stream to text
UTF-8 decoding, text processing
Chrome 71+, Firefox 105+, Safari 14.1+
CompressionStream
Compress data stream (gzip, deflate)
Client-side compression
Chrome 80+, Firefox 113+, Safari 16.4+
Example: Web Streams API usage
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Web Streams Demo</title></head><body> <div id="output"></div> <div id="progress"></div> <script> // Example 1: Fetch with streaming response async function fetchWithProgress(url) { const response = await fetch(url); const reader = response.body.getReader(); const contentLength = +response.headers.get('Content-Length'); let receivedLength = 0; const chunks = []; while (true) { const { done, value } = await reader.read(); if (done) break; chunks.push(value); receivedLength += value.length; // Update progress const percent = (receivedLength / contentLength * 100).toFixed(2); document.getElementById('progress').textContent = `${percent}% loaded`; } // Concatenate chunks into single array const chunksAll = new Uint8Array(receivedLength); let position = 0; for (let chunk of chunks) { chunksAll.set(chunk, position); position += chunk.length; } return chunksAll; } // Example 2: Async iteration over stream async function streamText(url) { const response = await fetch(url); const reader = response.body .pipeThrough(new TextDecoderStream()) .getReader(); for await (const chunk of readStream(reader)) { document.getElementById('output').textContent += chunk; } } async function* readStream(reader) { while (true) { const { done, value } = await reader.read(); if (done) return; yield value; } } // Example 3: Transform stream (uppercase) async function transformStream(text) { const stream = new ReadableStream({ start(controller) { controller.enqueue(text); controller.close(); } }); const transformedStream = stream.pipeThrough( new TransformStream({ transform(chunk, controller) { controller.enqueue(chunk.toUpperCase()); } }) ); const reader = transformedStream.getReader(); const { value } = await reader.read(); return value; } // Example 4: Create custom ReadableStream const customStream = new ReadableStream({ start(controller) { let i = 0; const interval = setInterval(() => { if (i < 10) { controller.enqueue(`Chunk ${i}\n`); i++; } else { controller.close(); clearInterval(interval); } }, 100); } }); // Example 5: Compression stream async function compressData(text) { const blob = new Blob([text]); const stream = blob.stream(); const compressedStream = stream.pipeThrough( new CompressionStream('gzip') ); const compressedBlob = await new Response(compressedStream).blob(); console.log('Original:', blob.size, 'Compressed:', compressedBlob.size); return compressedBlob; } // Example 6: Pipe streams async function pipeExample() { const response = await fetch('/data.txt'); // Chain multiple transforms const processedStream = response.body .pipeThrough(new TextDecoderStream()) .pipeThrough(new TransformStream({ transform(chunk, controller) { // Process each chunk const processed = chunk.trim().toUpperCase(); controller.enqueue(processed); } })); // Write to destination const writableStream = new WritableStream({ write(chunk) { console.log('Processed chunk:', chunk); document.getElementById('output').textContent += chunk; } }); await processedStream.pipeTo(writableStream); } // Example 7: Server-Sent Events with streams async function streamSSE(url) { const response = await fetch(url); const reader = response.body .pipeThrough(new TextDecoderStream()) .pipeThrough(new TransformStream({ transform(chunk, controller) { // Parse SSE format const lines = chunk.split('\n'); for (const line of lines) { if (line.startsWith('data: ')) { const data = line.slice(6); controller.enqueue(JSON.parse(data)); } } } })) .getReader(); for await (const event of readStream(reader)) { console.log('SSE event:', event); } } </script></body></html>
Stream Benefits: Memory efficient for large files (process chunk-by-chunk), enables progress
indicators, supports backpressure (slow consumer signals producer), composable (chain transformations), works
with Service Workers for offline-first apps.
6. Navigation API and SPA Routing
API Feature
Method/Event
Description
Browser Support
Navigation API NEW
navigation.navigate(url)
Modern replacement for History API
Chrome 102+, Edge 102+
navigation.navigate()
navigation.navigate('/page', {state})
Navigate to URL with state
Cleaner than pushState
navigation.back()
navigation.back()
Navigate backward in history
Equivalent to history.back()
navigation.forward()
navigation.forward()
Navigate forward in history
Equivalent to history.forward()
navigation.reload()
navigation.reload()
Reload current entry
Programmatic reload
navigate event
navigation.addEventListener('navigate')
Intercept all navigations (links, forms, browser UI)
Single event for all navigation types
navigatesuccess event
navigation.addEventListener('navigatesuccess')
Navigation completed successfully
Update UI, analytics
navigateerror event
navigation.addEventListener('navigateerror')
Navigation failed
Error handling
navigation.currentEntry
navigation.currentEntry.url
Current navigation entry
Access URL, state, key, index
View Transitions NEW
document.startViewTransition()
Animated transitions between pages
Chrome 111+, Edge 111+
Example: Navigation API for SPA routing
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Navigation API Demo</title> <style> /* View Transitions API */ @view-transition { navigation: auto; } ::view-transition-old(root) { animation: fade-out 0.3s ease-out; } ::view-transition-new(root) { animation: fade-in 0.3s ease-in; } @keyframes fade-out { to { opacity: 0; } } @keyframes fade-in { from { opacity: 0; } } </style></head><body> <nav> <a href="/">Home</a> <a href="/about">About</a> <a href="/contact">Contact</a> </nav> <main id="content"></main> <script> // Check for Navigation API support if ('navigation' in window) { console.log('Navigation API supported!'); // Intercept all navigations navigation.addEventListener('navigate', (event) => { // Only handle same-origin navigations if (!event.canIntercept || event.hashChange || event.downloadRequest) { return; } const url = new URL(event.destination.url); // Intercept and handle as SPA navigation event.intercept({ async handler() { // Fetch new content const response = await fetch(url.pathname); const html = await response.text(); // Use View Transitions API if available if (document.startViewTransition) { await document.startViewTransition(() => { document.getElementById('content').innerHTML = html; }).finished; } else { document.getElementById('content').innerHTML = html; } // Update page title document.title = `Page - ${url.pathname}`; }, // Optional: scroll to top, focus management scroll: 'after-transition', focusReset: 'after-transition' }); }); // Listen to successful navigation navigation.addEventListener('navigatesuccess', (event) => { console.log('Navigation succeeded:', navigation.currentEntry.url); // Analytics gtag?.('event', 'page_view', { page_path: navigation.currentEntry.url }); }); // Handle navigation errors navigation.addEventListener('navigateerror', (event) => { console.error('Navigation failed:', event.error); document.getElementById('content').innerHTML = '<h1>Error loading page</h1>'; }); // Programmatic navigation function navigateTo(path, state = {}) { navigation.navigate(path, { state, history: 'push' }); } // Navigation with state document.querySelector('#special-btn')?.addEventListener('click', () => { navigation.navigate('/dashboard', { state: { userId: 123, fromButton: true } }); }); // Access current navigation entry console.log('Current URL:', navigation.currentEntry.url); console.log('Current state:', navigation.currentEntry.getState()); console.log('Current key:', navigation.currentEntry.key); console.log('Current index:', navigation.currentEntry.index); // Traversal (back/forward) function goBack() { if (navigation.canGoBack) { navigation.back(); } } function goForward() { if (navigation.canGoForward) { navigation.forward(); } } // Get all entries const entries = navigation.entries(); console.log('Total entries:', entries.length); // Navigate to specific entry navigation.traverseTo(entries[0].key); } else { console.log('Navigation API not supported, using History API fallback'); // Fallback to traditional History API window.addEventListener('popstate', (event) => { loadPage(location.pathname, event.state); }); document.addEventListener('click', (e) => { if (e.target.tagName === 'A' && e.target.host === location.host) { e.preventDefault(); const path = e.target.pathname; history.pushState({}, '', path); loadPage(path); } }); async function loadPage(path, state = {}) { const response = await fetch(path); const html = await response.text(); document.getElementById('content').innerHTML = html; } } // View Transitions API (standalone) async function updateContent(newContent) { if (!document.startViewTransition) { // Fallback: instant update document.getElementById('content').innerHTML = newContent; return; } // Animated transition const transition = document.startViewTransition(() => { document.getElementById('content').innerHTML = newContent; }); // Wait for transition to complete await transition.finished; console.log('Transition complete'); } </script></body></html>
Feature
History API (Old)
Navigation API (New)
Improvement
Navigation events
popstate only (browser back/forward)
navigate (all navigations)
Single event for links, forms, browser UI
State management
pushState(), replaceState()
navigation.navigate()
Cleaner API, promises
Async handling
Manual promise management
event.intercept({ async handler })
Built-in async support
Scroll/focus
Manual implementation
scroll, focusReset options
Automatic scroll restoration
Navigation entries
Limited access
navigation.entries(), currentEntry
Full history access, metadata
Error handling
Manual try/catch
navigateerror event
Centralized error handling
Modern HTML Features Checklist
Browser Support Warning: Many features in this section are experimental or newly standardized.
Always check caniuse.com for current support. Use feature detection: if ('navigation' in window),
if (HTMLTemplateElement.prototype.hasOwnProperty('shadowRootMode')). Provide polyfills or graceful
degradation for production apps.