🌐 HTML5 (New Features)

This tutorial covers HTML5-specific features only — everything that was introduced or significantly improved in HTML5. No basic HTML tags covered here. This is a focused, advanced guide to semantic elements, APIs, forms, multimedia, graphics, storage, workers, and the modern web platform features that HTML5 brought to browsers.

>>HTML5 isn't just a new version — it's a platform. It turned the browser into an application runtime capable of doing things that previously required Flash, Java, or native apps.

📖 Table of Contents

  1. The HTML5 Doctype and New Basics
  2. Semantic Structural Elements
  3. New Text-Level Semantic Elements
  4. HTML5 Forms — New Input Types
  5. HTML5 Forms — New Attributes
  6. HTML5 Forms — Built-In Validation
  7. Audio and Video — Native Multimedia
  8. The Canvas API
  9. Scalable Vector Graphics (SVG) in HTML5
  10. Web Storage — localStorage and sessionStorage
  11. IndexedDB
  12. Web Workers
  13. Geolocation API
  14. Drag and Drop API
  15. History API
  16. WebSockets
  17. Server-Sent Events (SSE)
  18. The Notification API
  19. Fullscreen API
  20. The details and summary Elements
  21. The template Element
  22. The picture Element and Responsive Images
  23. Data Attributes (data-*)
  24. Content Editable and Spellcheck
  25. Microdata and Accessibility Improvements

1️⃣ The HTML5 Doctype and New Basics

HTML5 dramatically simplified the doctype declaration and charset meta tag compared to HTML 4 and XHTML, removing the need for long DTD references.

The Simplified Doctype

doctype-comparison.html

<!-- HTML 4.01 Strict (old, verbose) -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
  "http://www.w3.org/TR/html4/strict.dtd">

<!-- XHTML 1.0 Transitional (even more verbose) -->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<!-- HTML5 (clean, simple, short — all you ever need) -->
<!DOCTYPE html>

The New Minimal HTML5 Boilerplate

boilerplate.html

<!DOCTYPE html>
<html lang="en">
<head>
  <!-- Simplified charset declaration (was much longer before) -->
  <meta charset="UTF-8" />

  <!-- Viewport meta — controls layout on mobile devices (new in HTML5 era) -->
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />

  <!-- Theme color for browser chrome on mobile (HTML5+) -->
  <meta name="theme-color" content="#3b82f6" />

  <title>My HTML5 Page</title>
  <link rel="stylesheet" href="styles.css" />
</head>
<body>

  <!-- New HTML5 semantic structure replaces div soup -->
  <header>...</header>
  <nav>...</nav>
  <main>
    <article>...</article>
    <aside>...</aside>
  </main>
  <footer>...</footer>

  <!-- Script at the bottom, or use defer attribute -->
  <script src="app.js" defer></script>
</body>
</html>

New Script and Link Attributes

AttributeElementWhat It Does
defer<script>Download in parallel, execute after HTML is fully parsed
async<script>Download in parallel, execute immediately when ready (order not guaranteed)
type="module"<script>Treat as an ES module (supports import/export, deferred by default)
crossorigin<script>, <link>Control CORS for external resources
integrity<script>, <link>Subresource Integrity — verify file hasn't been tampered with

script-loading.html

<!-- defer — execute after DOM is ready, preserves order -->
<script src="app.js" defer></script>

<!-- async — execute as soon as downloaded, unordered -->
<script src="analytics.js" async></script>

<!-- ES module — supports import/export syntax -->
<script type="module" src="main.mjs"></script>

<!-- Subresource Integrity — CDN security best practice -->
<script
  src="https://cdn.example.com/lib.js"
  integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/ux..."
  crossorigin="anonymous"
></script>

Note

💡 Always use defer for your main app scripts — it's the modern best practice. Scripts at the bottom of <body> were the old workaround; defer is cleaner and works in the <head>.

2️⃣ Semantic Structural Elements

HTML5 introduced a full set of semantic sectioning elements to replace the endless<div> soup of HTML4. These tags communicate meaning to browsers, search engines, screen readers, and other tools.

The New Structural Elements

ElementRepresents
<header>Introductory content or navigational aids for its nearest ancestor sectioning element
<footer>Footer for its nearest ancestor sectioning element — author info, copyright, related links
<nav>A section of a page that links to other pages or parts of the page
<main>The dominant, unique content of the document — only one per page
<article>A self-contained, independently distributable piece of content (blog post, news story, forum post)
<section>A generic thematic grouping of content, typically with its own heading
<aside>Content tangentially related to surrounding content (sidebars, pull quotes, ads)
<figure>Self-contained referenced content like images, diagrams, code listings
<figcaption>Caption for a <figure> element
<address>Contact information for the nearest <article> or <body> ancestor

Before and After HTML5 Structure

before-html5.html

<!-- BEFORE HTML5 — meaningless div soup -->
<div id="header">
  <div id="logo">...</div>
  <div id="nav">...</div>
</div>
<div id="main">
  <div class="post">
    <div class="post-content">...</div>
  </div>
  <div id="sidebar">...</div>
</div>
<div id="footer">...</div>

after-html5.html

<!-- AFTER HTML5 — meaningful semantic structure -->
<header>
  <a href="/" class="logo">MySite</a>
  <nav>
    <ul>
      <li><a href="/about">About</a></li>
      <li><a href="/blog">Blog</a></li>
    </ul>
  </nav>
</header>

<main>
  <article>
    <header>
      <h1>How HTML5 Changed the Web</h1>
      <time datetime="2024-03-15">March 15, 2024</time>
    </header>
    <section>
      <h2>Semantic Elements</h2>
      <p>Article content here...</p>
    </section>
    <footer>
      <address>Written by <a href="mailto:author@example.com">Jane Doe</a></address>
    </footer>
  </article>

  <aside>
    <h2>Related Articles</h2>
    <ul>...</ul>
  </aside>
</main>

<footer>
  <p>&copy; 2024 MySite. All rights reserved.</p>
</footer>

article vs section — When to Use Which

  • Use <article> when the content makes sense completely on its own — you could syndicate it, share it, or display it on a different website and it would still make sense (blog post, tweet, product card)
  • Use <section> to group related content within a page — it's a thematic division that needs the surrounding context to make sense (a chapter within an article, a tab panel, a registration form step)
  • When in doubt: if it could have its own RSS feed entry, use <article>. Otherwise, use <section>.

3️⃣ New Text-Level Semantic Elements

HTML5 also added several inline semantic elements for marking up text with meaning beyond just visual styling.

ElementMeaningExample Use
<mark>Highlighted / relevant textSearch result highlights
<time>A specific date or timePublication dates, event times
<meter>A scalar measurement within a known rangeDisk usage, vote percentage
<progress>Progress of a taskFile upload, form completion
<output>Result of a calculation or user actionForm calculation result
<wbr>Word break opportunityLong URLs, no-break strings
<ruby>, <rt>, <rp>Ruby annotation (pronunciation guide for CJK)East Asian language text
<bdi>Bidirectional IsolationUser-generated content mixing LTR/RTL text
<dialog>A dialog box or modal windowConfirmation modals, popups

new-text-elements.html

<!-- <mark> — highlight relevant text (e.g. search matches) -->
<p>Search results for "HTML5":
  <mark>HTML5</mark> is a major revision of the HTML standard.
</p>

<!-- <time> — machine-readable date/time with human-readable display -->
<p>Published on <time datetime="2024-03-15">March 15, 2024</time></p>
<p>Event starts at <time datetime="19:30">7:30 PM</time></p>
<p>Deadline: <time datetime="2024-12-31T23:59:59">New Year's Eve</time></p>

<!-- <meter> — a value within a known range -->
<p>Disk Usage: <meter value="70" min="0" max="100" low="50" high="85" optimum="20">70%</meter></p>
<p>Score: <meter value="8.5" min="0" max="10" optimum="10">8.5/10</meter></p>

<!-- <progress> — task completion (indeterminate when no value) -->
<p>Uploading: <progress value="45" max="100">45%</progress></p>
<p>Loading: <progress></progress>  <!-- indeterminate spinner --></p>

<!-- <output> — result of a calculation -->
<form oninput="result.value=parseInt(a.value)+parseInt(b.value)">
  <input type="number" id="a" value="0"> +
  <input type="number" id="b" value="0"> =
  <output name="result" for="a b">0</output>
</form>

<!-- <dialog> — native modal dialog -->
<dialog id="myDialog">
  <h2>Are you sure?</h2>
  <p>This action cannot be undone.</p>
  <button onclick="document.getElementById('myDialog').close()">Cancel</button>
  <button>Confirm</button>
</dialog>
<button onclick="document.getElementById('myDialog').showModal()">Open Dialog</button>

<!-- <wbr> — where a long word can optionally break -->
<p>https://www.example.com/very/long/<wbr>path/to/<wbr>some/resource</p>

4️⃣ HTML5 Forms — New Input Types

HTML5 introduced 13 new input types. Browsers that support them render native, optimized UI controls (date pickers, color choosers, sliders) and perform built-in validation automatically. Unsupporting browsers gracefully fall back to plain text inputs.

TypeRendersValidates
emailText field with email keyboard on mobileMust be a valid email format
urlText field with URL keyboard on mobileMust be a valid URL
telText field with telephone keyboard on mobileNo format validation (use pattern)
numberNumeric spinner with up/down arrowsMust be a number; respects min/max/step
rangeSlider controlNumeric value within min/max
dateNative date picker calendarMust be a valid date
timeTime pickerMust be a valid time
datetime-localDate and time picker combinedMust be a valid local date/time
monthMonth and year pickerMust be a valid month
weekWeek and year pickerMust be a valid week number
colorColor picker swatchOutputs a hex color string
searchSearch box (may show clear ×)None special
fileFile chooser dialog (improved with accept/multiple)File type via accept attribute

new-input-types.html

<form>

  <!-- Email — mobile shows @ keyboard, validates format -->
  <label>Email: <input type="email" placeholder="you@example.com" /></label>

  <!-- URL — validates starts with http:// etc. -->
  <label>Website: <input type="url" placeholder="https://example.com" /></label>

  <!-- Tel — numeric keyboard on mobile, no format validation -->
  <label>Phone: <input type="tel" pattern="[0-9]{3}-[0-9]{3}-[0-9]{4}" /></label>

  <!-- Number — spinner UI, min/max/step control -->
  <label>Age: <input type="number" min="0" max="120" step="1" value="25" /></label>

  <!-- Range — slider control -->
  <label>Volume: <input type="range" min="0" max="100" step="5" value="50" /></label>

  <!-- Date — native date picker -->
  <label>Birthday: <input type="date" min="1900-01-01" max="2024-12-31" /></label>

  <!-- Time — native time picker -->
  <label>Alarm: <input type="time" /></label>

  <!-- Datetime-local — date and time combined -->
  <label>Appointment: <input type="datetime-local" /></label>

  <!-- Month picker -->
  <label>Month: <input type="month" /></label>

  <!-- Week picker -->
  <label>Week: <input type="week" /></label>

  <!-- Color picker — returns #rrggbb string -->
  <label>Favourite Color: <input type="color" value="#3b82f6" /></label>

  <!-- Search field -->
  <label>Search: <input type="search" placeholder="Search..." /></label>

  <!-- File upload with type restriction and multiple selection -->
  <label>Upload Images: <input type="file" accept="image/*" multiple /></label>

  <button type="submit">Submit</button>
</form>

5️⃣ HTML5 Forms — New Attributes

HTML5 added many powerful attributes to form elements that dramatically reduce the need for JavaScript validation and improve user experience out of the box.

AttributeApplies ToPurpose
placeholderAll text-like inputsHint text shown when input is empty
autofocusAny inputAuto-focuses this field when page loads
requiredAny inputField must have a value before submitting
patterntext, tel, url, etc.Value must match this regex pattern
min / maxnumber, range, date, timeMinimum and maximum allowed value
stepnumber, range, date, timeIncrement/decrement granularity
minlength / maxlengthtext, textareaCharacter count constraints
multipleemail, file, selectAllow multiple values
autocompleteform, inputControl browser autocomplete behavior
novalidate<form>Skip HTML5 validation on submission
formnovalidatesubmit buttonSkip validation for this specific submit button
listinputLinks to a <datalist> for suggestions
formAny inputAssociate an input with a form by ID (can be outside the form tag)

new-form-attributes.html

<form id="registration" novalidate>

  <!-- placeholder + autofocus + required -->
  <input
    type="text"
    name="username"
    placeholder="Choose a username"
    autofocus
    required
    minlength="3"
    maxlength="20"
  />

  <!-- pattern validation with a custom message hint -->
  <input
    type="tel"
    name="phone"
    pattern="[0-9]{3}-[0-9]{3}-[0-9]{4}"
    placeholder="555-123-4567"
    title="Format: 555-123-4567"
  />

  <!-- autocomplete hints for password managers -->
  <input type="email" name="email" autocomplete="email" required />
  <input type="password" name="new-password" autocomplete="new-password" required />

  <!-- multiple email recipients -->
  <input type="email" name="cc" multiple placeholder="alice@x.com, bob@y.com" />

  <!-- datalist — autocomplete suggestions -->
  <input type="text" list="browsers" name="browser" />
  <datalist id="browsers">
    <option value="Chrome" />
    <option value="Firefox" />
    <option value="Safari" />
    <option value="Edge" />
  </datalist>

  <!-- input OUTSIDE the form — associated via form="registration" -->
  <button type="submit" formnovalidate>Save Draft</button>
  <button type="submit">Submit</button>
</form>

<!-- This input is outside the form tag but associated with it -->
<input type="text" name="notes" form="registration" placeholder="Notes (optional)" />

6️⃣ HTML5 Forms — Built-In Validation

HTML5 Constraint Validation API lets the browser validate form fields automatically and provides a JavaScript API to customize validation behavior.

validation.html

<!-- HTML5 validates on submit automatically. For custom messages use JS: -->
<form id="myForm">
  <input type="email" id="emailInput" required />
  <button type="submit">Submit</button>
</form>

<script>
  const input = document.getElementById('emailInput');

  // Check validity without submitting
  input.addEventListener('input', () => {
    if (input.validity.typeMismatch) {
      input.setCustomValidity('Please enter a valid email address.');
    } else if (input.validity.valueMissing) {
      input.setCustomValidity('Email is required.');
    } else {
      input.setCustomValidity(''); // Clear — field is valid
    }
  });

  // Validity properties available on every input:
  // input.validity.valid          — true if all constraints pass
  // input.validity.valueMissing   — required but empty
  // input.validity.typeMismatch   — wrong type (e.g. not an email)
  // input.validity.patternMismatch — doesn't match pattern=""
  // input.validity.tooShort       — shorter than minlength
  // input.validity.tooLong        — longer than maxlength
  // input.validity.rangeUnderflow — below min
  // input.validity.rangeOverflow  — above max
  // input.validity.stepMismatch   — not a valid step value
</script>

7️⃣ Audio and Video — Native Multimedia

HTML5 eliminated the need for Flash or third-party plugins for audio and video playback by introducing native <audio> and <video> elements.

The audio Element

audio-element.html

<!-- Basic audio with controls -->
<audio src="song.mp3" controls></audio>

<!-- Multiple source formats for cross-browser support -->
<audio controls>
  <source src="song.ogg" type="audio/ogg" />
  <source src="song.mp3" type="audio/mpeg" />
  <source src="song.wav" type="audio/wav" />
  Your browser does not support the audio element.
</audio>

<!-- Audio attributes -->
<audio
  src="podcast.mp3"
  controls       <!-- Show play/pause/volume controls -->
  autoplay       <!-- Play immediately (usually blocked by browsers) -->
  loop           <!-- Repeat after ending -->
  muted          <!-- Start muted -->
  preload="auto" <!-- auto | metadata | none -->
></audio>

The video Element

video-element.html

<!-- Basic video -->
<video src="movie.mp4" controls width="800" height="450"></video>

<!-- Multiple formats + poster image + tracks -->
<video
  controls
  width="800"
  poster="thumbnail.jpg"
  preload="metadata"
>
  <source src="movie.webm" type="video/webm" />
  <source src="movie.mp4" type="video/mp4" />

  <!-- Subtitles / closed captions (also new in HTML5) -->
  <track kind="subtitles" src="subtitles-en.vtt" srclang="en" label="English" default />
  <track kind="subtitles" src="subtitles-fr.vtt" srclang="fr" label="Français" />
  <track kind="captions" src="captions-en.vtt" srclang="en" label="CC (English)" />

  Your browser does not support HTML5 video.
</video>

track Element — Kind Values

Kind ValuePurpose
subtitlesTranslation of the dialogue for users who don't understand the language
captionsTranscription of the dialogue AND sound effects for deaf users
descriptionsAudio descriptions of the video for blind users
chaptersChapter titles for navigation
metadataCustom data for scripts to use

Controlling Media with JavaScript

media-api.js

const video = document.querySelector('video');

// Playback control
video.play();
video.pause();
video.load();   // Reset and re-load the source

// Properties
console.log(video.duration);      // Total length in seconds
console.log(video.currentTime);   // Current position in seconds
console.log(video.paused);        // true/false
console.log(video.ended);         // true if playback ended
console.log(video.volume);        // 0.0 to 1.0
console.log(video.muted);         // true/false
console.log(video.playbackRate);  // 1.0 = normal speed, 2.0 = 2x, 0.5 = half

// Seek to a position
video.currentTime = 30;    // Jump to 30 seconds

// Change playback speed
video.playbackRate = 1.5;  // 1.5x speed

// Events
video.addEventListener('loadedmetadata', () => console.log('Duration:', video.duration));
video.addEventListener('play', () => console.log('Playing'));
video.addEventListener('pause', () => console.log('Paused'));
video.addEventListener('ended', () => console.log('Finished'));
video.addEventListener('timeupdate', () => {
  const progress = (video.currentTime / video.duration) * 100;
  progressBar.style.width = progress + '%';
});

8️⃣ The Canvas API

The <canvas> element provides a pixel-based 2D drawing surface that you control entirely with JavaScript. It's used for games, data visualizations, photo editors, and animations.

canvas-setup.html

<!-- Define the canvas element — size in HTML, not CSS -->
<canvas id="myCanvas" width="800" height="600">
  Your browser does not support the canvas element.
</canvas>

Drawing Shapes

canvas-shapes.js

const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');  // Get the 2D drawing context

// ── Rectangles ──────────────────────────────────
ctx.fillStyle = '#3b82f6';
ctx.fillRect(10, 10, 150, 100);        // Filled rectangle: x, y, width, height

ctx.strokeStyle = '#ef4444';
ctx.lineWidth = 3;
ctx.strokeRect(200, 10, 150, 100);     // Outlined rectangle

ctx.clearRect(50, 50, 50, 50);         // Clear a rectangular area (erase)

// ── Paths (lines and shapes) ─────────────────────
ctx.beginPath();                        // Start a new path
ctx.moveTo(50, 200);                    // Move to starting point
ctx.lineTo(200, 200);                   // Draw line to
ctx.lineTo(125, 300);                   // Draw another line
ctx.closePath();                        // Close the path back to start
ctx.fillStyle = '#10b981';
ctx.fill();                             // Fill the shape
ctx.strokeStyle = '#000';
ctx.stroke();                           // Also outline it

// ── Circles and Arcs ────────────────────────────
ctx.beginPath();
ctx.arc(400, 200, 80, 0, Math.PI * 2); // x, y, radius, startAngle, endAngle
ctx.fillStyle = '#f59e0b';
ctx.fill();

// Half circle
ctx.beginPath();
ctx.arc(400, 400, 60, 0, Math.PI);     // 0 to PI = half circle
ctx.strokeStyle = '#7c3aed';
ctx.lineWidth = 4;
ctx.stroke();

Text, Images, and Transforms

canvas-advanced.js

const ctx = document.getElementById('myCanvas').getContext('2d');

// ── Text ────────────────────────────────────────
ctx.font = 'bold 36px Arial';
ctx.fillStyle = '#1e293b';
ctx.fillText('Hello Canvas!', 50, 100);    // Filled text

ctx.strokeStyle = '#3b82f6';
ctx.lineWidth = 1;
ctx.strokeText('Outlined Text', 50, 160);  // Outlined text

ctx.textAlign = 'center';   // left | right | center | start | end
ctx.textBaseline = 'middle'; // top | hanging | middle | alphabetic | bottom

// ── Images ──────────────────────────────────────
const img = new Image();
img.src = 'photo.jpg';
img.onload = () => {
  ctx.drawImage(img, 0, 0);                // Draw at position
  ctx.drawImage(img, 0, 0, 200, 150);      // Draw scaled to 200x150
  // Crop: src x,y,w,h  →  dest x,y,w,h
  ctx.drawImage(img, 10, 10, 100, 100, 300, 0, 200, 200);
};

// ── Transforms ──────────────────────────────────
ctx.save();                    // Save current drawing state
ctx.translate(400, 300);       // Move the origin
ctx.rotate(Math.PI / 6);       // Rotate 30 degrees (radians)
ctx.scale(1.5, 1.5);           // Scale up 1.5x
ctx.fillRect(-50, -50, 100, 100); // Draw at origin (now centered)
ctx.restore();                 // Restore to saved state

// ── Gradients ───────────────────────────────────
const gradient = ctx.createLinearGradient(0, 0, 200, 0);
gradient.addColorStop(0, '#3b82f6');
gradient.addColorStop(1, '#8b5cf6');
ctx.fillStyle = gradient;
ctx.fillRect(50, 350, 200, 80);

Canvas Animation Loop

canvas-animation.js

const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

let x = 0;

function animate() {
  // Clear the canvas each frame
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  // Draw the updated scene
  ctx.fillStyle = '#3b82f6';
  ctx.beginPath();
  ctx.arc(x, canvas.height / 2, 30, 0, Math.PI * 2);
  ctx.fill();

  // Move the ball
  x += 2;
  if (x > canvas.width + 30) x = -30;

  // Schedule the next frame (~60fps)
  requestAnimationFrame(animate);
}

animate();

9️⃣ Scalable Vector Graphics (SVG) in HTML5

HTML5 allows inline SVG directly inside HTML markup — no separate file required. SVG is vector-based (scales perfectly at any size), fully styleable with CSS, and accessible to JavaScript.

inline-svg.html

<!-- Inline SVG directly in HTML5 — fully supported -->
<svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">

  <!-- Basic shapes -->
  <circle cx="100" cy="100" r="80" fill="#3b82f6" />
  <rect x="60" y="60" width="80" height="80" fill="white" rx="8" />
  <line x1="50" y1="50" x2="150" y2="150" stroke="#ef4444" stroke-width="3" />

  <!-- Text -->
  <text x="100" y="105" text-anchor="middle" fill="#1e293b" font-size="20" font-weight="bold">
    SVG
  </text>

  <!-- Path -->
  <path d="M 10 80 Q 95 10 180 80" fill="none" stroke="#f59e0b" stroke-width="4" />

  <!-- Group with transform -->
  <g transform="translate(100,100) rotate(45)">
    <rect x="-20" y="-20" width="40" height="40" fill="#10b981" />
  </g>

  <!-- Gradient fill -->
  <defs>
    <linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="0%">
      <stop offset="0%" style="stop-color:#3b82f6" />
      <stop offset="100%" style="stop-color:#8b5cf6" />
    </linearGradient>
  </defs>
  <rect x="10" y="170" width="180" height="20" fill="url(#grad)" rx="10" />

</svg>

SVG vs Canvas — When to Use Which

FeatureSVGCanvas
TypeVector — resolution independentRaster — pixel based
Scalability✅ Perfect at any size⚠️ Blurry when scaled up
DOM access✅ Every shape is a DOM element❌ Pixels only, no element API
CSS styling✅ Fully styleable with CSS❌ Not possible directly
Event handling✅ Click/hover events on shapes⚠️ Must calculate manually
Performance (many objects)⚠️ Slower with 1000s of elements✅ Fast for many pixels/particles
Best forIcons, logos, charts, diagrams, UIGames, photo editing, pixel art, real-time data

🔟 Web Storage — localStorage and sessionStorage

HTML5 introduced Web Storage as a modern replacement for cookies for storing data in the browser. It's simpler, larger (typically 5-10MB vs 4KB for cookies), and never sent to the server.

FeaturelocalStoragesessionStorage
PersistencePermanently (until explicitly cleared)Only for the current browser tab/session
ScopeShared across all tabs from the same originIsolated to the current tab only
Typical size5-10 MB per origin5-10 MB per origin
Sent with requests?❌ Never❌ Never

web-storage.js

// ── localStorage ───────────────────────────────────────
// Set a value (only strings — use JSON for objects)
localStorage.setItem('username', 'alice');
localStorage.setItem('settings', JSON.stringify({ theme: 'dark', lang: 'en' }));

// Get a value
const username = localStorage.getItem('username');             // "alice"
const settings = JSON.parse(localStorage.getItem('settings')); // {theme: "dark", ...}

// Remove one key
localStorage.removeItem('username');

// Clear ALL stored data for this origin
localStorage.clear();

// Check how many items are stored
console.log(localStorage.length);

// Iterate over all keys
for (let i = 0; i < localStorage.length; i++) {
  const key = localStorage.key(i);
  console.log(key, localStorage.getItem(key));
}

// ── sessionStorage — same API, different scope ──────────
sessionStorage.setItem('cart', JSON.stringify([{ id: 1, qty: 2 }]));
const cart = JSON.parse(sessionStorage.getItem('cart'));

// ── Storage Event — react when another tab changes localStorage ──
window.addEventListener('storage', (event) => {
  console.log('Key changed:', event.key);
  console.log('Old value:', event.oldValue);
  console.log('New value:', event.newValue);
});

Note

⚠️ Web Storage is synchronous and blocking. For large amounts of data or complex queries, use IndexedDB instead. Also, Web Storage is NOT secure — never store sensitive data like tokens or passwords in it.

1️⃣1️⃣ IndexedDB

IndexedDB is a low-level, asynchronous, transactional database built into the browser. It stores structured objects (not just strings), supports indexes for fast queries, and can hold much larger amounts of data than Web Storage.

indexeddb.js

// Open (or create) a database
const request = indexedDB.open('MyAppDB', 1);

// Create the schema the first time (or when version increases)
request.onupgradeneeded = (event) => {
  const db = event.target.result;

  // Create an object store (like a table)
  const store = db.createObjectStore('users', { keyPath: 'id', autoIncrement: true });

  // Create indexes for fast queries
  store.createIndex('email', 'email', { unique: true });
  store.createIndex('name', 'name', { unique: false });

  console.log('Database schema created!');
};

request.onsuccess = (event) => {
  const db = event.target.result;

  // ── Insert / Add ──────────────────────────────
  const addTx = db.transaction('users', 'readwrite');
  const addStore = addTx.objectStore('users');
  addStore.add({ name: 'Alice', email: 'alice@example.com', age: 30 });
  addStore.add({ name: 'Bob', email: 'bob@example.com', age: 25 });

  // ── Get by Key ───────────────────────────────
  const getTx = db.transaction('users', 'readonly');
  const getRequest = getTx.objectStore('users').get(1);
  getRequest.onsuccess = () => console.log('User:', getRequest.result);

  // ── Get by Index ─────────────────────────────
  const indexTx = db.transaction('users', 'readonly');
  const emailIndex = indexTx.objectStore('users').index('email');
  const byEmail = emailIndex.get('alice@example.com');
  byEmail.onsuccess = () => console.log('Found:', byEmail.result);

  // ── Get All Records ───────────────────────────
  const allTx = db.transaction('users', 'readonly');
  const allRequest = allTx.objectStore('users').getAll();
  allRequest.onsuccess = () => console.log('All users:', allRequest.result);

  // ── Update ───────────────────────────────────
  const putTx = db.transaction('users', 'readwrite');
  putTx.objectStore('users').put({ id: 1, name: 'Alice Smith', email: 'alice@example.com', age: 31 });

  // ── Delete ───────────────────────────────────
  const delTx = db.transaction('users', 'readwrite');
  delTx.objectStore('users').delete(2);
};

request.onerror = (event) => {
  console.error('Database error:', event.target.error);
};

1️⃣2️⃣ Web Workers

Web Workers let you run JavaScript in a background thread — completely separate from the main UI thread. This prevents heavy computations from freezing or janking the page.

Creating a Worker

worker.js

// worker.js — this file runs in the background thread

// Listen for messages from the main thread
self.addEventListener('message', (event) => {
  const data = event.data;
  console.log('Worker received:', data);

  // Perform expensive computation
  let result = 0;
  for (let i = 0; i < data.iterations; i++) {
    result += Math.sqrt(i) * Math.sin(i);
  }

  // Send the result back to the main thread
  self.postMessage({ result, done: true });
});

main.js

// main.js — runs on the main UI thread

// Create a worker from a separate file
const worker = new Worker('worker.js');

// Send data to the worker
worker.postMessage({ iterations: 10_000_000 });
console.log('Worker started — UI is still responsive!');

// Receive the result from the worker
worker.addEventListener('message', (event) => {
  console.log('Result from worker:', event.data.result);
});

// Handle errors
worker.addEventListener('error', (error) => {
  console.error('Worker error:', error.message);
});

// Terminate the worker when done
worker.terminate();

Inline Worker (No Separate File)

inline-worker.js

// Create a worker from a Blob without needing a separate .js file
const workerCode = `
  self.addEventListener('message', (e) => {
    const result = e.data.numbers.reduce((sum, n) => sum + n, 0);
    self.postMessage({ sum: result });
  });
`;

const blob = new Blob([workerCode], { type: 'application/javascript' });
const worker = new Worker(URL.createObjectURL(blob));

worker.postMessage({ numbers: [1, 2, 3, 4, 5] });
worker.onmessage = (e) => console.log('Sum:', e.data.sum);  // 15

Note

⚠️ Web Workers do NOT have access to the DOM, document, window, or most browser APIs. They communicate only via postMessage. Use them purely for computation.

1️⃣3️⃣ Geolocation API

The Geolocation API lets a web page request the user's geographic location (with their explicit permission). It can return the current position once or continuously watch for position changes.

geolocation.js

// Check if geolocation is supported
if ('geolocation' in navigator) {

  // ── Get current position (one-time) ──────────────
  navigator.geolocation.getCurrentPosition(
    (position) => {
      const { latitude, longitude, accuracy, altitude, speed } = position.coords;
      console.log('Latitude:', latitude);
      console.log('Longitude:', longitude);
      console.log('Accuracy:', accuracy, 'meters');

      // Use with a mapping service
      const mapUrl = `https://maps.google.com/?q=${latitude},${longitude}`;
    },
    (error) => {
      switch (error.code) {
        case error.PERMISSION_DENIED:
          console.error('User denied geolocation permission.');
          break;
        case error.POSITION_UNAVAILABLE:
          console.error('Position information is unavailable.');
          break;
        case error.TIMEOUT:
          console.error('Request timed out.');
          break;
      }
    },
    {
      enableHighAccuracy: true,  // Use GPS if available (slower, more battery)
      timeout: 10000,            // Wait up to 10 seconds
      maximumAge: 30000          // Accept cached position up to 30 seconds old
    }
  );

  // ── Watch position (continuous tracking) ──────────
  const watchId = navigator.geolocation.watchPosition(
    (position) => {
      console.log('Position updated:', position.coords.latitude, position.coords.longitude);
    },
    (error) => console.error(error)
  );

  // Stop watching
  navigator.geolocation.clearWatch(watchId);
}

1️⃣4️⃣ Drag and Drop API

HTML5's native Drag and Drop API lets users drag elements or files from the desktop and drop them into the browser, or rearrange elements within the page.

drag-drop.html

<!-- Make an element draggable -->
<div id="draggable" draggable="true">Drag me!</div>

<!-- Define a drop zone -->
<div id="dropzone">Drop here</div>

<script>
  const draggable = document.getElementById('draggable');
  const dropzone = document.getElementById('dropzone');

  // ── Draggable element events ──────────────────────
  draggable.addEventListener('dragstart', (e) => {
    e.dataTransfer.setData('text/plain', draggable.id);
    e.dataTransfer.effectAllowed = 'move';
    draggable.classList.add('dragging');
  });

  draggable.addEventListener('dragend', () => {
    draggable.classList.remove('dragging');
  });

  // ── Drop zone events ──────────────────────────────
  dropzone.addEventListener('dragover', (e) => {
    e.preventDefault();                    // Required to allow drop
    e.dataTransfer.dropEffect = 'move';
    dropzone.classList.add('drag-over');
  });

  dropzone.addEventListener('dragleave', () => {
    dropzone.classList.remove('drag-over');
  });

  dropzone.addEventListener('drop', (e) => {
    e.preventDefault();
    const id = e.dataTransfer.getData('text/plain');
    const el = document.getElementById(id);
    dropzone.appendChild(el);
    dropzone.classList.remove('drag-over');
  });
</script>

Dragging Files from the Desktop

file-drop.js

const dropzone = document.getElementById('dropzone');

dropzone.addEventListener('dragover', (e) => {
  e.preventDefault();
});

dropzone.addEventListener('drop', (e) => {
  e.preventDefault();
  const files = e.dataTransfer.files;  // FileList of dropped files

  for (const file of files) {
    console.log('File dropped:', file.name, file.size, file.type);

    // Read the file content
    const reader = new FileReader();
    reader.onload = (event) => {
      console.log('File content:', event.target.result);
    };
    reader.readAsText(file);  // or readAsDataURL, readAsArrayBuffer
  }
});

1️⃣5️⃣ History API

The HTML5 History API lets you manipulate the browser's session history and URL without triggering a page reload — the foundation of Single Page Application (SPA) routing.

history-api.js

// ── pushState — add a new history entry ──────────────
// history.pushState(stateObject, title, url)
history.pushState({ page: 'home', id: 1 }, '', '/home');
history.pushState({ page: 'about', id: 2 }, '', '/about');
history.pushState({ page: 'contact', id: 3 }, '', '/contact');
// URL bar now shows /contact — NO page reload happened!

// ── replaceState — replace CURRENT entry ──────────────
// Does NOT add a new entry — just updates the current one
history.replaceState({ page: 'contact', id: 3, updated: true }, '', '/contact-us');

// ── Navigate back and forward ─────────────────────────
history.back();          // Same as pressing Back button
history.forward();       // Same as pressing Forward button
history.go(-2);          // Go back 2 entries
history.go(1);           // Go forward 1 entry

// ── popstate — fires when user navigates back/forward ──
window.addEventListener('popstate', (event) => {
  console.log('Navigated to state:', event.state);
  // event.state is the object passed to pushState/replaceState
  // Use it to restore the correct view without reloading
  if (event.state) {
    loadPage(event.state.page);
  }
});

// Simple SPA router using the History API
function navigateTo(url, state) {
  history.pushState(state, '', url);
  renderPage(state);
}

1️⃣6️⃣ WebSockets

WebSockets provide a full-duplex, persistent communication channel between the browser and a server over a single TCP connection — enabling real-time features like chat, live dashboards, collaborative editing, and online games.

websocket.js

// Open a WebSocket connection
const socket = new WebSocket('wss://example.com/socket');
// wss:// = secure WebSocket (like https://)
// ws://  = unsecured WebSocket (like http://)

// ── Connection events ────────────────────────────────
socket.addEventListener('open', (event) => {
  console.log('WebSocket connected!');

  // Send a message to the server
  socket.send('Hello, server!');

  // Send structured data as JSON
  socket.send(JSON.stringify({
    type: 'join',
    room: 'general',
    user: 'alice'
  }));
});

// ── Receive messages ─────────────────────────────────
socket.addEventListener('message', (event) => {
  console.log('Received from server:', event.data);

  // Parse JSON messages
  const msg = JSON.parse(event.data);
  if (msg.type === 'chat') {
    displayChatMessage(msg.user, msg.text);
  }
});

// ── Connection closed ─────────────────────────────────
socket.addEventListener('close', (event) => {
  console.log('Connection closed:', event.code, event.reason);
  // Implement reconnect logic here if needed
});

// ── Errors ────────────────────────────────────────────
socket.addEventListener('error', (error) => {
  console.error('WebSocket error:', error);
});

// ── Close the connection manually ─────────────────────
socket.close();
socket.close(1000, 'Normal closure');

// ── Connection state ──────────────────────────────────
console.log(socket.readyState);
// 0 = CONNECTING
// 1 = OPEN
// 2 = CLOSING
// 3 = CLOSED

1️⃣7️⃣ Server-Sent Events (SSE)

Server-Sent Events provide a simpler, one-directional alternative to WebSockets for cases where the server needs to push data to the client continuously — live feeds, notifications, progress updates, real-time dashboards.

server-sent-events.js

// Connect to a server SSE endpoint
const eventSource = new EventSource('/api/live-updates');

// ── Default 'message' event ───────────────────────────
eventSource.addEventListener('message', (event) => {
  console.log('Update received:', event.data);
  const data = JSON.parse(event.data);
  updateDashboard(data);
});

// ── Custom named events (server can send named events) ──
eventSource.addEventListener('notification', (event) => {
  showNotification(JSON.parse(event.data));
});

eventSource.addEventListener('error-alert', (event) => {
  showError(event.data);
});

// ── Connection state ──────────────────────────────────
eventSource.addEventListener('open', () => {
  console.log('SSE connection established');
});

eventSource.addEventListener('error', (event) => {
  if (eventSource.readyState === EventSource.CLOSED) {
    console.log('Connection was closed');
  }
  // SSE auto-reconnects on error by default — no extra code needed
});

// ── Close the connection ──────────────────────────────
eventSource.close();

SSE vs WebSockets

FeatureServer-Sent EventsWebSockets
DirectionServer → Client onlyFull duplex (both ways)
ProtocolHTTP/HTTPS (regular)WS/WSS (separate protocol)
Auto-reconnect✅ Built-in❌ Must implement yourself
ComplexitySimpleMore complex
Best forFeeds, notifications, live dataChat, games, collaborative editing

1️⃣8️⃣ The Notification API

The Notification API lets web apps send native OS-level desktop notifications — the same style as notifications from desktop apps — even when the browser tab is in the background.

notifications.js

// Step 1: Request permission from the user
async function requestNotificationPermission() {
  const permission = await Notification.requestPermission();
  console.log('Permission:', permission);
  // "granted" | "denied" | "default"
  return permission === 'granted';
}

// Step 2: Send a notification
async function sendNotification() {
  if (Notification.permission !== 'granted') {
    const granted = await requestNotificationPermission();
    if (!granted) return;
  }

  const notification = new Notification('New Message!', {
    body: 'Alice sent you a message: "Hey, how are you?"',
    icon: '/icon-192.png',
    badge: '/badge.png',
    image: '/preview.jpg',
    tag: 'message-alice',     // Same tag replaces previous notification
    requireInteraction: true,  // Stay visible until user interacts
    silent: false,             // Play the default OS notification sound
    data: { url: '/messages/alice', userId: 42 }
  });

  // Handle notification clicks
  notification.addEventListener('click', () => {
    window.focus();
    window.location.href = notification.data.url;
    notification.close();
  });

  // Auto-close after 5 seconds
  setTimeout(() => notification.close(), 5000);
}

// Check current permission state
console.log(Notification.permission); // "granted" | "denied" | "default"

1️⃣9️⃣ Fullscreen API

The Fullscreen API lets you programmatically expand any element to fill the entire screen — perfect for video players, games, presentations, and image viewers.

fullscreen.js

const video = document.getElementById('myVideo');
const btn = document.getElementById('fullscreenBtn');

// Enter fullscreen
btn.addEventListener('click', async () => {
  if (video.requestFullscreen) {
    await video.requestFullscreen();
  }
  // Safari requires the webkit-prefixed version still
});

// Exit fullscreen
document.addEventListener('keydown', (e) => {
  if (e.key === 'Escape') {
    if (document.fullscreenElement) {
      document.exitFullscreen();
    }
  }
});

// Toggle fullscreen
async function toggleFullscreen(element) {
  if (!document.fullscreenElement) {
    await element.requestFullscreen();
  } else {
    await document.exitFullscreen();
  }
}

// Detect fullscreen state changes
document.addEventListener('fullscreenchange', () => {
  if (document.fullscreenElement) {
    console.log('Entered fullscreen:', document.fullscreenElement);
    btn.textContent = 'Exit Fullscreen';
  } else {
    console.log('Exited fullscreen');
    btn.textContent = 'Enter Fullscreen';
  }
});

// Check if fullscreen is possible
console.log(document.fullscreenEnabled); // true/false

fullscreen.css

/* Style elements differently when in fullscreen */
#myVideo:fullscreen {
  width: 100vw;
  height: 100vh;
  object-fit: contain;
  background: black;
}

/* Also works on the whole page */
:fullscreen body {
  background: #000;
}

2️⃣0️⃣ The details and summary Elements

The <details> and <summary> elements create a native, no-JavaScript required accordion / disclosure widget — the browser handles the open/close toggle automatically.

details-summary.html

<!-- Basic disclosure widget -->
<details>
  <summary>Click to expand</summary>
  <p>This is the hidden content that appears when you click the summary.</p>
</details>

<!-- Open by default -->
<details open>
  <summary>Already expanded</summary>
  <p>This starts open. Remove the 'open' attribute to start closed.</p>
</details>

<!-- FAQ accordion style -->
<details>
  <summary>What is HTML5?</summary>
  <p>HTML5 is the fifth revision of the HTML standard...</p>
</details>

<details>
  <summary>Is HTML5 backwards compatible?</summary>
  <p>Yes, HTML5 is designed to be backwards compatible with older browsers...</p>
</details>

<script>
  // Listen for toggle events
  document.querySelector('details').addEventListener('toggle', (e) => {
    if (e.target.open) {
      console.log('Details opened');
    } else {
      console.log('Details closed');
    }
  });
</script>

details-styling.css

/* Style the default marker/triangle */
summary {
  cursor: pointer;
  list-style: none;
  padding: 0.75rem 1rem;
  background: #f1f5f9;
  border-radius: 8px;
  font-weight: 600;
}

/* Hide default arrow in WebKit */
summary::-webkit-details-marker { display: none; }

/* Custom arrow using CSS */
summary::before {
  content: '';
  margin-right: 0.5rem;
  display: inline-block;
  transition: transform 0.2s;
}

details[open] summary::before {
  transform: rotate(90deg);
}

2️⃣1️⃣ The template Element

The <template> element holds HTML that is parsed but NOT rendered — it's invisible on the page until you clone it and insert it via JavaScript. Perfect for dynamic content patterns and reusable markup without frameworks.

template-element.html

<!-- Define a template — not shown in the page -->
<template id="card-template">
  <div class="card">
    <img class="card-img" src="" alt="" />
    <div class="card-body">
      <h3 class="card-title"></h3>
      <p class="card-text"></p>
      <button class="card-btn">Read More</button>
    </div>
  </div>
</template>

<!-- Container where cards will be injected -->
<div id="card-container"></div>

<script>
  const data = [
    { title: 'HTML5', text: 'The modern web platform', img: 'html5.png' },
    { title: 'CSS3', text: 'Stylesheets evolved', img: 'css3.png' },
    { title: 'JavaScript', text: 'The language of the web', img: 'js.png' },
  ];

  const template = document.getElementById('card-template');
  const container = document.getElementById('card-container');

  data.forEach(item => {
    // Clone the template's content (true = deep clone)
    const clone = template.content.cloneNode(true);

    // Fill in the data
    clone.querySelector('.card-title').textContent = item.title;
    clone.querySelector('.card-text').textContent = item.text;
    clone.querySelector('.card-img').src = item.img;
    clone.querySelector('.card-img').alt = item.title;

    // Add to the DOM
    container.appendChild(clone);
  });
</script>

2️⃣2️⃣ The picture Element and Responsive Images

HTML5 introduced <picture>, srcset, and sizes to serveresponsive, optimized images — the right image for the right screen size and resolution, without any JavaScript.

The picture Element — Art Direction

picture-element.html

<!-- Serve completely different image crops based on screen size -->
<picture>
  <!-- If viewport is 1200px or wider, use this landscape version -->
  <source
    media="(min-width: 1200px)"
    srcset="hero-desktop.webp"
    type="image/webp"
  />
  <!-- If viewport is 768px or wider, use this tablet version -->
  <source
    media="(min-width: 768px)"
    srcset="hero-tablet.webp"
    type="image/webp"
  />
  <!-- Fallback for modern browsers (smaller image for mobile) -->
  <source srcset="hero-mobile.webp" type="image/webp" />
  <!-- Final fallback for very old browsers (no <picture> support) -->
  <img src="hero-mobile.jpg" alt="Hero image" width="800" height="400" />
</picture>

<!-- Serve modern format (WebP/AVIF) with JPEG fallback -->
<picture>
  <source srcset="photo.avif" type="image/avif" />
  <source srcset="photo.webp" type="image/webp" />
  <img src="photo.jpg" alt="A photo" />
</picture>

srcset and sizes — Resolution Switching

srcset-sizes.html

<!-- srcset — provide multiple resolutions, let the BROWSER pick the best one -->
<img
  src="image-400.jpg"
  srcset="
    image-400.jpg   400w,
    image-800.jpg   800w,
    image-1200.jpg 1200w,
    image-1600.jpg 1600w
  "
  sizes="
    (max-width: 640px) 100vw,
    (max-width: 1024px) 50vw,
    33vw
  "
  alt="Responsive image"
  width="1600"
  height="900"
  loading="lazy"
  decoding="async"
/>

<!-- Retina / high-DPI display support with x descriptor -->
<img
  src="logo.png"
  srcset="logo.png 1x, logo@2x.png 2x, logo@3x.png 3x"
  alt="Logo"
  width="100"
  height="40"
/>

New Image Attributes in HTML5

AttributePurpose
loading="lazy"Defer loading until the image is near the viewport (native lazy load)
decoding="async"Decode the image off the main thread for better performance
srcsetList of image sources with width or pixel density descriptors
sizesTells the browser what size the image will be at different viewport widths
fetchpriorityhigh for LCP images, low for off-screen images

2️⃣3️⃣ Data Attributes (data-*)

HTML5 formally introduced custom data attributes — any attribute beginning withdata- — as a standards-compliant way to embed custom metadata directly on HTML elements without using non-standard attributes or hidden form fields.

data-attributes.html

<!-- Embed custom data on elements -->
<div
  class="product-card"
  data-product-id="42"
  data-price="19.99"
  data-category="electronics"
  data-in-stock="true"
>
  Product Card
</div>

<button data-action="delete" data-target="user" data-id="7">Delete</button>
<button data-action="edit" data-target="user" data-id="7">Edit</button>

data-attributes.js

const card = document.querySelector('.product-card');

// Access via dataset (camelCase — hyphen-separated becomes camelCase)
console.log(card.dataset.productId);    // "42"     (data-product-id)
console.log(card.dataset.price);         // "19.99"  (data-price)
console.log(card.dataset.category);      // "electronics"
console.log(card.dataset.inStock);       // "true"   (data-in-stock)

// NOTE: all values are STRINGS — parse as needed
const price = parseFloat(card.dataset.price);
const id = parseInt(card.dataset.productId);
const inStock = card.dataset.inStock === 'true';

// Set a data attribute
card.dataset.quantity = '5';   // Creates data-quantity="5"

// Delete a data attribute
delete card.dataset.category;

// Event delegation using data attributes (very common pattern)
document.addEventListener('click', (e) => {
  if (e.target.matches('[data-action]')) {
    const { action, target, id } = e.target.dataset;
    console.log(`Action: ${action} on ${target} #${id}`);
  }
});

// CSS can also select and even display data attribute values
// [data-in-stock="false"] { opacity: 0.5; }

2️⃣4️⃣ contenteditable and spellcheck

The contenteditable attribute, formally standardized in HTML5, makes any HTML element directly editable by the user in the browser — the foundation of rich text editors like Quill, ProseMirror, and Draft.js.

contenteditable.html

<!-- Make any element editable in-place -->
<div contenteditable="true" id="editor">
  <h2>Click here to edit this heading</h2>
  <p>This paragraph is <strong>editable</strong> directly in the browser.</p>
  <ul>
    <li>You can type here</li>
    <li>Add formatting</li>
  </ul>
</div>

<!-- Disable spellcheck on code editors, etc. -->
<pre contenteditable="true" spellcheck="false">
  const greeting = "hello world";
</pre>

<!-- plaintext-only — no rich formatting (modern browsers) -->
<div contenteditable="plaintext-only" id="plain-editor">
  Only plain text allowed here, no bold/italic.
</div>

<script>
  const editor = document.getElementById('editor');

  // Detect content changes
  editor.addEventListener('input', () => {
    console.log('Content changed');
    const html = editor.innerHTML;
    const text = editor.innerText;
  });

  // Programmatic formatting commands (via document.execCommand — legacy but still used)
  document.getElementById('boldBtn')?.addEventListener('click', () => {
    document.execCommand('bold');
  });

  // Get/set content
  editor.innerHTML = '<p>New content</p>';
  console.log(editor.innerHTML);  // Raw HTML
  console.log(editor.innerText);  // Plain text only
</script>

2️⃣5️⃣ Microdata and Accessibility Improvements

ARIA Landmark Roles (Reinforced in HTML5)

HTML5 semantic elements have implicit ARIA roles, but you can add explicit ARIA attributes to improve accessibility for screen readers when the semantic meaning isn't clear from the element alone.

aria-roles.html

<!-- HTML5 semantic elements have IMPLICIT ARIA roles -->
<header>...</header>   <!-- role="banner" -->
<nav>...</nav>         <!-- role="navigation" -->
<main>...</main>       <!-- role="main" -->
<aside>...</aside>     <!-- role="complementary" -->
<footer>...</footer>   <!-- role="contentinfo" -->
<form>...</form>       <!-- role="form" -->

<!-- Add explicit ARIA when needed for custom components -->
<div role="alert" aria-live="polite" id="status-message">
  <!-- Dynamically updated content that screen readers should announce -->
</div>

<div role="dialog" aria-modal="true" aria-labelledby="dialog-title">
  <h2 id="dialog-title">Confirm Action</h2>
  <p>Are you sure?</p>
  <button>Confirm</button>
  <button>Cancel</button>
</div>

<!-- Progress announcements -->
<div role="progressbar" aria-valuenow="65" aria-valuemin="0" aria-valuemax="100">
  65%
</div>

<!-- Tab interface -->
<div role="tablist">
  <button role="tab" aria-selected="true" aria-controls="panel1">Tab 1</button>
  <button role="tab" aria-selected="false" aria-controls="panel2">Tab 2</button>
</div>
<div role="tabpanel" id="panel1">Content 1</div>
<div role="tabpanel" id="panel2" hidden>Content 2</div>

HTML5 Microdata

HTML5 Microdata lets you embed structured, machine-readable data about your content directly in HTML using itemscope, itemtype, and itempropattributes. Search engines use this to create rich results (e.g. star ratings, event dates in Google).

microdata.html

<!-- Product microdata for search engine rich results -->
<div itemscope itemtype="https://schema.org/Product">
  <h1 itemprop="name">Wireless Headphones Pro</h1>
  <img itemprop="image" src="headphones.jpg" alt="Headphones" />
  <p itemprop="description">Premium noise-cancelling wireless headphones.</p>

  <div itemprop="offers" itemscope itemtype="https://schema.org/Offer">
    <span itemprop="priceCurrency" content="USD">$</span>
    <span itemprop="price">149.99</span>
    <link itemprop="availability" href="https://schema.org/InStock" />In Stock
  </div>

  <div itemprop="aggregateRating" itemscope itemtype="https://schema.org/AggregateRating">
    Rating: <span itemprop="ratingValue">4.8</span>/5
    based on <span itemprop="reviewCount">1,247</span> reviews
  </div>
</div>

<!-- Person / Author microdata -->
<address itemscope itemtype="https://schema.org/Person">
  Written by <span itemprop="name">Jane Doe</span>
  (<a itemprop="email" href="mailto:jane@example.com">jane@example.com</a>)
</address>

Note

📌 Prefer JSON-LD over Microdata for new projects. JSON-LD (JavaScript Object Notation for Linked Data) achieves the same goal — structured data for search engines — but is kept in a separate <script type="application/ld+json"> block, making it easier to maintain without touching your HTML markup.
>>HTML5 didn't just add new tags — it gave the browser a full application runtime. Every feature in this tutorial exists to close the gap between what websites and native apps can do. 🌐

Hint

📌 What to Explore Next: Dive into the Web Components standard (Custom Elements, Shadow DOM, HTML Templates combined), the File System Access API for reading/writing local files, the Web Share API for native sharing, the Clipboard API, and Progressive Web Apps (PWAs) — which build on HTML5's foundations to create installable, offline-capable web applications.