Modern HTML Features and Experimental APIs

1. HTML5.1 and HTML5.2 New Features

Feature Version Description Browser Support
Multiple <main> elements HTML5.1 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.

2. Popover API and Native Modals

Attribute/Method Syntax Description Browser Support
popover attribute NEW <div popover>...</div> Declarative popover without JavaScript Chrome 114+, Edge 114+, Safari 17+
popover="auto" <div popover="auto"> Light dismiss (click outside or Esc closes) Default behavior for popover
popover="manual" <div popover="manual"> Manual control (no light dismiss) Requires explicit hide/toggle
popovertarget <button popovertarget="id"> Button to toggle popover Toggles visibility of target popover
popovertargetaction popovertargetaction="show|hide|toggle" Specify action: show, hide, or toggle Default is "toggle"
showPopover() element.showPopover() JavaScript method to show popover Programmatic control
hidePopover() element.hidePopover() JavaScript method to hide popover Programmatic control
togglePopover() element.togglePopover() JavaScript method to toggle popover Programmatic control
::backdrop pseudo-element [popover]:popover-open::backdrop Style the backdrop behind popover CSS styling for overlay

Example: Popover API usage

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Popover API Demo</title>
  <style>
    /* Style the popover */
    [popover] {
      padding: 1rem;
      border: 1px solid #ccc;
      border-radius: 8px;
      background: white;
      box-shadow: 0 4px 6px rgba(0,0,0,0.1);
    }
    
    /* Style the backdrop (for manual popovers) */
    [popover]:popover-open::backdrop {
      background: rgba(0, 0, 0, 0.5);
    }
    
    /* Animation when opening */
    @starting-style {
      [popover]:popover-open {
        opacity: 0;
        transform: translateY(-10px);
      }
    }
    
    [popover]:popover-open {
      opacity: 1;
      transform: translateY(0);
      transition: opacity 0.3s, transform 0.3s;
    }
  </style>
</head>
<body>
  <!-- Basic auto popover (light dismiss) -->
  <button popovertarget="popover1">Open Popover</button>
  <div id="popover1" popover>
    <h3>Auto Popover</h3>
    <p>Click outside or press Esc to close.</p>
  </div>
  
  <!-- Manual popover (no light dismiss) -->
  <button popovertarget="popover2" popovertargetaction="show">Show Manual Popover</button>
  <button popovertarget="popover2" popovertargetaction="hide">Hide Manual Popover</button>
  
  <div id="popover2" popover="manual">
    <h3>Manual Popover</h3>
    <p>Must explicitly close this popover.</p>
    <button popovertarget="popover2" popovertargetaction="hide">Close</button>
  </div>
  
  <!-- Tooltip-style popover -->
  <button popovertarget="tooltip1">Hover for info</button>
  <div id="tooltip1" popover>
    <p>This is helpful information!</p>
  </div>
  
  <!-- Programmatic control -->
  <button id="customBtn">Open with JS</button>
  <div id="popover3" popover>
    <h3>JavaScript Controlled</h3>
    <p>Opened via JavaScript method</p>
  </div>
  
  <script>
    document.getElementById('customBtn').addEventListener('click', () => {
      document.getElementById('popover3').showPopover();
    });
    
    // Listen to popover events
    document.getElementById('popover1').addEventListener('toggle', (e) => {
      console.log('Popover state:', e.newState); // 'open' or 'closed'
    });
    
    document.getElementById('popover1').addEventListener('beforetoggle', (e) => {
      console.log('Before toggle:', e.oldState, '->', e.newState);
      // Can prevent opening/closing here
    });
  </script>
</body>
</html>
Feature Popover API Dialog Element Custom Modal
Light dismiss ✓ Built-in (auto mode) ✓ With backdrop click handler ✗ Manual implementation
Backdrop ✓ ::backdrop pseudo-element ✓ ::backdrop pseudo-element ✗ Must create overlay div
Accessibility Basic (no focus trap by default) ✓ Full (focus trap, ARIA) ✗ Requires manual ARIA
Top layer ✓ Always on top ✓ Always on top ✗ z-index required
Use case Tooltips, menus, pickers Modals, alerts, forms Complex custom UI
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.