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.
📖 Table of Contents
- The HTML5 Doctype and New Basics
- Semantic Structural Elements
- New Text-Level Semantic Elements
- HTML5 Forms — New Input Types
- HTML5 Forms — New Attributes
- HTML5 Forms — Built-In Validation
- Audio and Video — Native Multimedia
- The Canvas API
- Scalable Vector Graphics (SVG) in HTML5
- Web Storage — localStorage and sessionStorage
- IndexedDB
- Web Workers
- Geolocation API
- Drag and Drop API
- History API
- WebSockets
- Server-Sent Events (SSE)
- The Notification API
- Fullscreen API
- The details and summary Elements
- The template Element
- The picture Element and Responsive Images
- Data Attributes (data-*)
- Content Editable and Spellcheck
- 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
| Attribute | Element | What 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
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
| Element | Represents |
|---|---|
| <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>© 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.
| Element | Meaning | Example Use |
|---|---|---|
| <mark> | Highlighted / relevant text | Search result highlights |
| <time> | A specific date or time | Publication dates, event times |
| <meter> | A scalar measurement within a known range | Disk usage, vote percentage |
| <progress> | Progress of a task | File upload, form completion |
| <output> | Result of a calculation or user action | Form calculation result |
| <wbr> | Word break opportunity | Long URLs, no-break strings |
| <ruby>, <rt>, <rp> | Ruby annotation (pronunciation guide for CJK) | East Asian language text |
| <bdi> | Bidirectional Isolation | User-generated content mixing LTR/RTL text |
| <dialog> | A dialog box or modal window | Confirmation 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.
| Type | Renders | Validates |
|---|---|---|
| Text field with email keyboard on mobile | Must be a valid email format | |
| url | Text field with URL keyboard on mobile | Must be a valid URL |
| tel | Text field with telephone keyboard on mobile | No format validation (use pattern) |
| number | Numeric spinner with up/down arrows | Must be a number; respects min/max/step |
| range | Slider control | Numeric value within min/max |
| date | Native date picker calendar | Must be a valid date |
| time | Time picker | Must be a valid time |
| datetime-local | Date and time picker combined | Must be a valid local date/time |
| month | Month and year picker | Must be a valid month |
| week | Week and year picker | Must be a valid week number |
| color | Color picker swatch | Outputs a hex color string |
| search | Search box (may show clear ×) | None special |
| file | File 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.
| Attribute | Applies To | Purpose |
|---|---|---|
| placeholder | All text-like inputs | Hint text shown when input is empty |
| autofocus | Any input | Auto-focuses this field when page loads |
| required | Any input | Field must have a value before submitting |
| pattern | text, tel, url, etc. | Value must match this regex pattern |
| min / max | number, range, date, time | Minimum and maximum allowed value |
| step | number, range, date, time | Increment/decrement granularity |
| minlength / maxlength | text, textarea | Character count constraints |
| multiple | email, file, select | Allow multiple values |
| autocomplete | form, input | Control browser autocomplete behavior |
| novalidate | <form> | Skip HTML5 validation on submission |
| formnovalidate | submit button | Skip validation for this specific submit button |
| list | input | Links to a <datalist> for suggestions |
| form | Any input | Associate 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 Value | Purpose |
|---|---|
| subtitles | Translation of the dialogue for users who don't understand the language |
| captions | Transcription of the dialogue AND sound effects for deaf users |
| descriptions | Audio descriptions of the video for blind users |
| chapters | Chapter titles for navigation |
| metadata | Custom 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
| Feature | SVG | Canvas |
|---|---|---|
| Type | Vector — resolution independent | Raster — 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 for | Icons, logos, charts, diagrams, UI | Games, 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.
| Feature | localStorage | sessionStorage |
|---|---|---|
| Persistence | Permanently (until explicitly cleared) | Only for the current browser tab/session |
| Scope | Shared across all tabs from the same origin | Isolated to the current tab only |
| Typical size | 5-10 MB per origin | 5-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
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); // 15Note
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 = CLOSED1️⃣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
| Feature | Server-Sent Events | WebSockets |
|---|---|---|
| Direction | Server → Client only | Full duplex (both ways) |
| Protocol | HTTP/HTTPS (regular) | WS/WSS (separate protocol) |
| Auto-reconnect | ✅ Built-in | ❌ Must implement yourself |
| Complexity | Simple | More complex |
| Best for | Feeds, notifications, live data | Chat, 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/falsefullscreen.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
| Attribute | Purpose |
|---|---|
| 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 |
| srcset | List of image sources with width or pixel density descriptors |
| sizes | Tells the browser what size the image will be at different viewport widths |
| fetchpriority | high 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>