Skip to content

HTML5 チートシート

Fifth revision of HTML with new semantic elements and APIs.

01

Semantic Elements

Document Structure

HTML5 introduced semantic elements that describe their meaning to both browser and developer. header, nav, main, article, aside, footer define document structure. DOCTYPE html triggers standards mode, lang improves accessibility and SEO, and charset UTF-8 is required for proper encoding.

html5
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Page Title</title>
</head>
<body>
  <header>Header content</header>
  <nav>Navigation</nav>
  <main>
    <article>Article content</article>
    <aside>Sidebar</aside>
  </main>
  <footer>Footer</footer>
</body>
</html>

Header & Footer

header represents introductory content (logos, titles, search forms) and may appear multiple times. footer holds ending content like copyright, links, or contact info. Both can be used within sectioning elements like article or section, not just at the page level.

html5
<header>
  <h1>Site Title</h1>
  <p>Tagline</p>
</header>

<footer>
  <nav>
    <a href="/about">About</a>
    <a href="/contact">Contact</a>
  </nav>
  <small>&copy; 2024 Example</small>
</footer>

Navigation Element

nav is reserved for major navigation blocks. Not every list of links needs nav—only groups significant for site navigation. Use aria-label to distinguish multiple nav elements (e.g., main vs breadcrumb). aria-current indicates the current page.

html5
<nav aria-label="Main navigation">
  <ul>
    <li><a href="/" aria-current="page">Home</a></li>
    <li><a href="/blog">Blog</a></li>
    <li><a href="/about">About</a></li>
  </ul>
</nav>

<nav aria-label="Breadcrumb">
  <ol>
    <li><a href="/">Home</a></li>
    <li><a href="/blog">Blog</a></li>
    <li aria-current="page">Post</li>
  </ol>
</nav>

Main & Article

main wraps the dominant content of the page and should appear only once per page (mapped to the 'main' ARIA landmark). article is a self-contained composition—a blog post, news story, or widget—reusable independently. section groups thematically related content, always with a heading.

html5
<body>
  <header>Site header</header>
  <main>
    <article>
      <h1>Blog Post Title</h1>
      <p>Body content...</p>
      <section>
        <h2>Subsection</h2>
        <p>More content</p>
      </section>
    </article>
  </main>
</body>

Section & Aside

section groups related content and should always include a heading. aside represents content tangentially related to the main content—sidebars, pull quotes, or ads. Both improve the document outline. Use aria-labelledby to give sections an accessible name.

html5
<section aria-labelledby="features-heading">
  <h2 id="features-heading">Features</h2>
  <p>Feature description</p>
</section>

<aside aria-label="Related links">
  <h2>Related</h2>
  <ul>
    <li><a href="/post-2">Next post</a></li>
  </ul>
</aside>

Figure & Figcaption

figure groups content (images, code listings, quotes) referenced from the main flow, with figcaption providing a caption. figcaption must be the first or last child. figure is not limited to images—it works for any self-contained content like code snippets or blockquotes.

html5
<figure>
  <img src="chart.png" alt="Sales chart showing growth">
  <figcaption>Figure 1: Quarterly sales growth in 2024.</figcaption>
</figure>

<figure>
  <blockquote>
    <p>The best way to predict the future is to invent it.</p>
  </blockquote>
  <figcaption>— Alan Kay</figcaption>
</figure>
02

Form Enhancements

New Input Types

HTML5 added input types like email, url, tel, date, time, color, range, number, month, week, and datetime-local. Browsers provide native validation and specialized UI (date pickers, color pickers). type=email/url trigger automatic format validation on form submission.

html5
<form>
  <label>Email: <input type="email" required></label>
  <label>URL: <input type="url"></label>
  <label>Phone: <input type="tel"></label>
  <label>Date: <input type="date"></label>
  <label>Time: <input type="time"></label>
  <label>Color: <input type="color"></label>
  <label>Range: <input type="range" min="0" max="100"></label>
  <label>Number: <input type="number" min="0" step="0.01"></label>
  <button>Submit</button>
</form>

Datalist Autocomplete

datalist provides autocomplete suggestions for an input via the list attribute. Unlike select, users can type free-form text or pick a suggestion. Each option's value becomes a suggestion. datalist degrades gracefully on unsupported browsers as a plain text input.

html5
<label>
  Choose a browser:
  <input list="browsers" name="browser">
</label>
<datalist id="browsers">
  <option value="Chrome">
  <option value="Firefox">
  <option value="Safari">
  <option value="Edge">
</datalist>

Output Element

output represents the result of a calculation or user action. It has a value property accessible via JS and a for attribute referencing the inputs that contributed. It is a form-associated element, so its value is submitted with the form. Great for live calculators.

html5
<form oninput="result.value = (+a.value + +b.value)">
  <input type="number" name="a" value="0">
  +
  <input type="number" name="b" value="0">
  =
  <output name="result" for="a b">0</output>
</form>

Progress & Meter

progress shows completion of a task (value/max); omit value for an indeterminate state. meter displays a scalar measurement within a known range, with low/high/optimum thresholds that affect its color. meter is read-only and not for progress indication.

html5
<label>Uploading:
  <progress value="70" max="100">70%</progress>
</label>

<label>Disk usage:
  <meter value="0.6" min="0" max="1" low="0.4" high="0.8" optimum="0.2">
    60%
  </meter>
</label>

<!-- Indeterminate progress -->
<progress>Processing...</progress>

Form Attribute & Fieldset

The form attribute associates an input, fieldset, or button with a form by id, even when the element is not nested inside it. fieldset groups related controls and legend provides a caption. The disabled attribute on fieldset disables all contained controls at once.

html5
<form id="myForm" action="/submit" method="post"></form>

<!-- Input outside the form, but associated via form attribute -->
<label>
  Name:
  <input type="text" name="name" form="myForm" required>
</label>

<fieldset form="myForm" disabled>
  <legend>Shipping Options</legend>
  <label><input type="radio" name="ship" value="standard"> Standard</label>
  <label><input type="radio" name="ship" value="express"> Express</label>
</fieldset>

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

Placeholder & Autofocus

placeholder shows a hint inside the input and disappears on focus—it is not a replacement for a label. autofocus focuses the element on page load (use sparingly, one per page). autocomplete controls whether the browser remembers past values. title provides a tooltip but should not replace accessible labels.

html5
<form>
  <label>
    Search:
    <input type="search" name="q"
           placeholder="Type to search..."
           autofocus
           autocomplete="off">
  </label>
  <label>
    Hint text:
    <input type="text" name="hint" title="Helpful tooltip">
  </label>
  <button type="submit">Go</button>
</form>
03

Form Validation

Required Attribute

The boolean required attribute marks a field as mandatory; the browser blocks submission and shows an error bubble if it is empty. It works on all text, number, checkbox, radio, file, and select controls. Pair it with :invalid and :valid CSS pseudo-classes for visual feedback.

html5
<form>
  <label>
    Email (required):
    <input type="email" name="email" required>
  </label>
  <label>
    Optional phone:
    <input type="tel" name="phone">
  </label>
  <button>Submit</button>
</form>

Pattern Validation

pattern accepts a JavaScript regular expression (no slashes) the value must match. Anchoring with ^ and $ is recommended. The title attribute gives users a hint on failure. Pattern is implicitly anchored for type=email/url but explicit for text-based inputs.

html5
<form>
  <label>
    Product code:
    <input type="text" name="code"
           pattern="[A-Z]{3}-\d{4}"
           title="Three letters, hyphen, four digits (e.g., ABC-1234)"
           required>
  </label>
  <label>
    Hex color:
    <input type="text" name="color" pattern="^#[0-9A-Fa-f]{6}$">
  </label>
  <button>Submit</button>
</form>

Min, Max & Step

min and max define the lower and upper bounds for numeric and date inputs. step specifies the increment; values must be min + n*step. step='any' allows any decimal. The browser rejects out-of-range values on submit and adjusts spinner clicks.

html5
<form>
  <label>
    Quantity:
    <input type="number" name="qty" min="1" max="99" step="1" value="1">
  </label>
  <label>
    Price:
    <input type="number" name="price" min="0" step="0.01">
  </label>
  <label>
    Date range:
    <input type="date" name="start" min="2024-01-01" max="2024-12-31">
  </label>
  <button>Submit</button>
</form>

Minlength & Maxlength

minlength and maxlength constrain the number of characters for text inputs and textareas. minlength triggers validation only if the field has a value (combine with required for mandatory fields). maxlength hard-traps input beyond the limit, preventing typing.

html5
<form>
  <label>
    Username (3-20 chars):
    <input type="text" name="user" minlength="3" maxlength="20" required>
  </label>
  <label>
    Bio (max 280 chars):
    <textarea name="bio" maxlength="280"></textarea>
  </label>
  <button>Submit</button>
</form>

Constraint Validation API

The Constraint Validation API exposes validity.valid, valueMissing, typeMismatch, patternMismatch, tooShort, rangeOverflow, and more. setCustomValidity(msg) shows a custom message (pass empty string to clear). checkValidity() returns a boolean and fires the invalid event; reportValidity() also shows the UI.

html5
<form id="f">
  <label>
    Username:
    <input name="u" required minlength="3">
  </label>
  <button>Submit</button>
</form>

<script>
  const input = document.querySelector('#f input[name=u]');
  input.addEventListener('input', () => {
    if (input.validity.valueMissing) {
      input.setCustomValidity('Please pick a username.');
    } else if (input.validity.tooShort) {
      input.setCustomValidity('At least 3 characters.');
    } else {
      input.setCustomValidity('');
    }
  });
</script>

novalidate & formnovalidate

novalidate on the form disables browser validation entirely, useful when you validate via JS. formnovalidate on a submit button skips validation only for that button's submission (e.g., a 'Save draft' that allows incomplete data). Both still submit the form.

html5
<!-- Browser validation disabled for whole form -->
<form novalidate>
  <label>Email: <input type="email" name="e" required></label>
  <button>Save draft</button>
  <!-- Skip validation only for this button -->
  <button type="submit" formnovalidate>Cancel</button>
</form>
04

Canvas Drawing

Canvas Element & 2D Context

canvas is a bitmap drawing surface. getContext('2d') returns the 2D rendering context; 'webgl' returns a 3D context. The width/height attributes set the drawing buffer size (not CSS size). For crisp rendering on retina displays, scale the buffer by devicePixelRatio. Fallback content goes between the tags.

html5
<canvas id="cv" width="400" height="300">
  Your browser does not support canvas.
</canvas>

<script>
  const canvas = document.getElementById('cv');
  const ctx = canvas.getContext('2d');
  // High-DPI scaling
  const dpr = window.devicePixelRatio || 1;
  canvas.width = 400 * dpr;
  canvas.height = 300 * dpr;
  ctx.scale(dpr, dpr);
</script>

Drawing Rectangles

fillRect(x, y, w, h) draws a filled rectangle, strokeRect draws only the outline, and clearRect erases pixels to transparent. fillStyle and strokeStyle accept colors, gradients, or patterns. lineWidth affects the stroke thickness, centered on the path edge.

html5
<script>
  const ctx = canvas.getContext('2d');
  // Filled rectangle
  ctx.fillStyle = '#3498db';
  ctx.fillRect(10, 10, 100, 50);
  // Outlined rectangle
  ctx.strokeStyle = '#e74c3c';
  ctx.lineWidth = 3;
  ctx.strokeRect(130, 10, 100, 50);
  // Clear a rectangle (erase)
  ctx.clearRect(50, 20, 40, 30);
</script>

Paths & Lines

beginPath starts a new path. moveTo sets the pen position without drawing; lineTo adds a line segment. closePath connects the last point to the start. Call fill() and/or stroke() to render. Arcs (arc), curves (quadraticCurveTo, bezierCurveTo), and rects can also be added to a path.

html5
<script>
  const ctx = canvas.getContext('2d');
  ctx.beginPath();
  ctx.moveTo(20, 20);          // starting point
  ctx.lineTo(180, 20);         // top edge
  ctx.lineTo(100, 140);        // bottom corner
  ctx.closePath();             // back to start
  ctx.fillStyle = 'rgba(46,204,113,0.6)';
  ctx.fill();
  ctx.stroke();
</script>

Drawing Text

font uses the same shorthand as CSS font. textAlign (start, end, left, right, center) and textBaseline (top, middle, alphabetic, bottom) control alignment. fillText draws solid text; strokeText draws only the outline. measureText('foo').width returns the rendered width.

html5
<script>
  const ctx = canvas.getContext('2d');
  ctx.font = '600 24px system-ui, sans-serif';
  ctx.textAlign = 'center';
  ctx.textBaseline = 'middle';
  ctx.fillStyle = '#2c3e50';
  ctx.fillText('Hello Canvas', 200, 150);
  ctx.lineWidth = 1;
  ctx.strokeStyle = '#bdc3c7';
  ctx.strokeText('Outlined', 200, 200);
</script>

Drawing Images

drawImage can take an img, canvas, or video element. With 3 args it draws at natural size; with 5 args it scales to w/h; with 9 args the first four are source-rectangle (crop) and the last four are destination-rectangle. Wait for onload before drawing, or the image may not appear.

html5
<script>
  const img = new Image();
  img.src = 'pic.jpg';
  img.onload = () => {
    ctx.drawImage(img, 0, 0);                      // full size
    ctx.drawImage(img, 0, 0, 100, 75);             // scaled
    // Source crop -> destination
    ctx.drawImage(img, 32, 32, 64, 64, 200, 0, 64, 64);
  };
</script>

Gradients

createLinearGradient(x0, y0, x1, y1) and createRadialGradient(x0, y0, r0, x1, y1, r1) define gradients. addColorStop(offset, color) adds color stops with offset from 0 to 1. Always pass standard color strings; mismatched formats (mixing hex and rgba) can break interpolation. Assign the gradient to fillStyle or strokeStyle.

html5
<script>
  // Linear gradient
  const lg = ctx.createLinearGradient(0, 0, 200, 0);
  lg.addColorStop(0, '#1e90ff');
  lg.addColorStop(1, '#ffffff');
  ctx.fillStyle = lg;
  ctx.fillRect(0, 0, 200, 80);

  // Radial gradient
  const rg = ctx.createRadialGradient(100, 180, 5, 100, 180, 80);
  rg.addColorStop(0, 'rgba(255,200,0,1)');
  rg.addColorStop(1, 'rgba(255,200,0,0)');
  ctx.fillStyle = rg;
  ctx.fillRect(0, 100, 200, 160);
</script>
05

Inline SVG

SVG Element Basics

Inline SVG uses the svg element with width/height for display size and viewBox for the coordinate system. xmlns is required when the SVG is used standalone, optional inline. SVG is vector-based: it scales crisply at any size and is searchable and styleable with CSS like normal HTML.

html5
<svg width="200" height="120" viewBox="0 0 200 120"
     xmlns="http://www.w3.org/2000/svg">
  <rect x="10" y="10" width="180" height="100"
        fill="#3498db" rx="8" />
  <text x="100" y="65" text-anchor="middle"
        fill="white" font-size="18">SVG</text>
</svg>

Basic Shapes

SVG primitives include rect, circle, ellipse, line, polyline (open), and polygon (closed). cx/cy are center coordinates; r is radius. Fill is the interior color, stroke is the outline. stroke-width sets outline thickness. All attributes can also be set via CSS.

html5
<svg width="240" height="120" viewBox="0 0 240 120">
  <rect x="10" y="10" width="80" height="80" fill="#e74c3c" />
  <circle cx="150" cy="50" r="40" fill="#2ecc71" />
  <ellipse cx="200" cy="60" rx="30" ry="18" fill="#f1c40f" />
  <line x1="10" y1="110" x2="230" y2="110" stroke="#333" stroke-width="2" />
  <polyline points="10,100 60,70 110,100 160,70"
            fill="none" stroke="#9b59b6" stroke-width="3" />
</svg>

Paths

path is the most powerful SVG element, drawing via a d attribute of commands. M = moveTo, L = lineTo, H/V = horizontal/vertical lines, C = cubic bezier, Q = quadratic bezier, A = arc, Z = close path. Lowercase letters use relative coordinates. Most editor-exported SVGs use paths.

html5
<svg width="200" height="120" viewBox="0 0 200 120">
  <!-- M=moveTo, L=lineTo, C=cubicBezier, Z=closePath -->
  <path d="M 10 80
           C 40 10, 90 10, 120 80
           L 180 80 Z"
        fill="none" stroke="#3498db" stroke-width="3" />
  <!-- A=arc -->
  <path d="M 10 100 A 90 90 0 0 1 190 100"
        fill="none" stroke="#e74c3c" stroke-width="2" />
</svg>

Text in SVG

text renders scalable, selectable text. x/y position the baseline. text-anchor (start/middle/end) and dominant-baseline control alignment. tspan allows per-segment styling, positioning, and line breaks. SVG text stays crisp at any zoom and is accessible, unlike text drawn on canvas.

html5
<svg width="300" height="100" viewBox="0 0 300 100">
  <text x="20" y="40" font-family="system-ui" font-size="20"
        fill="#2c3e50">Plain text</text>
  <text x="20" y="75" font-size="20" fill="#e74c3c"
        text-decoration="underline">Underlined</text>
  <text x="180" y="50">
    <tspan fill="#3498db">Blue</tspan>
    <tspan fill="#27ae60">Green</tspan>
  </text>
</svg>

Gradients & Patterns

Gradients live inside defs and are referenced by id via fill='url(#id)'. linearGradient uses x1/y1/x2/y2 (objectBoundingBox by default, 0-1). radialGradient defaults to a centered circle. stop elements define color stops. defs also holds patterns, clip paths, and reusable symbols.

html5
<svg width="220" height="120" viewBox="0 0 220 120">
  <defs>
    <linearGradient id="g1" x1="0" y1="0" x2="1" y2="0">
      <stop offset="0%" stop-color="#1e90ff" />
      <stop offset="100%" stop-color="#ffffff" />
    </linearGradient>
    <radialGradient id="g2">
      <stop offset="0%" stop-color="#f1c40f" />
      <stop offset="100%" stop-color="#e67e22" />
    </radialGradient>
  </defs>
  <rect x="10" y="10" width="90" height="90" fill="url(#g1)" />
  <circle cx="160" cy="55" r="45" fill="url(#g2)" />
</svg>

Inline SVG vs Canvas

SVG is resolution-independent, each shape is a DOM node you can style and attach events to—ideal for icons, charts with few elements, and accessible graphics. Canvas is a single bitmap, faster for thousands of objects or pixel manipulation (games, image processing), but not scalable or DOM-accessible.

html5
<!-- SVG: vector, DOM-accessible, scalable -->
<svg width="100" height="100" viewBox="0 0 100 100">
  <circle id="c" cx="50" cy="50" r="40" fill="#3498db" />
</svg>
<button onclick="document.getElementById('c')
  .setAttribute('r','20')">Shrink</button>

<!-- Canvas: bitmap, pixel-based, faster for many objects -->
<canvas id="cv" width="100" height="100"></canvas>
06

Audio & Video

Video Element

video embeds video without plugins. controls shows native UI; poster is the thumbnail before playback. Multiple source elements let the browser pick the first supported codec. Fallback text between the tags shows on unsupported browsers. Always include width/height to avoid layout shift.

html5
<video controls width="640" poster="cover.jpg">
  <source src="movie.mp4" type="video/mp4">
  <source src="movie.webm" type="video/webm">
  Your browser does not support the video tag.
</video>

Audio Element

audio works like video without visual dimensions. controls adds play/pause/seek/volume UI. Without controls, the element is invisible and must be controlled via JS. Provide multiple sources (mp3, ogg) for cross-browser support. autoplay may be blocked by browsers unless muted.

html5
<audio controls>
  <source src="song.mp3" type="audio/mpeg">
  <source src="song.ogg" type="audio/ogg">
  Your browser does not support audio.
</audio>

<!-- Simple single-source -->
<audio src="beep.mp3" autoplay></audio>

Multiple Sources

Browsers choose the first source whose type they can play. Including the codecs parameter helps them decide without downloading. List modern formats (webm/av1) first for smaller payloads, mp4 as the universal fallback. A download link inside the tags is a graceful fallback.

html5
<video controls>
  <source src="clip.webm" type='video/webm; codecs="vp9, opus"'>
  <source src="clip.mp4"  type='video/mp4; codecs="hvc1"'>
  <source src="clip.ogv"  type="video/ogg">
  <p>Download <a href="clip.mp4">clip.mp4</a></p>
</video>

Track Element (Captions)

track adds timed text tracks via WebVTT (.vtt) files. kind can be subtitles, captions, descriptions, chapters, or metadata. srclang declares the language, label is shown in the menu, and default picks the initial track. Captions are essential for accessibility and improve SEO.

html5
<video controls>
  <source src="talk.mp4" type="video/mp4">
  <track kind="subtitles" src="talk.en.vtt"
         srclang="en" label="English" default>
  <track kind="captions" src="talk.en.cc.vtt"
         srclang="en" label="English CC">
  <track kind="chapters" src="talk.chapters.vtt"
         srclang="en" label="Chapters">
</video>

Media Attributes

autoplay starts playback automatically but is blocked unless muted is also set. loop repeats forever. playsinline prevents forced fullscreen on iOS (essential for inline backgrounds). preload can be none, metadata (just duration/dimensions), or auto (buffer the whole file). Avoid autoplay+preload=auto together to save bandwidth.

html5
<video src="bg.mp4"
       autoplay muted loop
       playsinline
       preload="auto"
       width="1280" height="720">
</video>

<video src="preview.mp4"
       controls
       preload="metadata"
       poster="preview.jpg">
</video>

Media API

The HTMLMediaElement API exposes play(), pause(), load(), currentTime, duration, volume, muted, playbackRate, and readyState. Events include play, pause, timeupdate, ended, volumechange, loadedmetadata, and waiting. Build custom players by hiding controls and wiring buttons to these methods.

html5
<video id="v" src="movie.mp4"></video>
<button onclick="v.play()">Play</button>
<button onclick="v.pause()">Pause</button>
<input type="range" min="0" max="100" oninput="v.volume = this.value/100">

<script>
  const v = document.getElementById('v');
  v.addEventListener('timeupdate', () => {
    console.log(v.currentTime, '/', v.duration);
  });
  v.addEventListener('ended', () => alert('Done!'));
  // Jump 10s forward
  function skip() { v.currentTime += 10; }
</script>
07

Geolocation

getCurrentPosition

getCurrentPosition takes success and optional error callbacks plus an options object. The position object exposes coords.latitude, longitude, accuracy, and—when available—altitude, heading, and speed. Geolocation only works on HTTPS (or localhost) and prompts the user for permission.

html5
<script>
  if ('geolocation' in navigator) {
    navigator.geolocation.getCurrentPosition(
      (pos) => {
        console.log('Latitude:', pos.coords.latitude);
        console.log('Longitude:', pos.coords.longitude);
        console.log('Accuracy:', pos.coords.accuracy, 'meters');
      },
      (err) => console.error(err.message),
      { enableHighAccuracy: true, timeout: 10000 }
    );
  }
</script>

Position Options

enableHighAccuracy asks for the most precise result (GPS) at the cost of time and battery. timeout caps the wait; if exceeded, error.code is TIMEOUT (3). maximumAge lets the browser return a cached position if it is younger than the given milliseconds, avoiding repeated GPS calls.

html5
<script>
  navigator.geolocation.getCurrentPosition(success, error, {
    enableHighAccuracy: true,  // use GPS, slower
    timeout: 5000,             // max wait in ms
    maximumAge: 60000          // accept cached fix up to 60s old
  });

  function success(pos) { /* ... */ }
  function error(err) { /* ... */ }
</script>

watchPosition

watchPosition calls the success callback every time the position changes, returning an ID. Pass that ID to clearWatch to stop updates. This is essential for navigation apps. Be mindful of battery—use a reasonable update interval and stop watching when not needed.

html5
<script>
  const watchId = navigator.geolocation.watchPosition(
    (pos) => updateMap(pos.coords),
    (err) => console.warn(err.message),
    { enableHighAccuracy: true }
  );

  // Stop tracking
  document.getElementById('stop').onclick = () => {
    navigator.geolocation.clearWatch(watchId);
  };
</script>

Error Handling

The error callback receives a GeolocationPositionError with code and message. PERMISSION_DENIED (1) means the user declined or the browser blocked it. POSITION_UNAVAILABLE (2) means the device could not determine a fix. TIMEOUT (3) means the request took longer than the timeout option. Always handle all three cases gracefully.

html5
<script>
  navigator.geolocation.getCurrentPosition(
    (pos) => console.log(pos),
    (err) => {
      switch (err.code) {
        case err.PERMISSION_DENIED:  // 1
          console.log('User denied permission'); break;
        case err.POSITION_UNAVAILABLE: // 2
          console.log('Position unavailable'); break;
        case err.TIMEOUT:              // 3
          console.log('Timed out'); break;
      }
      console.log(err.message);
    }
  );
</script>

Permissions API

The Permissions API lets you check the state of a permission without triggering a prompt. query({ name: 'geolocation' }) returns a PermissionStatus whose state is granted, denied, or prompt. Listen to onchange to react when the user grants or revokes permission elsewhere. Supported for geolocation, notifications, camera, microphone, and others.

html5
<script>
  navigator.permissions
    .query({ name: 'geolocation' })
    .then((result) => {
      // result.state: 'granted' | 'denied' | 'prompt'
      if (result.state === 'granted') {
        startTracking();
      } else if (result.state === 'prompt') {
        showEnableButton();
      } else {
        showInstructions();
      }
      result.onchange = () => console.log(result.state);
    });
</script>
08

Web Storage

localStorage Basics

localStorage stores string key/value pairs with no expiration; data persists across tabs and sessions. Values are strings, so numbers and booleans are auto-converted. The limit is ~5MB per origin. Storage is synchronous and blocks the main thread, so avoid large writes. Accessing it in private mode may throw, so wrap in try/catch.

html5
<script>
  // Store a value (strings only)
  localStorage.setItem('theme', 'dark');
  // Read a value
  const theme = localStorage.getItem('theme'); // 'dark'
  // Remove one key
  localStorage.removeItem('theme');
  // Remove everything
  localStorage.clear();

  // Number of stored keys
  console.log(localStorage.length);
</script>

sessionStorage

sessionStorage has the same API as localStorage but is scoped to a single tab and cleared when the tab closes. It is useful for transient data like a multi-step form draft, a temporary shopping cart, or CSRF tokens. Data is not shared between tabs even on the same origin.

html5
<script>
  // sessionStorage works like localStorage but per-tab
  sessionStorage.setItem('cart', '3 items');
  console.log(sessionStorage.getItem('cart'));
  sessionStorage.removeItem('cart');

  // Survives a page reload but not a tab close
  // Also isolated per tab (not shared)
</script>

JSON Data Storage

Since Web Storage only stores strings, use JSON.stringify and JSON.parse for objects and arrays. Always wrap parse in try/catch because corrupted or manually edited data will throw. For complex data, version the key (e.g., 'settings.v2') so schema changes don't break old data.

html5
<script>
  const settings = { theme: 'dark', fontSize: 16, lang: 'en' };

  // Serialize before saving
  localStorage.setItem('settings', JSON.stringify(settings));

  // Parse when reading
  try {
    const saved = JSON.parse(localStorage.getItem('settings'));
    console.log(saved.theme); // 'dark'
  } catch (e) {
    console.warn('Corrupted data', e);
  }
</script>

Storage Events

The storage event fires in every other tab of the same origin when localStorage or sessionStorage changes—but NOT in the tab that made the change. It is the simplest cross-tab communication channel. The event object carries key, oldValue, newValue, url, and storageArea. Use it to sync logout, theme changes, or live updates across tabs.

html5
<script>
  // Listen in OTHER tabs (same origin) for changes
  window.addEventListener('storage', (e) => {
    console.log('Key changed:', e.key);
    console.log('Old value:', e.oldValue);
    console.log('New value:', e.newValue);
    console.log('URL:', e.url);
  });

  // The tab that calls setItem does NOT receive the event
  localStorage.setItem('count', '42');
</script>

Clearing Storage

clear() removes every key for the origin—use sparingly and confirm with the user. key(n) returns the key name at index n, or null if out of range. Iteration order is implementation-defined, so don't rely on it. For partial cleanup, list keys with a prefix (e.g., 'cache:') and removeItem them in a loop.

html5
<script>
  // Remove a single key
  localStorage.removeItem('temp');

  // Wipe all keys for this origin
  localStorage.clear();

  // Iterate keys
  const keys = [];
  for (let i = 0; i < localStorage.length; i++) {
    keys.push(localStorage.key(i));
  }
  console.log(keys);

  // Indexed access (order is not guaranteed)
  localStorage.key(0);
</script>
09

IndexedDB

Opening a Database

indexedDB.open(name, version) opens or creates a database. onupgradeneeded fires when the version increases or the DB is first created—it is the ONLY place you can create or alter object stores and indexes. Bump the version number whenever you change the schema. Stores need a keyPath (the property used as the key) or an auto-incrementing key.

html5
<script>
  const request = indexedDB.open('MyDB', 1);

  request.onupgradeneeded = (e) => {
    const db = e.target.result;
    if (!db.objectStoreNames.contains('users')) {
      const store = db.createObjectStore('users', { keyPath: 'id' });
      store.createIndex('email', 'email', { unique: true });
    }
  };

  request.onsuccess = (e) => {
    const db = e.target.result;
    console.log('DB ready:', db.name, db.version);
  };

  request.onerror = (e) => console.error(e.target.error);
</script>

Object Stores

An object store is like a table; each record has a key and a value (any structured-clone-able object). keyPath tells the store which property holds the key. autoIncrement assigns sequential integer keys. Indexes let you query by a non-key property; unique: true enforces no duplicates on that field.

html5
<script>
  request.onupgradeneeded = (e) => {
    const db = e.target.result;

    // With a key path
    const users = db.createObjectStore('users', { keyPath: 'id' });

    // Auto-incrementing integer key
    const logs = db.createObjectStore('logs', { autoIncrement: true });

    // Indexes for fast lookups
    users.createIndex('name', 'name', { unique: false });
    users.createIndex('email', 'email', { unique: true });
  };
</script>

Adding Data

All operations happen inside a transaction created with db.transaction(storeName, mode). Mode is 'readonly' (default) or 'readwrite'. add() rejects duplicates; put() upserts. The transaction auto-commits when all requests complete; listen to oncomplete. Keep transactions short—they block other transactions on the same stores.

html5
<script>
  function addUser(db, user) {
    const tx = db.transaction('users', 'readwrite');
    const store = tx.objectStore('users');
    store.add(user);          // throws on duplicate key
    // store.put(user);       // inserts OR overwrites

    tx.oncomplete = () => console.log('Saved');
    tx.onerror = () => console.error(tx.error);
  }

  addUser(db, { id: 1, name: 'Ada', email: '[email protected]' });
</script>

Reading Data

get(key) reads a single record by primary key; getAll() returns all records as an array. To look up by a non-key field, open the index with store.index(name) and call get/getAll on it. Each request has onsuccess and onerror. Results are in event.target.result or request.result.

html5
<script>
  const tx = db.transaction('users', 'readonly');
  const store = tx.objectStore('users');

  // By primary key
  const req = store.get(1);
  req.onsuccess = () => console.log(req.result);

  // By index
  const byEmail = store.index('email');
  byEmail.get('[email protected]').onsuccess = (e) => {
    console.log(e.target.result);
  };

  // All records
  store.getAll().onsuccess = (e) => {
    console.log(e.target.result); // array
  };
</script>

Cursors & Indexes

openCursor() lets you iterate records one at a time, ideal for large datasets that won't fit in memory. Call cursor.continue() to advance. cursor.update(value) modifies the current record; cursor.delete() removes it. IDBKeyRange (bound, only, lowerBound, upperBound) scopes index queries—useful for prefix or range searches.

html5
<script>
  const tx = db.transaction('users', 'readwrite');
  const store = tx.objectStore('users');

  // Iterate and modify
  const req = store.openCursor();
  req.onsuccess = (e) => {
    const cursor = e.target.result;
    if (cursor) {
      if (cursor.value.name === 'Ada') {
        cursor.value.role = 'admin';
        cursor.update(cursor.value);
      }
      cursor.continue();
    }
  };

  // Range query on an index
  const range = IDBKeyRange.bound('A', 'M');
  store.index('name').openCursor(range).onsuccess = (e) => { /* ... */ };
</script>
10

Drag and Drop API

Making Elements Draggable

Set draggable='true' on any element to make it draggable. The dragstart event fires when dragging begins; use dataTransfer.setData to package data for the drop target. effectAllowed (copy, move, link, all, none) hints at the intended operation. dragend fires when the drag finishes, regardless of whether it dropped.

html5
<div id="card" draggable="true">
  Drag me!
</div>

<script>
  const card = document.getElementById('card');
  card.addEventListener('dragstart', (e) => {
    e.dataTransfer.setData('text/plain', card.id);
    e.dataTransfer.effectAllowed = 'move';
    card.classList.add('dragging');
  });
  card.addEventListener('dragend', () => {
    card.classList.remove('dragging');
  });
</script>

Drag Events

To accept a drop, you MUST call preventDefault() on both dragover and drop—otherwise the browser does nothing. dragenter/dragleave fire when the pointer enters/leaves the target. dropEffect (copy, move, link, none) controls the cursor. The drag event fires continuously on the source during dragging.

html5
<script>
  // Events on the SOURCE element:
  //   dragstart, drag, dragend

  // Events on the TARGET element:
  //   dragenter, dragover, dragleave, drop

  const drop = document.getElementById('drop');
  drop.addEventListener('dragover', (e) => {
    e.preventDefault();                 // REQUIRED to allow drop
    e.dataTransfer.dropEffect = 'move';
  });
  drop.addEventListener('drop', (e) => {
    e.preventDefault();
    const id = e.dataTransfer.getData('text/plain');
    drop.appendChild(document.getElementById(id));
  });
</script>

DataTransfer

dataTransfer carries data between the drag source and drop target. setData(format, data) stores a string; getData(format) reads it. Standard formats include text/plain, text/uri-list, text/html, and files. You can also register custom MIME types. Some browsers restrict getData to drop events for security.

html5
<script>
  card.addEventListener('dragstart', (e) => {
    // Multiple data types
    e.dataTransfer.setData('text/plain', 'Hello');
    e.dataTransfer.setData('text/uri-list', 'https://example.com');
    e.dataTransfer.setData('application/json', JSON.stringify({ a: 1 }));

    // Read in the drop handler
    // e.dataTransfer.getData('text/plain')
  });
</script>

Drop Zone

A common pattern: highlight the drop zone on dragenter/dragover, clear it on dragleave/drop. dataTransfer.files gives a FileList when dragging files from the OS, enabling drag-and-drop uploads. Always preventDefault on dragover or the drop event will not fire. Use dragenter/leave counters if you have nested elements.

html5
<style>
  .dropzone { border: 2px dashed #bbb; padding: 24px; }
  .dropzone.over { background: #eaf6ff; border-color: #3498db; }
</style>

<div class="dropzone" id="dz">Drop files here</div>

<script>
  const dz = document.getElementById('dz');
  dz.addEventListener('dragenter', () => dz.classList.add('over'));
  dz.addEventListener('dragover',  (e) => e.preventDefault());
  dz.addEventListener('dragleave', () => dz.classList.remove('over'));
  dz.addEventListener('drop', (e) => {
    e.preventDefault();
    dz.classList.remove('over');
    const files = e.dataTransfer.files;   // FileList
    for (const f of files) console.log(f.name, f.size);
  });
</script>

Drag Image

setDragImage(element, offsetX, offsetY) replaces the default drag ghost with a custom element. The element must be in the DOM and rendered, so create it, append, then remove after dragstart. Offsets position the cursor relative to the image's top-left. If you pass no image, the browser screenshots the dragged element.

html5
<script>
  card.addEventListener('dragstart', (e) => {
    const ghost = document.createElement('div');
    ghost.textContent = 'Dragging card';
    ghost.style.cssText = 'position:absolute;top:-999px;' +
                          'padding:8px;background:#3498db;color:#fff;';
    document.body.appendChild(ghost);
    e.dataTransfer.setDragImage(ghost, 20, 20);
    setTimeout(() => document.body.removeChild(ghost), 0);
  });
</script>
11

Web Workers

Worker Basics

new Worker(url) spawns a background thread that runs the script file. Communication is via postMessage and onmessage, with data copied (structured clone) between threads. Workers cannot touch the DOM, window, or document—but they can use fetch, IndexedDB, and setTimeout. Use them for heavy computation so the UI stays responsive.

html5
<!-- main.js -->
<script>
  const worker = new Worker('worker.js');

  worker.postMessage({ cmd: 'sum', nums: [1, 2, 3] });

  worker.onmessage = (e) => {
    console.log('Result:', e.data);   // 6
  };

  worker.onerror = (e) => {
    console.error(e.message, e.filename, e.lineno);
  };
</script>

Worker Script

Inside a worker, self (or this) is the global scope—postMessage and onmessage hang off it. There is no window or document. Workers can use fetch, importScripts(), setTimeout/setInterval, and IndexedDB. importScripts('a.js','b.js') synchronously loads other scripts into the worker. Each worker runs in its own isolate.

html5
// worker.js
self.onmessage = (e) => {
  const { cmd, nums } = e.data;
  if (cmd === 'sum') {
    let total = 0;
    for (const n of nums) total += n;
    self.postMessage(total);
  } else if (cmd === 'heavy') {
    const result = doExpensiveWork();
    self.postMessage(result);
  }
};

function doExpensiveWork() {
  // ... long-running computation
  return 'done';
}

Posting Messages

postMessage(data, transferables) sends data, copying it via structured clone. Pass an array of ArrayBuffer, MessagePort, or ImageBitmap in the second argument to TRANSFER ownership instead of copying—much faster for large buffers, but the original becomes unusable in the sending thread.

html5
<script>
  const w = new Worker('worker.js');

  // Plain object
  w.postMessage({ type: 'start' });

  // Transferable: zero-copy move of a buffer
  const buffer = new ArrayBuffer(1024);
  w.postMessage(buffer, [buffer]);
  // buffer is now "neutered" in main thread

  // Two-way communication
  w.onmessage = (e) => console.log('Worker said:', e.data);
</script>

Transferable Objects

Transferable objects (ArrayBuffer, MessagePort, ImageBitmap, OffscreenCanvas) move between threads without copying. transferControlToOffscreen hands a canvas to a worker, which can then render to it independently—great for smooth animations while the main thread is busy. The main thread loses access to the transferred object.

html5
<script>
  // Send a canvas ImageBitmap to a worker for processing
  const offscreen = canvas.transferControlToOffscreen();
  const worker = new Worker('paint.js');
  worker.postMessage({ canvas: offscreen }, [offscreen]);

  // Now the worker draws directly to the canvas
  // without round-trips to the main thread.
</script>

<!-- paint.js -->
self.onmessage = (e) => {
  const ctx = e.data.canvas.getContext('2d');
  ctx.fillStyle = '#3498db';
  ctx.fillRect(0, 0, 100, 100);
};

Shared Workers

A SharedWorker is a single worker shared by multiple tabs of the same origin. Each connection gets a MessagePort via the connect event. Use it for cross-tab state (live counts, shared caches) or a single WebSocket fan-out. SharedWorkers are not supported in Safari on iOS and have quirks; test before relying on them.

html5
<!-- main.js -->
<script>
  const sw = new SharedWorker('shared.js');
  sw.port.onmessage = (e) => console.log('Broadcast:', e.data);
  sw.port.start();
  sw.port.postMessage({ from: 'tab-A' });
</script>

<!-- shared.js -->
const ports = new Set();
self.onconnect = (e) => {
  const port = e.ports[0];
  ports.add(port);
  port.onmessage = (ev) => {
    // Broadcast to every connected tab
    for (const p of ports) p.postMessage(ev.data);
  };
  port.start();
};
12

WebSockets

Opening a Connection

new WebSocket(url) opens a persistent, bidirectional connection. Use wss:// (TLS) in production—ws:// is plaintext and blocked on HTTPS pages. The readyState goes CONNECTING (0) to OPEN (1) to CLOSING (2) to CLOSED (3). Listen for open, message, error, and close events.

html5
<script>
  const ws = new WebSocket('wss://echo.example.com/chat');

  ws.addEventListener('open', () => {
    console.log('Connected');
    ws.send('Hello server!');
  });

  ws.addEventListener('message', (e) => {
    console.log('Received:', e.data);
  });

  ws.addEventListener('close', (e) => {
    console.log('Closed', e.code, e.reason);
  });
</script>

Sending Messages

send() accepts strings, Blobs, ArrayBuffers, and ArrayBufferViews. For structured data, JSON.stringify first and parse on the other end. Messages are queued if sent before open, but to be safe check readyState === OPEN. The protocol is full-duplex—either side can send at any time.

html5
<script>
  // Strings
  ws.send('plain text');

  // JSON
  ws.send(JSON.stringify({ type: 'chat', text: 'hi' }));

  // Binary
  const blob = new Blob([new Uint8Array([1,2,3])]);
  ws.send(blob);
  ws.send(new ArrayBuffer(8));

  // Check state before sending
  if (ws.readyState === WebSocket.OPEN) ws.send(data);
</script>

Receiving Messages

message events deliver e.data as a string (text), Blob (binary default), or ArrayBuffer (set ws.binaryType = 'arraybuffer'). For text protocols, JSON is the most common encoding. For binary, choose Blob for files/images and ArrayBuffer for numeric parsing. Define a message schema so both sides agree on shape.

html5
<script>
  ws.addEventListener('message', (e) => {
    // Text messages: e.data is a string
    if (typeof e.data === 'string') {
      const msg = JSON.parse(e.data);
      console.log(msg.type, msg.text);
    }
    // Binary: e.data is a Blob or ArrayBuffer
    else {
      e.data.arrayBuffer().then((buf) => {
        const view = new DataView(buf);
        console.log(view.getInt32(0));
      });
    }
  });

  // Force binary type
  ws.binaryType = 'arraybuffer';
</script>

Errors & Closing

error events carry little detail for security; check the close event afterward. close(code, reason) initiates a clean shutdown; code 1000 is normal closure, 1006 means the connection dropped without a close frame (network issue). Codes 4000-4999 are reserved for application use. wasClean is true only when both sides completed the handshake.

html5
<script>
  ws.addEventListener('error', (e) => {
    console.error('Socket error', e);
  });

  ws.addEventListener('close', (e) => {
    console.log('code:', e.code, 'reason:', e.reason, 'clean:', e.wasClean);
  });

  // Close gracefully (1000 = normal closure)
  ws.close(1000, 'goodbye');

  // Common codes:
  //   1000 normal, 1001 going away, 1006 abnormal (no close frame)
  //   4000-4999 app-defined
</script>

Reconnection

Browsers don't auto-reconnect, so implement it yourself with exponential backoff (delay = min(base * 2^retry, max)). Send periodic pings so dead sockets are detected and closed. Reconnect on close but cap the retry interval to avoid hammering the server. Reset the retry counter once connected successfully.

html5
<script>
  let ws;
  let retry = 0;

  function connect() {
    ws = new WebSocket('wss://example.com/live');
    ws.onopen = () => { retry = 0; console.log('open'); };
    ws.onmessage = (e) => console.log(e.data);
    ws.onclose = () => {
      const delay = Math.min(1000 * 2 ** retry, 30000);
      retry++;
      setTimeout(connect, delay);
    };
  }
  connect();

  // Heartbeat to detect dead connections
  setInterval(() => {
    if (ws.readyState === WebSocket.OPEN) ws.send('ping');
  }, 30000);
</script>
13

History API

pushState

pushState(state, title, url) adds a new entry to the session history and updates the address bar without reloading the page—the core of client-side routing. The URL must be same-origin or pushState throws. The title argument is currently ignored by browsers. The state object is serializable and retrievable via history.state.

html5
<script>
  // Add a new entry WITHOUT reloading
  history.pushState(
    { page: 'about' },      // state object
    'About',                // title (ignored by most browsers)
    '/about'                // URL
  );

  console.log(history.length);     // entries count
  console.log(history.state);      // { page: 'about' }
</script>

replaceState

replaceState(state, title, url) works like pushState but replaces the current entry instead of adding a new one—so back/forward skip over the original. Use it to update the URL or state without polluting history, e.g., after applying a filter you don't want the user to 'go back' through.

html5
<script>
  // Replace the CURRENT entry (no new history item)
  history.replaceState(
    { page: 'home', tab: 'featured' },
    '',
    '/home?tab=featured'
  );

  // Useful for: cleaning up redirects, updating URL after
  // AJAX filter changes, or normalizing the entry URL.
</script>

popstate Event

popstate fires when the user clicks back/forward or history.go/back/forward is called—not on pushState/replaceState. The event.state is the state object from the entry being navigated to. Most SPA routers call the render function in both navigate() and the popstate handler so all navigations update the view.

html5
<script>
  window.addEventListener('popstate', (e) => {
    console.log('Location:', location.pathname);
    console.log('State:', e.state);
    renderRoute(location.pathname);
  });

  function navigate(path) {
    history.pushState({ path }, '', path);
    renderRoute(path);
  }

  // NOTE: pushState/replaceState do NOT fire popstate.
  // Only back/forward (and history.go) do.
</script>

Navigation Methods

back(), forward(), and go(delta) move through the session history; go(0) reloads. history.length is the number of entries in the current session, capped by the browser. These methods are asynchronous—popstate fires once navigation completes. Calling go beyond the bounds does nothing.

html5
<script>
  history.back();              // go back one entry
  history.forward();           // go forward one entry
  history.go(-2);              // go back two entries
  history.go(2);               // go forward two entries
  history.go(0);               // reload current page

  if (history.length === 1) {
    console.log('First page in this tab');
  }
</script>

SPA Routing

A minimal SPA router: intercept internal link clicks, pushState to update the URL, and render the new view. Handle popstate so back/forward works. Use data-link or a class to mark internal links, and skip external or target=_blank links. Real frameworks add nested routes, params, guards, and scroll restoration.

html5
<script>
  document.body.addEventListener('click', (e) => {
    const a = e.target.closest('a');
    if (!a || !a.matches('[data-link]')) return;
    if (a.origin !== location.origin) return;

    e.preventDefault();
    history.pushState({ path: a.pathname }, '', a.pathname);
    render(a.pathname);
  });

  window.addEventListener('popstate', () => render(location.pathname));

  function render(path) {
    fetch(path, { headers: { 'X-Ajax': '1' } })
      .then((r) => r.text())
      .then((html) => {
        document.querySelector('#app').innerHTML = html;
      });
  }
</script>
14

Inline Semantic Elements

time Element

time represents a specific date, time, or duration. The machine-readable value goes in the datetime attribute (YYYY-MM-DD, HH:MM, or full ISO 8601 with timezone); the human-readable text is the content. Search engines and calendar apps use datetime to extract events. Without datetime, the content itself must be a valid date string.

html5
<p>
  Published on
  <time datetime="2024-07-04">July 4, 2024</time>.
</p>
<p>
  Event starts at
  <time datetime="2024-07-04T19:00-05:00">7 PM EST</time>.
</p>
<p>
  Open
  <time datetime="09:00">9:00 AM</time>-
  <time datetime="17:00">5:00 PM</time> daily.
</p>

mark Element

mark highlights text as relevant for the user's current context, like search-term matches or notes the user added. Unlike strong or em it carries no semantic emphasis—just visual highlighting. The default style is a yellow background. It's also useful inside blockquotes to mark text the quoter wants to draw attention to.

html5
<p>
  The function <code>querySelector()</code> returns the
  first match, while <mark>querySelectorAll()</mark>
  returns a static NodeList.
</p>

<p>
  Search results: <mark>HTML5</mark> is the latest
  version of the Hypertext Markup Language.
</p>

details & summary

details creates a disclosure widget the user can expand or collapse—no JS required. summary is the always-visible label; click it to toggle. Add the open attribute to expand by default. The toggle event fires when it opens or closes. Great for FAQs, advanced settings, and progressive disclosure of content.

html5
<details>
  <summary>What is HTML5?</summary>
  <p>HTML5 is the fifth major revision of the HTML standard,
  introducing semantic elements, new form controls, and APIs
  for graphics, media, and offline apps.</p>
</details>

<details open>
  <summary>System requirements</summary>
  <ul>
    <li>Modern browser (Chrome, Firefox, Safari, Edge)</li>
    <li>JavaScript enabled</li>
  </ul>
</details>

dialog Element

dialog represents a modal or non-modal box. show() opens it non-modally; showModal() opens it modally with a backdrop and inert rest of the page. method='dialog' on a form closes the dialog and sets returnValue to the clicked button's value. The close event fires after closing. ::backdrop styles the overlay.

html5
<button onclick="dlg.showModal()">Open dialog</button>

<dialog id="dlg">
  <form method="dialog">
    <p>Are you sure?</p>
    <menu>
      <button value="cancel">Cancel</button>
      <button value="confirm">OK</button>
    </menu>
  </form>
</dialog>

<script>
  const dlg = document.getElementById('dlg');
  dlg.addEventListener('close', () => {
    console.log('User chose:', dlg.returnValue);
  });
</script>

abbr, cite & q

abbr expands abbreviations via title (hover tooltip) and aids screen readers. cite marks the title of a work (book, article, film)—not the author. q is an inline quote (browsers add quotes); blockquote is for block-level. dfn marks the defining instance of a term; subsequent uses can link to it with a.

html5
<p>
  <abbr title="World Health Organization">WHO</abbr>
  was founded in 1948.
</p>

<p>
  As <cite>MDN Web Docs</cite> notes,
  <q>HTML is the standard markup language.</q>
</p>

<p>
  Use <dfn>semantics</dfn> to describe the meaning of
  content, then refer to it normally.
</p>
15

Responsive Design

Meta Viewport

The viewport meta tag tells mobile browsers to use the device width as the layout viewport and to start at scale 1. Without it, phones pretend to be 980px wide. initial-scale=1 prevents zoom on load. viewport-fit=cover lets you use the notch area with env(safe-area-inset-*). Avoid user-scalable=no—it breaks accessibility.

html5
<head>
  <meta name="viewport"
        content="width=device-width, initial-scale=1, viewport-fit=cover">
</head>

<!-- Without this tag, mobile browsers render the page at a
     ~980px "desktop" width and shrink it down, making text
     unreadable. -->

<!-- viewport-fit=cover lets content extend into the notch
     area on iPhone X+. -->

Picture Element

picture lets you serve different images based on media conditions (art direction). The browser picks the first source whose media matches and ignores the rest. The img is mandatory as a fallback and provides alt text. Use this when you need to crop or change the image entirely per breakpoint, not just resize it.

html5
<picture>
  <!-- Art direction: different image per viewport -->
  <source media="(min-width: 800px)" srcset="wide.jpg">
  <source media="(orientation: portrait)" srcset="tall.jpg">
  <img src="default.jpg" alt="A responsive photo">
</picture>

<!-- Browser picks the first matching <source>,
     falling back to the <img>. -->

srcset & sizes

srcset lists image candidates with their intrinsic width (480w = 480px wide). sizes tells the browser how wide the image will be displayed at each breakpoint. The browser then picks the best candidate based on viewport and device pixel ratio—no JS needed. Use this when the same image just needs different resolutions.

html5
<img src="small.jpg"
     srcset="small.jpg 480w,
             medium.jpg 800w,
             large.jpg  1200w"
     sizes="(max-width: 600px) 100vw,
            (max-width: 1200px) 50vw,
            33vw"
     alt="A responsive photo">

Viewport Units

vw and vh are percentages of the viewport width/height. On mobile, 100vh can include the URL bar, causing jumpiness—prefer dvh (dynamic), svh (small), or lvh (large). clamp(min, preferred, max) is the cleanest way to do fluid typography that doesn't get too small or too large. vmin/vmax use the smaller/larger dimension.

html5
<style>
  .hero { height: 100vh; }           /* full viewport height */
  .half { width: 50vw; }             /* half the viewport width */

  /* Responsive typography */
  h1 {
    font-size: clamp(1.5rem, 4vw + 1rem, 3rem);
  }

  /* Use dvh/svh/lvh to handle mobile browser UI bars */
  .hero { height: 100dvh; }
</style>

Picture with Type

source with type lets you serve modern image formats with a JPEG/PNG fallback. The browser picks the first type it supports, ignoring the rest. AVIF and WebP offer 30-50% smaller files than JPEG at equal quality. Combined with loading='lazy' on the img, this is the most efficient way to deliver images.

html5
<picture>
  <!-- Modern format first, smaller file size -->
  <source type="image/avif" srcset="photo.avif">
  <source type="image/webp" srcset="photo.webp">
  <img src="photo.jpg" alt="Optimized photo" loading="lazy">
</picture>

<!-- Browser picks the first type it supports.
     AVIF and WebP are ~30-50% smaller than JPEG/PNG. -->
16

Accessibility (ARIA)

ARIA Roles

ARIA roles tell assistive tech what an element does, but the first rule of ARIA is: don't use it if a native HTML element exists. header, nav, main, article, footer already expose banner, navigation, main, article, contentinfo roles. Use ARIA for custom widgets (tab, dialog, slider, menu) that have no native equivalent, and pair roles with keyboard support.

html5
<div role="banner">           <!-- like <header> -->
  <h1>Site title</h1>
</div>

<div role="navigation">       <!-- like <nav> -->
  <a href="/">Home</a>
</div>

<div role="main">             <!-- like <main> -->
  <div role="article">        <!-- like <article> -->
    <p>Content</p>
  </div>
</div>

<button role="button" aria-pressed="false">Toggle</button>

aria-label & aria-labelledby

aria-label provides an accessible name when no visible text exists (icon-only buttons, etc.). aria-labelledby references the id of visible text to use as the name—useful when a heading already labels a section. Prefer a visible <label for> for form fields. aria-describedby points to hint text read after the label.

html5
<button aria-label="Close menu">×</button>

<nav aria-labelledby="main-nav-heading">
  <h2 id="main-nav-heading" class="sr-only">Main navigation</h2>
  <ul>...</ul>
</nav>

<input id="email" type="email">
<label id="email-label" for="email">Email address</label>
<!-- aria-labelledby="email-label" would also work -->

aria-hidden

aria-hidden='true' removes an element from the accessibility tree—use for purely decorative icons, duplicate text, or off-screen content that shouldn't be announced. Never hide focusable elements. The .sr-only pattern hides text visually while keeping it for screen readers—essential for labels that should be spoken but not seen.

html5
<!-- Decorative icon: hide from screen readers -->
<svg aria-hidden="true" focusable="false">
  <use href="#icon-check"></use>
</svg>

<!-- Visually hidden but still announced -->
<span class="sr-only">3 new messages</span>

<style>
  .sr-only {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap; border: 0;
  }
</style>

aria-live Regions

aria-live marks regions whose updates should be announced automatically. polite waits for a pause; assertive interrupts. role='alert' implies assertive and is the standard for error messages. aria-atomic='true' announces the whole region on change; aria-relevant controls which changes are spoken. Use sparingly—overuse makes interfaces noisy.

html5
<!-- Polite: announce when screen reader is idle -->
<div aria-live="polite" id="status">Saving...</div>

<!-- Assertive: announce immediately, may interrupt -->
<div aria-live="assertive" role="alert" id="error"></div>

<script>
  function show(msg) {
    document.getElementById('status').textContent = msg;
  }
  function fail(msg) {
    document.getElementById('error').textContent = msg;
  }
</script>

Focus Management

Keyboard users navigate via Tab; focus must always be visible and logical. :focus-visible styles focus only for keyboard, not mouse. When opening a modal, move focus into it; when closing, return focus to the trigger. Trap Tab inside the modal so focus doesn't escape to hidden content. Never set tabindex > 0; use 0 to make elements focusable and -1 to make them programmatically focusable.

html5
<style>
  :focus-visible {
    outline: 3px solid #3498db;
    outline-offset: 2px;
  }
</style>

<script>
  // Move focus into a modal
  function openModal(dlg) {
    dlg.showModal();
    dlg.querySelector('input, button').focus();
  }
  // Trap focus inside the modal (simplified)
  // Restore focus to the trigger on close
  const trigger = document.activeElement;
  dlg.addEventListener('close', () => trigger.focus());
</script>

Skip Links

A skip link is the first focusable element on the page and lets keyboard users jump past repetitive navigation to the main content. It's visually hidden until focused. Essential for accessibility—WCAG 2.1 Success Criterion 2.4.1. Pair it with a <main id='main'> landmark so the target is clear.

html5
<body>
  <a href="#main" class="skip-link">Skip to main content</a>
  <header>...nav with 30 links...</header>
  <main id="main">...</main>
</body>

<style>
  .skip-link {
    position: absolute;
    left: -999px;
    top: 0;
    background: #000; color: #fff;
    padding: 8px 16px;
    z-index: 1000;
  }
  .skip-link:focus {
    left: 0;
  }
</style>
17

Web Components

Custom Elements

customElements.define(name, class) registers a custom element. The name must contain a hyphen. Extend HTMLElement for autonomous elements, or a specific class like HTMLButtonElement (with is='name') to extend a built-in. Custom elements are the foundation of web components—reusable, framework-agnostic UI.

html5
<script>
  class MyButton extends HTMLElement {
    constructor() {
      super();
      this.addEventListener('click', () => {
        console.log('Custom button clicked');
      });
    }
  }
  customElements.define('my-button', MyButton);

  // Autonomous element: extends HTMLElement
  // Customized built-in: extends HTMLButtonElement
  //   <button is="my-button">
</script>

<my-button>Click me</my-button>

Shadow DOM

attachShadow({ mode: 'open' }) creates an encapsulated DOM tree attached to the element. Styles and ids inside the shadow tree don't leak out, and page styles don't leak in (except inheritable properties). :host targets the host element; ::slotted() styles slotted children. mode: 'closed' blocks external access to the shadowRoot—rarely needed.

html5
<script>
  class FancyCard extends HTMLElement {
    constructor() {
      super();
      const shadow = this.attachShadow({ mode: 'open' });
      shadow.innerHTML = `
        <style>
          :host { display: block; padding: 16px;
                  background: #f4f4f4; border-radius: 8px; }
          ::slotted(*) { color: #333; }
        </style>
        <div class="card">
          <slot></slot>
        </div>
      `;
    }
  }
  customElements.define('fancy-card', FancyCard);
</script>

HTML Templates

The template element holds inert HTML that is parsed but not rendered until cloned. Access its content via template.content, then cloneNode(true) and append. Templates are perfect for repeating structures and form the markup side of web components. Unlike innerHTML, templates can contain <tr>, <td>, <option>, and other elements with parsing quirks.

html5
<template id="row-template">
  <tr>
    <td class="name"></td>
    <td class="age"></td>
  </tr>
</template>

<script>
  const tpl = document.getElementById('row-template');
  const tbody = document.querySelector('tbody');

  function addRow(name, age) {
    const clone = tpl.content.cloneNode(true);
    clone.querySelector('.name').textContent = name;
    clone.querySelector('.age').textContent = age;
    tbody.appendChild(clone);
  }
  addRow('Ada', 36);
</script>

Slots

Slots let consumers pass light-DOM children into specific places in the shadow tree. A <slot name='x'> receives children with slot='x'; an unnamed <slot> receives the rest. The slotted children stay in the light DOM—the slot is just an insertion point. Listen to slotchange on the slot to react when slotted nodes change.

html5
<fancy-card>
  <h2 slot="title">Card Title</h2>
  <p>Body text goes here.</p>
</fancy-card>

<!-- Inside the shadow DOM of <fancy-card>: -->
<template>
  <div class="card">
    <slot name="title"></slot>
    <div class="body"><slot></slot></div>
  </div>
</template>

Lifecycle Callbacks

Custom elements have lifecycle callbacks: connectedCallback (inserted into DOM), disconnectedCallback (removed), attributeChangedCallback (only for attributes listed in observedAttributes), and adoptedCallback (moved to a new document). The constructor runs on creation—keep it light. Do setup in connectedCallback, cleanup in disconnectedCallback. Upgrade happens automatically for elements already in the DOM when define() is called.

html5
<script>
  class Observed extends HTMLElement {
    static get observedAttributes() {
      return ['count', 'label'];
    }
    constructor() { super(); }
    connectedCallback() {
      console.log('Added to DOM');
    }
    disconnectedCallback() {
      console.log('Removed from DOM');
    }
    attributeChangedCallback(name, oldVal, newVal) {
      console.log(name, 'changed', oldVal, '->', newVal);
    }
  }
  customElements.define('observed-el', Observed);
</script>
18

Offline Applications

Service Worker Registration

A service worker is a script the browser runs in the background, separate from a page, enabling offline support, push notifications, and background sync. Register it from a page script; the SW file must be served over HTTPS (or localhost) and its scope is its directory by default. Once installed, it controls page loads via fetch events.

html5
<script>
  if ('serviceWorker' in navigator) {
    window.addEventListener('load', () => {
      navigator.serviceWorker
        .register('/sw.js', { scope: '/' })
        .then((reg) => console.log('SW registered', reg.scope))
        .catch((err) => console.error('SW failed', err));
    });
  }
</script>

Cache API

The Cache API stores Request/Response pairs, persisted across sessions. In the install event, pre-cache critical assets. In the fetch event, respond from cache first and fall back to the network (cache-first strategy). Other strategies: network-first, stale-while-revalidate, cache-only, network-only. Bump the cache name to invalidate old entries.

html5
// sw.js
const CACHE = 'app-v1';
const ASSETS = ['/', '/styles.css', '/app.js', '/offline.html'];

self.addEventListener('install', (e) => {
  e.waitUntil(
    caches.open(CACHE).then((c) => c.addAll(ASSETS))
  );
});

self.addEventListener('fetch', (e) => {
  e.respondWith(
    caches.match(e.request).then((r) => r || fetch(e.request))
  );
});

Offline Fallback

A network-first strategy with an offline fallback tries the network, then cache, then a generic offline page. The activate event runs after install completes and is the right place to delete old caches—otherwise they pile up. Clients can claim existing tabs immediately with self.clients.claim() inside activate.

html5
// sw.js
self.addEventListener('fetch', (e) => {
  e.respondWith(
    fetch(e.request).catch(() =>
      caches.match(e.request).then((r) =>
        r || caches.match('/offline.html')
      )
    )
  );
});

// Activate event: clean up old caches
self.addEventListener('activate', (e) => {
  e.waitUntil(
    caches.keys().then((keys) =>
      Promise.all(
        keys.filter((k) => k !== 'app-v1')
            .map((k) => caches.delete(k))
      )
    )
  );
});

Web App Manifest

A web app manifest is a JSON file describing how the app should look when installed on the home screen. name/short_name, icons, start_url, and display (standalone, fullscreen, minimal-ui, browser) are the key fields. Combined with a service worker, a manifest makes the app installable (PWA). theme_color affects the OS UI chrome.

html5
<!-- index.html -->
<link rel="manifest" href="manifest.json">

<!-- manifest.json -->
{
  "name": "My App",
  "short_name": "App",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#3498db",
  "icons": [
    { "src": "/icon-192.png", "sizes": "192x192", "type": "image/png" },
    { "src": "/icon-512.png", "sizes": "512x512", "type": "image/png" }
  ]
}

Background Sync

Background Sync (registered via navigator.serviceWorker.ready.then(r => r.sync.register('tag'))) defers work until the user has connectivity. The browser fires a sync event in the service worker when network returns—even if the page is closed. Use it to send queued form submissions, messages, or analytics. tag identifies the sync; duplicates are coalesced.

html5
// sw.js
self.addEventListener('sync', (e) => {
  if (e.tag === 'send-messages') {
    e.waitUntil(sendQueuedMessages());
  }
});

async function sendQueuedMessages() {
  const queue = await getQueue();
  for (const msg of queue) {
    try { await fetch('/api/send', { method: 'POST', body: msg }); }
    catch (e) { throw e; }   // retry later
  }
}
19

SEO Best Practices

Title & Meta Description

The title (under ~60 chars) is the most important on-page SEO signal and appears as the search result's headline. The meta description (under ~160 chars) is the snippet text—it doesn't affect ranking but affects click-through. Use one title per page, include the primary keyword, and make it compelling. canonical prevents duplicate-content issues.

html5
<head>
  <title>HTML5 Cheatsheet - Semantic Elements & APIs</title>
  <meta name="description"
        content="A quick reference to HTML5 semantic elements,
                 form types, and browser APIs with examples.">
  <meta name="robots" content="index, follow">
  <link rel="canonical" href="https://example.com/html5">
</head>

Open Graph & Twitter Cards

Open Graph tags control how your page looks when shared on Facebook, LinkedIn, Slack, and most social platforms. og:image should be 1200x630px. Twitter Cards use a similar set with twitter: prefixes (or fall back to OG). Without these, shares get a random or no preview image, dramatically reducing click-through.

html5
<head>
  <!-- Open Graph (Facebook, LinkedIn, etc.) -->
  <meta property="og:title" content="HTML5 Cheatsheet">
  <meta property="og:description" content="Quick HTML5 reference.">
  <meta property="og:image" content="https://example.com/og.png">
  <meta property="og:url" content="https://example.com/html5">
  <meta property="og:type" content="article">

  <!-- Twitter Card -->
  <meta name="twitter:card" content="summary_large_image">
  <meta name="twitter:site" content="@example">
</head>

Canonical & hreflang

rel='canonical' tells search engines which URL is the master version when several URLs have similar content (print, sort, query params)—prevents duplicate-content penalties. hreflang points to translated or regional versions so Google serves the right one. Always include an x-default for unmatched locales. Both must be absolute URLs.

html5
<head>
  <!-- Canonical: the master URL for duplicate content -->
  <link rel="canonical" href="https://example.com/article">

  <!-- hreflang: language/region alternatives -->
  <link rel="alternate" hreflang="en" href="https://example.com/en/article">
  <link rel="alternate" hreflang="es" href="https://example.com/es/article">
  <link rel="alternate" hreflang="x-default" href="https://example.com/article">
</head>

Structured Data (JSON-LD)

JSON-LD structured data describes your page in a machine-readable format (schema.org vocabulary) and enables rich results in search: article carousels, breadcrumbs, FAQs, reviews, events, products. Put it in a <script type='application/ld+json'>. Validate with Google's Rich Results Test. It doesn't directly boost rankings but improves how results look.

html5
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Article",
  "headline": "HTML5 Cheatsheet",
  "author": { "@type": "Person", "name": "Ada" },
  "datePublished": "2024-07-04",
  "image": "https://example.com/cover.png",
  "publisher": {
    "@type": "Organization",
    "name": "Example"
  }
}
</script>

Semantic Headings

Search engines and screen readers use headings to understand the page outline. Use one h1 (the page title), then h2 for major sections, h3 for subsections—never skip levels (no h2 straight to h4). Don't choose a heading for its size; use CSS for styling. A logical heading hierarchy improves both SEO and accessibility.

html5
<article>
  <h1>Article Title (only one h1 per page)</h1>
  <p>Intro paragraph.</p>

  <section>
    <h2>First major section</h2>
    <p>Content...</p>
    <h3>Subsection</h3>
    <p>Detail...</p>
  </section>

  <section>
    <h2>Second major section</h2>
    <p>Content...</p>
  </section>
</article>
20

Performance Optimization

preload

rel='preload' tells the browser to fetch a high-priority resource the current page will need, before it would normally be discovered. Always specify as (font, style, script, image, fetch, etc.) so the browser applies correct priorities and CSP. Fonts need crossorigin. Use preload only for truly critical resources—overuse wastes bandwidth.

html5
<head>
  <!-- Preload critical resources for the current page -->
  <link rel="preload" href="/fonts/main.woff2"
        as="font" type="font/woff2" crossorigin>
  <link rel="preload" href="/css/critical.css" as="style">
  <link rel="preload" href="/js/app.js" as="script">

  <!-- Preload a hero image -->
  <link rel="preload" href="/hero.webp" as="image">
</head>

prefetch & preconnect

preconnect completes DNS, TCP, and TLS to an origin early, saving ~100-300ms when the actual request happens—use for critical third-party domains. dns-prefetch is the lighter version (DNS only). prefetch fetches a low-priority resource for a likely next navigation. Use them sparingly: too many preconnects waste sockets, and prefetched resources may go unused.

html5
<head>
  <!-- preconnect: early handshake to a third-party origin -->
  <link rel="preconnect" href="https://cdn.example.com">
  <link rel="preconnect" href="https://api.example.com" crossorigin>

  <!-- dns-prefetch: just DNS lookup, lighter -->
  <link rel="dns-prefetch" href="https://cdn.example.com">

  <!-- prefetch: fetch a resource for the NEXT navigation -->
  <link rel="prefetch" href="/next-page.html">
</head>

lazy loading

loading='lazy' on img and iframe defers loading until the element approaches the viewport—saving bandwidth and CPU on initial render. decoding='async' prevents image decode from blocking the main thread. Always set width/height to avoid layout shift. Use loading='eager' (default) and fetchpriority='high' only for above-the-fold hero images. Supported in all modern browsers.

html5
<!-- Native lazy loading (no JS needed) -->
<img src="photo.jpg" loading="lazy" decoding="async" alt="...">
<iframe src="embed.html" loading="lazy"></iframe>

<!-- Eager (default) loads immediately -->
<img src="hero.jpg" loading="eager" fetchpriority="high" alt="Hero">

<!-- Defer non-critical images until they near the viewport -->
<img src="below-fold.jpg" loading="lazy" width="800" height="600" alt="">

defer & async

A plain <script> blocks HTML parsing while it downloads and runs. async downloads in parallel and runs as soon as ready—use for independent scripts (analytics, ads) where order doesn't matter. defer downloads in parallel but waits to run, in order, after the document is parsed—use for main app scripts that depend on the DOM. defer scripts run before DOMContentLoaded.

html5
<head>
  <!-- async: download in parallel, run ASAP (order not guaranteed) -->
  <script src="analytics.js" async></script>

  <!-- defer: download in parallel, run after parse, in order -->
  <script src="app.js" defer></script>
  <script src="page.js" defer></script>

  <!-- Classic: block parsing to download and run -->
  <script src="blocking.js"></script>
</head>

Resource Hints Summary

preload (current page), prefetch (next page), preconnect (origin handshake), and dns-prefetch (DNS only) are the core resource hints. prerender is deprecated in favor of the Speculation Rules API, which can fully prerender pages for instant navigation. Apply hints based on real user behavior—over-hinting wastes data and can hurt performance.

html5
<head>
  <link rel="preload"    href="/critical.woff2" as="font" crossorigin>
  <link rel="preconnect" href="https://cdn.example.com">
  <link rel="dns-prefetch" href="https://cdn.example.com">
  <link rel="prefetch"   href="/next-page.html">

  <!-- Speculation Rules API (Chrome) -->
  <script type="speculationrules">
  { "prerender": [{ "where": { "href_matches": "/next/*" } }] }
  </script>
</head>

Critical Rendering

Inlining critical CSS for above-the-fold content removes the render-blocking request and improves First Contentful Paint. Load the rest via preload+swap. content-visibility: auto lets the browser skip rendering off-screen sections, dramatically improving scroll performance for long pages—pair with contain-intrinsic-size to reserve space and avoid scrollbar jumpiness.

html5
<head>
  <!-- Inline critical CSS for above-the-fold -->
  <style>
    body { margin: 0; font: system-ui; }
    .hero { height: 60vh; background: #3498db; }
  </style>

  <!-- Load the rest asynchronously -->
  <link rel="preload" href="/full.css" as="style" onload="this.rel='stylesheet'">
  <noscript><link rel="stylesheet" href="/full.css"></noscript>
</head>
<body>
  <!-- Use content-visibility to skip off-screen rendering -->
  <section style="content-visibility: auto; contain-intrinsic-size: 500px;">
    Heavy content here
  </section>
</body>

Was this helpful?

Learning path

Learn from scratch

Learn this language from the ground up with structured lessons.