1. HTML Document Structure and Syntax

1.1 HTML5 Document Structure and Boilerplate

Element Purpose Required Notes
<!DOCTYPE html> Declares document as HTML5 Yes Must be first line, triggers standards mode
<html> Root element containing all content Yes Should include lang attribute
<head> Contains metadata and document info Yes Not displayed in browser viewport
<meta charset> Character encoding declaration Yes Should be within first 1024 bytes
<title> Page title shown in browser tab Yes Important for SEO and accessibility
<body> Contains visible page content Yes Only one per document

Example: Complete HTML5 boilerplate structure

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta name="description" content="Page description for SEO">
  <title>Page Title</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <!-- Visible content goes here -->
  <script src="script.js"></script>
</body>
</html>

1.2 Doctype Declarations and Legacy Support

Version Doctype Declaration Usage
HTML5 <!DOCTYPE html> All modern browsers - Recommended for all new projects
HTML 4.01 Strict <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> DEPRECATED Legacy only
HTML 4.01 Transitional <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> DEPRECATED Allowed deprecated elements
XHTML 1.0 Strict <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> DEPRECATED XML-based HTML
Quirks Mode No doctype or invalid doctype Never use - Triggers non-standard rendering
Note: HTML5 doctype is case-insensitive and works in all browsers including IE6+. Always use it for new projects.

1.3 Character Encoding and Meta Setup

Meta Tag Purpose Example Priority
charset Declares character encoding <meta charset="UTF-8"> Critical - First in <head>
viewport Controls mobile rendering and zoom <meta name="viewport" content="width=device-width, initial-scale=1.0"> Essential for responsive design
description Page description for search engines <meta name="description" content="155-160 chars"> Important for SEO
http-equiv HTTP header equivalents <meta http-equiv="X-UA-Compatible" content="IE=edge"> Legacy IE compatibility
author Document author information <meta name="author" content="Name"> Optional metadata
keywords Search keywords <meta name="keywords" content="word1, word2"> DEPRECATED Ignored by major search engines

Common Viewport Settings

Property Values
width device-width, number
initial-scale 1.0 (100% zoom)
maximum-scale 1.0 to 10.0
user-scalable yes, no

Character Encoding Options

Encoding Use Case
UTF-8 Recommended - Universal support
ISO-8859-1 Western European (legacy)
Windows-1252 Windows Western (legacy)

Example: Complete meta setup for modern websites

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta name="description" content="Concise page description under 160 characters">
  <meta name="author" content="Your Name">
  <meta name="robots" content="index, follow">
  <title>Page Title - Site Name</title>
</head>

1.4 Element Syntax and Nesting Rules

Rule Description Valid Example Invalid Example
Opening/Closing Tags Most elements require both tags <p>Text</p> <p>Text (missing closing)
Tag Names Case-insensitive, lowercase preferred <div>, <DIV> Both valid, <div> recommended
Attribute Quotes Optional but recommended <div class="name"> <div class=name> (works but avoid)
Attribute Values Can use single or double quotes class="text", class='text' Both valid, be consistent
Boolean Attributes Presence = true, value optional <input disabled>, <input disabled="disabled"> Both valid forms
Proper Nesting Elements must close in reverse order <div><p>Text</p></div> <div><p>Text</div></p>

Block vs Inline Nesting

Container Can Contain
Block Elements Block + Inline elements
Inline Elements Only inline elements + text
<p> Inline only (no block elements)
<a> HTML5: Can contain block elements

Special Nesting Rules

Element Restriction
<ul>/<ol> Direct children must be <li>
<dl> Only <dt> and <dd> allowed
<table> Specific structure required
<form> Cannot nest forms

Example: Correct element nesting patterns

<!-- Valid nesting -->
<div class="container">
  <p>Paragraph with <strong>bold</strong> and <em>italic</em> text.</p>
  <ul>
    <li>List item with <a href="#">link</a></li>
  </ul>
</div>

<!-- Invalid: Block element inside inline -->
<!-- WRONG: <span><div>Text</div></span> -->

<!-- Invalid: Improper closing order -->
<!-- WRONG: <div><p>Text</div></p> -->

1.5 Void Elements and Self-closing Tags

Element HTML5 Syntax XHTML Syntax Purpose
<br> <br> <br /> Line break
<hr> <hr> <hr /> Thematic break / horizontal rule
<img> <img src="url" alt="text"> <img src="url" alt="text" /> Embedded image
<input> <input type="text"> <input type="text" /> Form input control
<link> <link rel="stylesheet" href="url"> <link rel="stylesheet" href="url" /> External resource link
<meta> <meta charset="UTF-8"> <meta charset="UTF-8" /> Metadata
<area> <area shape="rect" coords="x,y"> <area shape="rect" coords="x,y" /> Image map area
<base> <base href="url"> <base href="url" /> Base URL for relative links
<col> <col span="2"> <col span="2" /> Table column definition
<embed> <embed src="url"> <embed src="url" /> External content embed
<param> <param name="x" value="y"> <param name="x" value="y" /> Object parameters
<source> <source src="url" type="mime"> <source src="url" type="mime" /> Media source
<track> <track src="url" kind="subtitles"> <track src="url" kind="subtitles" /> Media text tracks
<wbr> <wbr> <wbr /> Word break opportunity
Note: In HTML5, the trailing slash in void elements is optional. Both <br> and <br /> are valid. Use consistent style throughout your document.

Example: Void elements in practical use

<!-- Line breaks -->
<p>First line<br>Second line<br>Third line</p>

<!-- Horizontal rule -->
<section>Content</section>
<hr>
<section>More content</section>

<!-- Image with required alt attribute -->
<img src="photo.jpg" alt="Descriptive text" width="300" height="200">

<!-- Form inputs -->
<form>
  <input type="text" placeholder="Name">
  <input type="email" placeholder="Email">
  <input type="submit" value="Send">
</form>

<!-- Multiple media sources -->
<video controls>
  <source src="video.mp4" type="video/mp4">
  <source src="video.webm" type="video/webm">
  <track src="subtitles.vtt" kind="subtitles" srclang="en">
</video>

1.6 HTML Comments and Conditional Comments

Type Syntax Use Case Browser Support
Standard Comment <!-- Comment text --> General documentation and notes All browsers
Multi-line Comment <!-- Line 1
Line 2 -->
Longer explanations or disabled code blocks All browsers
Conditional Comment (IE) <!--[if IE]>...<![endif]--> IE-specific code (legacy) DEPRECATED IE only
Conditional IE Version <!--[if lt IE 9]>...<![endif]--> Target specific IE versions DEPRECATED IE only
Downlevel-hidden <!--[if !IE]><!-->...<!--<![endif]--> Hide from IE, show to others DEPRECATED
Note: Comment Best Practices
  • Use for documentation and code organization
  • Avoid sensitive information (visible in source)
  • Keep comments concise and relevant
  • Remove debug comments in production
  • Use for temporarily disabling code

Conditional Comment Operators

Operator Meaning
lt Less than
lte Less than or equal
gt Greater than
gte Greater than or equal
! Not

Example: Various comment types and uses

<!-- Single line comment for documentation -->

<!-- 
  Multi-line comment for longer explanations
  or to temporarily disable code blocks
  during development and debugging
-->

<!-- Section: Main Navigation -->
<nav>...</nav>
<!-- End Section: Main Navigation -->

<!-- TODO: Add responsive images here -->

<!-- Legacy IE conditional comments (no longer needed) -->
<!--[if lt IE 9]>
  <script src="html5shiv.js"></script>
  <script src="respond.js"></script>
<![endif]-->
Warning: HTML comments are visible in page source. Never include passwords, API keys, or sensitive information. Comments increase file size and are downloaded by browsers.

Section 1 Key Takeaways

  • Always start with <!DOCTYPE html> for HTML5 documents
  • Include <meta charset="UTF-8"> as first element in <head>
  • Use viewport meta tag for responsive design: width=device-width, initial-scale=1.0
  • Follow proper nesting rules: close elements in reverse order of opening
  • Void elements don't require closing tags in HTML5
  • Comments are visible in source code - avoid sensitive information
  • Conditional comments are deprecated; use feature detection instead

2. HTML5 Semantic Elements and Structure

Element Purpose Typical Use Can Contain
<header> Introductory content or navigation Page header, article header, section header Any content except <header> or <footer>
<nav> Navigation links section Main menu, breadcrumbs, pagination, table of contents Links, lists, text content
<main> HTML5 Primary page content (unique per page) Main article, central content area Any content except <header>, <footer>, <aside>, <nav>
<aside> Tangentially related content Sidebars, pull quotes, advertising, related links Any flow content
<footer> Footer for nearest sectioning element Page footer, article footer, copyright info Any content except <header> or <footer>

Usage Rules

  • <main> must be unique (only one per page)
  • <main> should not be descendant of <article>, <aside>, <footer>, <header>, <nav>
  • <header> and <footer> can appear multiple times
  • <nav> for major navigation only
  • <aside> can be inside <article> or at page level

Accessibility Benefits

  • Screen readers identify page landmarks
  • Keyboard navigation shortcuts enabled
  • Clearer document structure for assistive tech
  • Better content prioritization
  • Improved skip navigation functionality

Example: Complete page structure with semantic elements

<body>
  <header>
    <h1>Site Name</h1>
    <nav>
      <ul>
        <li><a href="/">Home</a></li>
        <li><a href="/about">About</a></li>
        <li><a href="/contact">Contact</a></li>
      </ul>
    </nav>
  </header>

  <main>
    <article>
      <header>
        <h2>Article Title</h2>
        <p>By Author | Published: Jan 1, 2025</p>
      </header>
      <p>Article content...</p>
      <footer>
        <p>Tags: HTML, Semantic</p>
      </footer>
    </article>
  </main>

  <aside>
    <h3>Related Articles</h3>
    <ul>
      <li><a href="#">Article 1</a></li>
      <li><a href="#">Article 2</a></li>
    </ul>
  </aside>

  <footer>
    <p>&copy; 2025 Company Name. All rights reserved.</p>
  </footer>
</body>

2.2 Content Sectioning (section, article, hgroup)

Element Purpose When to Use Should Contain
<section> Thematic grouping of content Chapters, tabbed content, themed groups Heading + related content
<article> Self-contained, distributable content Blog posts, news articles, forum posts, comments Independent, reusable content
<hgroup> REMOVED Groups heading with subheading Removed from HTML5 spec, avoid use Multiple heading elements (h1-h6)
<div> Generic container (no semantic meaning) Styling/scripting only, when no semantic element fits Any content

<section> vs <article>

Aspect <section> <article>
Independence Part of a whole Standalone content
Reusability Context-dependent Fully reusable
RSS Feed No Yes, makes sense
Nesting Can contain <article> Can contain <section>
Note: <div> vs Semantic Elements
  • Use semantic elements first
  • <div> for styling containers only
  • Semantic = better SEO + accessibility
  • <div> has no meaning to assistive tech
  • Prefer <section> over <div> when possible

Example: Proper use of section and article

<!-- Article containing sections -->
<article>
  <h1>Complete Guide to HTML5</h1>
  <section>
    <h2>Introduction</h2>
    <p>Introduction content...</p>
  </section>
  <section>
    <h2>Semantic Elements</h2>
    <p>Semantic elements explanation...</p>
  </section>
</article>

<!-- Section containing articles (blog posts) -->
<section>
  <h2>Latest Blog Posts</h2>
  <article>
    <h3>Post Title 1</h3>
    <p>Post content...</p>
  </article>
  <article>
    <h3>Post Title 2</h3>
    <p>Post content...</p>
  </article>
</section>
Warning: <hgroup> was removed from HTML5 specification. Use <header> with heading and <p> for subtitles instead.

2.3 Heading Hierarchy and Document Outline

Level Element Purpose SEO Weight
1 <h1> Main page heading (one per page recommended) Highest
2 <h2> Major sections Very High
3 <h3> Subsections High
4 <h4> Sub-subsections Medium
5 <h5> Minor headings Low
6 <h6> Smallest headings Lowest
Best Practices:
  • One h1 per page (primary topic)
  • Don't skip heading levels (h1→h2→h3)
  • Use headings for structure, not styling
  • Each section should have a heading
  • Headings create document outline
  • Screen readers use headings for navigation
Common Mistakes:
  • ❌ Multiple h1 elements (debated)
  • ❌ Skipping levels (h1 → h3)
  • ❌ Using headings for font size only
  • ❌ Empty headings
  • ❌ Inconsistent hierarchy
  • ❌ Headings inside <address>

Example: Proper heading hierarchy

<!-- Correct hierarchy -->
<h1>Website Title</h1>
  <h2>Main Section</h2>
    <h3>Subsection</h3>
      <h4>Detail Point</h4>
    <h3>Another Subsection</h3>
  <h2>Second Main Section</h2>

<!-- Article with its own heading structure -->
<article>
  <h2>Article Title</h2>
  <p>Introduction...</p>
  <h3>First Point</h3>
  <p>Content...</p>
  <h3>Second Point</h3>
  <p>Content...</p>
</article>
Note: HTML5 sectioning elements (<article>, <section>, etc.) were intended to allow multiple h1 elements, but this is not well supported by assistive technologies. Stick to traditional hierarchy.

2.4 Address and Contact Information

Element Purpose Can Contain Cannot Contain
<address> Contact info for article/page author Contact details, links, text Headings, sectioning content, <address>
Appropriate Uses:
  • Author contact information
  • Company/organization contact details
  • Email addresses
  • Physical addresses
  • Phone numbers
  • Social media links
Inappropriate Uses:
  • ❌ Postal addresses unrelated to contact
  • ❌ Publication dates
  • ❌ Generic company info in content
  • ❌ Addresses in article text
  • ❌ Mailing list descriptions

Example: Various address element uses

<!-- Page footer contact -->
<footer>
  <address>
    Contact us at:<br>
    <a href="mailto:info@example.com">info@example.com</a><br>
    Phone: <a href="tel:+1234567890">+1 (234) 567-890</a><br>
    123 Main Street, City, State 12345
  </address>
</footer>

<!-- Article author contact -->
<article>
  <h2>Article Title</h2>
  <p>Article content...</p>
  <footer>
    <address>
      Written by <a href="mailto:author@example.com">John Doe</a><br>
      Follow on Twitter: <a href="https://twitter.com/johndoe">@johndoe</a>
    </address>
  </footer>
</article>

<!-- Multiple contact methods -->
<address>
  <strong>Customer Support:</strong><br>
  Email: <a href="mailto:support@example.com">support@example.com</a><br>
  Live Chat: <a href="/chat">Start Chat</a><br>
  Phone: Available 9AM-5PM EST
</address>
Note: Default browser styling typically italicizes <address> content. Use CSS to override if needed. The element provides semantic meaning, not just styling.

2.5 Time and Date Elements

Attribute Purpose Format Example
datetime Machine-readable date/time ISO 8601 format 2025-12-21
datetime (time) Specific time HH:MM or HH:MM:SS 14:30, 14:30:45
datetime (full) Date and time combined YYYY-MM-DDTHH:MM 2025-12-21T14:30
datetime (timezone) With timezone offset ...T...+/-HH:MM 2025-12-21T14:30+05:00
datetime (duration) Time duration P[n]Y[n]M[n]DT[n]H[n]M[n]S P3D (3 days), PT2H30M (2.5 hours)

Common Date Formats

Type Format Example
Date YYYY-MM-DD 2025-12-21
Year-Month YYYY-MM 2025-12
Year YYYY 2025
Week YYYY-W## 2025-W51

Duration Examples

Duration Format
1 hour PT1H
30 minutes PT30M
2 days P2D
1 year 6 months P1Y6M

Example: Time element in various contexts

<!-- Human-readable with machine-readable datetime -->
<p>Published on <time datetime="2025-12-21">December 21, 2025</time></p>

<!-- Specific time -->
<p>Event starts at <time datetime="2025-12-21T19:00">7:00 PM</time></p>

<!-- With timezone -->
<time datetime="2025-12-21T19:00-05:00">7:00 PM EST</time>

<!-- Duration -->
<p>Video length: <time datetime="PT2H30M">2 hours 30 minutes</time></p>

<!-- Relative dates -->
<p>Posted <time datetime="2025-12-20">yesterday</time></p>

<!-- Just year -->
<p>Copyright <time datetime="2025">2025</time></p>

<!-- Date range (use two time elements) -->
<p>Conference: <time datetime="2025-06-15">June 15</time> to 
   <time datetime="2025-06-17">June 17, 2025</time></p>
Note: The <time> element improves SEO and accessibility by providing machine-readable dates. Search engines and assistive technologies can parse and understand temporal information.

2.6 Ruby Annotations for East Asian Typography

Element Purpose Required Example Content
<ruby> Container for ruby annotation Yes Base text + annotation
<rb> Ruby base text Optional (implicit) Kanji, Hanzi characters
<rt> Ruby text (pronunciation/meaning) Yes Furigana, Pinyin, pronunciation
<rp> Ruby parentheses (fallback) Optional Parentheses for unsupported browsers
<rtc> Ruby text container Optional Groups multiple <rt> elements
Common Use Cases:
  • Japanese: Furigana for kanji pronunciation
  • Chinese: Pinyin romanization
  • Korean: Hanja pronunciation
  • Pronunciation guides
  • Translation annotations
  • Phonetic guides for learning

Browser Support

Element Support
<ruby> All modern browsers
<rt> All modern browsers
<rp> All browsers
<rtc>, <rb> Limited support

Example: Ruby annotations for different languages

<!-- Japanese Furigana (simple) -->
<ruby>
  漢字
  <rp>(</rp>
  <rt>かんじ</rt>
  <rp>)</rp>
</ruby>

<!-- Chinese Pinyin -->
<ruby>
  汉语
  <rp>(</rp>
  <rt>Hànyǔ</rt>
  <rp>)</rp>
</ruby>

<!-- Multiple characters with individual annotations -->
<ruby>
  <rb>東</rb><rt>とう</rt>
  <rb>京</rb><rt>きょう</rt>
</ruby>

<!-- Complex ruby with double annotations -->
<ruby>
  <rb>東京</rb>
  <rtc><rt>とうきょう</rt></rtc>
  <rtc><rt>Tokyo</rt></rtc>
</ruby>

<!-- Fallback for old browsers using <rp> -->
<ruby>
  日本語
  <rp>(</rp>
  <rt>にほんご</rt>
  <rp>)</rp>
</ruby>
<!-- Renders as: 日本語(にほんご) in unsupported browsers -->
Note: Always include <rp> elements for graceful degradation in browsers that don't support ruby annotations. The text will display with parentheses as fallback.

Section 2 Key Takeaways

  • Use <header>, <nav>, <main>, <aside>, <footer> for page structure
  • <main> must be unique per page; only one allowed
  • <article> for standalone content; <section> for thematic grouping
  • Maintain proper heading hierarchy (h1→h2→h3); don't skip levels
  • One h1 per page recommended for best SEO and accessibility
  • <address> for contact information only, not general addresses
  • <time> with datetime attribute for machine-readable dates
  • <ruby> with <rt> and <rp> for East Asian typography annotations

3. Text Content and Typography Elements

3.1 Block-level Text Elements (p, div, blockquote)

Element Purpose Display Semantic Meaning
<p> Paragraph of text Block (margin top/bottom) Text paragraph
<div> Generic container Block (no default margin) No semantic meaning (use for styling/layout only)
<blockquote> Extended quotation Block (indented, margin) Quoted content from external source
<cite> Citation/reference title Inline (italic) Title of creative work
<q> Inline quotation Inline (with quotes) Short quoted text
<hr> Thematic break Block (horizontal rule) Topic/section separation
Paragraph (<p>) Rules:
  • Cannot contain block elements
  • Can contain inline and text only
  • Auto-closes when encountering block element
  • Use for text content, not layout
  • Browser adds default margins
Blockquote Attributes:
Attribute Purpose
cite URL of quote source

Use <cite> element for visible citation

Example: Text block elements usage

<!-- Paragraphs -->
<p>This is a standard paragraph with text content.</p>
<p>Paragraphs can contain <strong>inline elements</strong> and <a href="#">links</a>.</p>

<!-- Blockquote with citation -->
<blockquote cite="https://example.com/source">
  <p>The only way to do great work is to love what you do.</p>
  <footer>— <cite>Steve Jobs</cite></footer>
</blockquote>

<!-- Inline quote -->
<p>As Einstein said, <q>Imagination is more important than knowledge.</q></p>

<!-- Thematic break -->
<section>First topic content</section>
<hr>
<section>New topic content</section>

<!-- Generic container (styling only) -->
<div class="content-wrapper">
  <p>Wrapped content</p>
</div>

3.2 List Elements (ul, ol, li, dl, dt, dd)

Element Type Purpose Child Elements
<ul> Unordered List Bulleted list (order doesn't matter) Only <li>
<ol> Ordered List Numbered list (sequential order) Only <li>
<li> List Item Individual list item Any flow content
<dl> Description List Term-description pairs Only <dt> and <dd>
<dt> Description Term Term being defined Inline content only
<dd> Description Details Definition/description of term Any flow content

Ordered List Attributes

Attribute Values Purpose
type 1, A, a, I, i Numbering style
start Number Starting value
reversed Boolean Descending order
List Item Attributes:
Attribute Purpose
value Set specific item number (ol only)
List Nesting:
  • Lists can be nested infinitely
  • Nest lists inside <li> elements
  • Can mix ul and ol in nested lists

Example: Various list types and configurations

<!-- Unordered list -->
<ul>
  <li>First item</li>
  <li>Second item</li>
  <li>Third item</li>
</ul>

<!-- Ordered list with attributes -->
<ol type="A" start="3">
  <li>Item C</li>
  <li>Item D</li>
  <li value="10">Item J (skip to 10)</li>
</ol>

<!-- Reversed numbering -->
<ol reversed>
  <li>Third</li>
  <li>Second</li>
  <li>First</li>
</ol>

<!-- Nested lists -->
<ul>
  <li>Main item 1
    <ul>
      <li>Sub item 1.1</li>
      <li>Sub item 1.2</li>
    </ul>
  </li>
  <li>Main item 2</li>
</ul>

<!-- Description list -->
<dl>
  <dt>HTML</dt>
  <dd>HyperText Markup Language</dd>
  
  <dt>CSS</dt>
  <dd>Cascading Style Sheets</dd>
  
  <dt>JavaScript</dt>
  <dd>Programming language for web interactivity</dd>
  <dd>Also known as ECMAScript</dd>
</dl>

3.3 Preformatted Content (pre, code, samp, kbd)

Element Purpose Whitespace Font
<pre> Preformatted text (preserves whitespace) Preserved Monospace
<code> Computer code fragment Collapsed Monospace
<samp> Sample program output Collapsed Monospace
<kbd> Keyboard input Collapsed Monospace
<var> Variable or placeholder Collapsed Italic (default)

Common Patterns

Pattern Use Case
<pre><code> Multi-line code blocks
<code> Inline code snippets
<samp> Console/terminal output
<kbd> Keyboard shortcuts
Important Notes:
  • <pre> preserves all whitespace and line breaks
  • Always escape HTML in code examples
  • <code> is inline by default
  • Combine <pre><code> for code blocks
  • <samp> vs <code>: output vs source code
  • <kbd> can be nested for key combinations

Example: Preformatted and code elements

<!-- Inline code -->
<p>Use the <code>console.log()</code> function to debug.</p>

<!-- Multi-line code block -->
<pre><code>function greet(name) {
    return `Hello, ${name}!`;
}
console.log(greet('World'));</code></pre>

<!-- Sample output -->
<p>The program outputs: <samp>Hello, World!</samp></p>

<!-- Keyboard input -->
<p>Press <kbd>Ctrl</kbd> + <kbd>C</kbd> to copy.</p>
<p>Save file with <kbd><kbd>Ctrl</kbd>+<kbd>S</kbd></kbd></p>

<!-- Variable/placeholder -->
<p>Replace <var>filename</var> with your actual file name.</p>

<!-- ASCII art / formatted text -->
<pre>
  *
 ***
*****
 ***
  *
</pre>

<!-- Terminal session -->
<pre><samp>$ npm install package-name
added 42 packages in 3.5s</samp></pre>
Warning: <pre> preserves ALL whitespace including leading spaces and blank lines. Be careful with indentation in your source code.

3.4 Inline Text Semantics (span, em, strong, mark)

Element Purpose Default Style Semantic Meaning
<span> Generic inline container None No semantic meaning (styling/scripting only)
<em> Emphasis Italic Stress emphasis
<strong> Strong importance Bold Strong importance/urgency
<mark> Highlighted/marked text Yellow background Relevance/reference highlight
<i> Alternate voice/mood Italic Technical terms, foreign words, thoughts
<b> Bring attention to Bold Keywords, product names (no importance)
<u> Unarticulated annotation Underline Spelling errors, proper names in Chinese
<s> Strikethrough Line through No longer accurate/relevant
<em> vs <i>:
  • <em>: Stress emphasis
  • <i>: Different quality (technical, foreign)
  • Screen readers emphasize <em>
  • Use <em> when meaning changes with stress
<strong> vs <b>:
  • <strong>: Important/urgent
  • <b>: Stylistic offset (no importance)
  • Use <strong> for warnings, critical info
  • Use <b> for keywords without emphasis

Use Cases by Element

Element Example Use
<mark> Search result highlights
<i> Foreign words, ship names
<b> Lead paragraph keywords
<u> Spelling mistake indication
<s> Old price, outdated info

Example: Inline text semantics in context

<!-- Emphasis changes meaning -->
<p><em>I</em> didn't say that.</p>
<p>I <em>didn't</em> say that.</p>
<p>I didn't <em>say</em> that.</p>

<!-- Strong importance -->
<p><strong>Warning:</strong> This action cannot be undone.</p>

<!-- Highlight/mark -->
<p>Search results for <mark>HTML</mark> elements.</p>

<!-- Italic for technical/foreign terms -->
<p>The <i>HMS Titanic</i> sank in 1912.</p>
<p>The term <i lang="fr">raison d'être</i> means reason for being.</p>

<!-- Bold for keywords (no importance) -->
<p>The <b>Responsive Web Design</b> approach adapts to screen sizes.</p>

<!-- Strikethrough for outdated info -->
<p>Price: <s>$99.99</s> $49.99 (Sale!)</p>

<!-- Underline for spelling errors -->
<p>She made a <u>speling</u> mistake.</p>

<!-- Generic span for styling -->
<p>Status: <span class="status-active">Active</span></p>

3.5 Text Modifications (del, ins, sub, sup, small)

Element Purpose Display Attributes
<del> Deleted/removed text Strikethrough cite, datetime
<ins> Inserted/added text Underline cite, datetime
<sub> Subscript Lowered, smaller None
<sup> Superscript Raised, smaller None
<small> Fine print/side comments Smaller text None

del/ins Attributes

Attribute Purpose Example
cite URL explaining change cite="changes.html"
datetime When change occurred datetime="2025-12-21"
Common Use Cases:
  • <sub>: Chemical formulas (H₂O), math
  • <sup>: Exponents (x²), footnotes
  • <small>: Copyright, disclaimers, legal
  • <del>/<ins>: Document revisions
  • Track changes in collaborative editing

Example: Text modification elements

<!-- Document revisions -->
<p>The meeting is scheduled for 
   <del datetime="2025-12-20T10:00">Thursday</del>
   <ins datetime="2025-12-20T14:30">Friday</ins>.
</p>

<!-- With citation -->
<p>Price: 
   <del cite="pricing-update.html">$99</del>
   <ins cite="pricing-update.html">$79</ins>
</p>

<!-- Subscript for chemical formulas -->
<p>Water: H<sub>2</sub>O</p>
<p>Carbon Dioxide: CO<sub>2</sub></p>

<!-- Superscript for exponents -->
<p>Area = πr<sup>2</sup></p>
<p>E = mc<sup>2</sup></p>

<!-- Superscript for footnotes -->
<p>This statement requires citation<sup>1</sup>.</p>

<!-- Small for fine print -->
<p>
  <small>© 2025 Company Name. All rights reserved.</small>
</p>
<p>
  Special offer! <small>*Terms and conditions apply</small>
</p>

<!-- Mathematical expressions -->
<p>
  (a + b)<sup>2</sup> = a<sup>2</sup> + 2ab + b<sup>2</sup>
</p>
Note: <del> and <ins> are block or inline depending on context. They can wrap paragraphs or be inline within text.

3.6 Abbreviations and Data (abbr, data, var, time)

Element Purpose Key Attribute Display
<abbr> Abbreviation or acronym title (full form) Dotted underline (tooltip on hover)
<data> Machine-readable content value (machine format) Normal text (inline)
<var> Variable/mathematical placeholder None Italic
<time> Date/time (covered in 2.5) datetime Normal text (inline)
<dfn> Defining instance of term None Italic
Abbreviation Best Practices:
  • Always include title attribute
  • Expand on first use in article
  • Use for both abbreviations and acronyms
  • Title shows on hover (desktop)
  • Improves accessibility for screen readers
Data Element Usage:
  • Links human-readable to machine-readable
  • Use for product codes, IDs, values
  • value attribute is machine format
  • Content is human-readable display
  • Useful for sorting, filtering

Example: Abbreviations and data elements

<!-- Abbreviations with title -->
<p>The <abbr title="World Wide Web">WWW</abbr> was invented by Tim Berners-Lee.</p>
<p>Use <abbr title="HyperText Markup Language">HTML</abbr> for structure.</p>
<p>Meeting at <abbr title="As Soon As Possible">ASAP</abbr>.</p>

<!-- Data element for machine-readable values -->
<p>Product: <data value="SKU-12345">Premium Widget</data></p>
<p>Rating: <data value="4.5">4.5 out of 5 stars</data></p>
<p>Price: <data value="49.99">$49.99</data></p>

<!-- Variable in mathematical context -->
<p>If <var>x</var> = 5, then <var>x</var> + 3 = 8</p>
<p>The formula is <var>f</var>(<var>x</var>) = <var>x</var><sup>2</sup></p>

<!-- Definition term -->
<p><dfn>Responsive Design</dfn> is an approach where design adapts to screen size.</p>
<p>A <dfn><abbr title="Application Programming Interface">API</abbr></dfn> 
   is a set of protocols for building software.</p>

<!-- Combined usage -->
<p>
  <abbr title="Uniform Resource Locator">URL</abbr> format:
  <code><var>protocol</var>://<var>domain</var>/<var>path</var></code>
</p>
Note: <dfn> should be used only for the defining instance of a term (first occurrence where it's explained), not for every mention.

Section 3 Key Takeaways

  • Use <p> for text paragraphs, <div> for layout/styling only
  • <blockquote> for extended quotes with optional cite attribute
  • Three list types: <ul> (unordered), <ol> (ordered), <dl> (description)
  • <pre> preserves whitespace; combine with <code> for code blocks
  • <em> for stress emphasis, <strong> for importance (not just styling)
  • <i> and <b> have semantic meaning (not just italic/bold)
  • <sub> and <sup> for subscript/superscript (formulas, footnotes)
  • <abbr> with title attribute improves accessibility
  • <data> links human-readable text to machine-readable values

4. HTML Attributes and Global Properties

4.1 Core Global Attributes (id, class, title, lang)

Attribute Purpose Values Uniqueness
id Unique identifier for element Alphanumeric, hyphen, underscore (no spaces) Must be unique per page
class CSS class name(s) for styling Space-separated list of class names Can be reused across elements
title Advisory information (tooltip) Any text (shows on hover) Can be duplicated
lang Language of element content BCP 47 language tag (e.g., en, es, fr) Inherits from parent if not set
dir Text directionality ltr, rtl, auto Inherits from parent
style Inline CSS styles CSS property declarations Avoid (use CSS instead)
tabindex Tab order for keyboard navigation Integer (-1, 0, positive) Controls focus order
accesskey Keyboard shortcut to activate element Single character Limited browser support
ID Attribute Rules:
  • Must be unique on the page
  • Start with letter (a-z, A-Z)
  • Can contain: letters, digits, hyphens, underscores, periods
  • Case-sensitive
  • Used for: CSS, JavaScript, URL fragments (#)
  • Avoid special characters and spaces

Tabindex Values

Value Behavior
-1 Focusable by script, not by tab
0 Natural tab order (recommended)
1+ Explicit order (avoid - breaks accessibility)

Example: Core global attributes usage

<!-- ID - unique identifier -->
<div id="main-content">...</div>
<a href="#main-content">Skip to content</a>

<!-- Class - multiple classes -->
<p class="intro highlight important">Important introduction</p>

<!-- Title - tooltip text -->
<abbr title="HyperText Markup Language">HTML</abbr>
<button title="Click to submit form">Submit</button>

<!-- Lang - language specification -->
<html lang="en">
<p lang="es">Hola, ¿cómo estás?</p>
<p lang="fr">Bonjour, comment allez-vous?</p>

<!-- Dir - text direction -->
<p dir="ltr">Left to right text (English)</p>
<p dir="rtl">نص من اليمين إلى اليسار (Arabic)</p>

<!-- Tabindex - keyboard navigation -->
<div tabindex="0">Keyboard focusable div</div>
<div tabindex="-1">Script-only focusable</div>

<!-- Accesskey - keyboard shortcut -->
<a href="/" accesskey="h">Home</a> <!-- Alt+H or similar -->
Warning: Avoid positive tabindex values (1, 2, 3...) as they disrupt natural tab order and harm accessibility. Use tabindex="0" for custom interactive elements.

4.2 Event Handler Attributes (onclick, onload, etc.)

Category Events Fires When
Mouse Events onclick, ondblclick, onmousedown, onmouseup, onmouseover, onmouseout, onmousemove User interacts with mouse/pointer
Keyboard Events onkeydown, onkeyup, onkeypress DEPRECATED User presses/releases keys
Form Events onsubmit, onchange, oninput, onfocus, onblur, onreset Form field interactions
Document Events onload, onunload, onbeforeunload, onresize, onscroll Document/window state changes
Drag Events ondrag, ondragstart, ondragend, ondragover, ondrop Drag and drop operations
Media Events onplay, onpause, onended, onvolumechange, ontimeupdate Media playback events
Clipboard Events oncopy, oncut, onpaste Clipboard operations

Common Mouse Events

Event Use Case
onclick Button clicks, interactive elements
ondblclick Double-click actions
onmouseover Hover effects, tooltips
onmouseout End hover effects
Best Practices:
  • Prefer addEventListener() in JavaScript
  • Inline event handlers discouraged
  • Violates separation of concerns
  • Harder to maintain and debug
  • Security risks (CSP violations)
  • Limited to single handler per event

Example: Inline event handlers (avoid in production)

<!-- Mouse events -->
<button onclick="alert('Clicked!')">Click Me</button>
<div onmouseover="this.style.background='yellow'">Hover me</div>

<!-- Keyboard events -->
<input type="text" onkeydown="console.log('Key pressed')">

<!-- Form events -->
<form onsubmit="return validateForm()">
  <input type="text" onchange="handleChange(this.value)">
  <input type="text" oninput="liveUpdate(this.value)">
</form>

<!-- Document events -->
<body onload="init()" onresize="handleResize()">

<!-- PREFERRED: Use addEventListener in JavaScript -->
<button id="myButton">Click Me</button>
<script>
  document.getElementById('myButton')
    .addEventListener('click', function() {
      alert('Clicked!');
    });
</script>
Warning: Inline event handlers are discouraged. Use addEventListener() in JavaScript files for better maintainability, multiple handlers, and Content Security Policy compliance.

4.3 Data Attributes (data-*) and Custom Properties

Feature Syntax Purpose Access Method
Data Attributes data-{name}="{value}" Store custom data on elements JavaScript: element.dataset.name
Naming Rules Lowercase, hyphen-separated Valid: data-user-id Converts to camelCase in dataset
Values String (any text) Store: IDs, config, state Always returns string
Data Attribute Rules:
  • Must start with data-
  • Name must be lowercase
  • Use hyphens for multi-word names
  • No uppercase letters after "data-"
  • Can store any string value
  • Accessible via CSS attribute selectors

JavaScript Access

HTML JavaScript
data-id dataset.id
data-user-name dataset.userName
data-index-value dataset.indexValue

Example: Data attributes usage

<!-- Storing IDs and metadata -->
<article data-post-id="12345" data-author="john" data-category="tech">
  <h2>Article Title</h2>
</article>

<!-- Configuration data -->
<button data-action="delete" data-confirm="true" data-target-id="item-123">
  Delete
</button>

<!-- State information -->
<div data-state="collapsed" data-animate="true">Collapsible content</div>

<!-- Complex data (JSON) -->
<div data-config='{"theme":"dark","size":"large"}'></div>

<!-- JavaScript access -->
<script>
  const article = document.querySelector('article');
  
  // Read data attributes
  console.log(article.dataset.postId);     // "12345"
  console.log(article.dataset.author);     // "john"
  console.log(article.dataset.category);   // "tech"
  
  // Set data attributes
  article.dataset.views = "1500";
  article.dataset.publishDate = "2025-12-21";
  
  // Delete data attribute
  delete article.dataset.category;
</script>

<!-- CSS with data attributes -->
<style>
  [data-state="collapsed"] { display: none; }
  [data-theme="dark"] { background: black; color: white; }
</style>
Note: Data attributes are visible in HTML source. Don't store sensitive information. Values are always strings; parse JSON/numbers in JavaScript if needed.

4.4 Content Editable and User Interaction

Attribute Values Purpose Browser Support
contenteditable true, false, inherit Makes element editable by user All modern browsers
spellcheck true, false Enable/disable spell checking All modern browsers
translate yes, no Control automatic translation Modern browsers
autocapitalize off, on, words, characters Auto-capitalization (mobile) Mobile browsers
inputmode text, numeric, tel, email, url Virtual keyboard type (mobile) Mobile browsers
enterkeyhint enter, done, go, next, search, send Enter key label (mobile) Mobile browsers
ContentEditable Use Cases:
  • Rich text editors (WYSIWYG)
  • Inline editing interfaces
  • Note-taking applications
  • Collaborative editing tools
  • Content management systems
  • Live comment editing

InputMode Values

Value Keyboard Type
text Standard keyboard
numeric Number pad
tel Telephone keypad
email Email keyboard (@)
url URL keyboard (.com)

Example: Content editable and user interaction

<!-- Basic contenteditable -->
<div contenteditable="true">
  This text can be edited by the user. Click to edit.
</div>

<!-- Rich text editor -->
<div contenteditable="true" spellcheck="true" class="editor">
  <h2>Editable Heading</h2>
  <p>Editable paragraph with spell checking enabled.</p>
</div>

<!-- Disable editing in child -->
<div contenteditable="true">
  Editable content
  <span contenteditable="false">But this part is not editable</span>
</div>

<!-- Spellcheck control -->
<textarea spellcheck="true"></textarea>
<textarea spellcheck="false"></textarea>

<!-- Translation control -->
<p translate="no">Product Name™</p>
<code translate="no">console.log()</code>

<!-- Mobile keyboard optimization -->
<input type="text" inputmode="numeric" enterkeyhint="done" 
       placeholder="Enter amount">
<input type="text" inputmode="email" enterkeyhint="next">
<input type="text" inputmode="tel" pattern="[0-9]*">

<!-- Auto-capitalization -->
<input type="text" autocapitalize="words"> <!-- Title Case -->
<input type="text" autocapitalize="characters"> <!-- ALL CAPS -->
Warning: ContentEditable creates security risks. Always sanitize user input before saving. Malicious users can paste HTML/scripts. Use libraries like DOMPurify for sanitization.

4.5 Draggable and Drop Zone Attributes

Attribute Values Purpose Use On
draggable true, false, auto Makes element draggable Any element
dropzone REMOVED copy, move, link Defines drop behavior (removed from spec) Drop targets

Drag Events (on dragged element)

Event Fires When
ondragstart Drag begins
ondrag Element is dragged
ondragend Drag operation ends

Drop Events (on drop target)

Event Fires When
ondragenter Dragged element enters
ondragover Dragged over target
ondragleave Dragged element leaves
ondrop Element is dropped

Example: Drag and drop implementation

<!-- Draggable elements -->
<div draggable="true" id="drag1" ondragstart="drag(event)">
  Drag me!
</div>

<!-- Drop zone -->
<div id="dropzone" ondrop="drop(event)" ondragover="allowDrop(event)">
  Drop here
</div>

<script>
  function drag(event) {
    // Store dragged element's ID
    event.dataTransfer.setData("text", event.target.id);
    event.dataTransfer.effectAllowed = "move";
  }
  
  function allowDrop(event) {
    // Must prevent default to allow drop
    event.preventDefault();
    event.dataTransfer.dropEffect = "move";
  }
  
  function drop(event) {
    event.preventDefault();
    const data = event.dataTransfer.getData("text");
    const draggedElement = document.getElementById(data);
    event.target.appendChild(draggedElement);
  }
</script>

<!-- List reordering example -->
<ul>
  <li draggable="true">Item 1</li>
  <li draggable="true">Item 2</li>
  <li draggable="true">Item 3</li>
</ul>

<!-- File upload drop zone -->
<div id="file-drop" ondrop="handleFiles(event)" ondragover="allowDrop(event)">
  Drop files here to upload
</div>
Note: Default draggable elements: <a>, <img>, selected text. For custom elements, set draggable="true". Must call preventDefault() on dragover to enable drop.

4.6 Hidden, Inert, and Visibility Controls

Attribute Values Effect Accessibility
hidden Boolean (no value needed) Removes from rendering (display: none) Hidden from screen readers
inert NEW Boolean Makes element and descendants inert (unfocusable, unclickable) Disabled for all interactions
aria-hidden true, false Visible but hidden from screen readers Hidden from assistive tech only

Visibility Comparison

Method Display Screen Reader Focusable
hidden ❌ Hidden ❌ Hidden ❌ No
inert ✅ Visible ❌ Hidden ❌ No
aria-hidden ✅ Visible ❌ Hidden ✅ Yes
visibility:hidden ❌ Hidden ❌ Hidden ❌ No
opacity:0 ❌ Hidden ✅ Visible ✅ Yes
Use Cases:
  • hidden: Conditional content, tabs, accordions
  • inert: Modal backgrounds, loading states
  • aria-hidden: Decorative icons, duplicates
  • Toggle hidden with JavaScript
  • Inert prevents focus traps
  • Combine for complex visibility needs

Example: Visibility control attributes

<!-- Hidden attribute -->
<div hidden>
  This content is completely hidden from view and screen readers.
</div>

<!-- Toggle hidden with JavaScript -->
<button onclick="toggle()">Toggle</button>
<div id="content" hidden>Hidden content</div>
<script>
  function toggle() {
    const el = document.getElementById('content');
    el.hidden = !el.hidden;
  }
</script>

<!-- Inert attribute (disable interactions) -->
<div inert>
  This content is visible but cannot be interacted with.
  <button>Can't click me</button>
  <a href="#">Can't follow this link</a>
</div>

<!-- Modal with inert background -->
<main inert>Background content (inert when modal open)</main>
<dialog open>
  <h2>Modal Dialog</h2>
  <button>Close</button>
</dialog>

<!-- aria-hidden (visible but hidden from screen readers) -->
<button>
  <span aria-hidden="true">🔍</span>
  Search
</button>

<!-- Decorative images -->
<img src="decoration.png" aria-hidden="true" alt="">

<!-- Tab panels -->
<div role="tabpanel" hidden id="tab1">Tab 1 content</div>
<div role="tabpanel" id="tab2">Tab 2 content (visible)</div>
Warning: Don't use aria-hidden="true" on focusable elements. This creates confusion where keyboard users can focus invisible content. Use hidden or inert instead.

Section 4 Key Takeaways

  • id must be unique per page; class can be reused
  • Use tabindex="0" for custom focusable elements; avoid positive values
  • Prefer addEventListener() over inline event handlers
  • Data attributes (data-*) store custom data; access via dataset
  • contenteditable="true" enables rich text editing; sanitize input
  • draggable="true" enables drag; must preventDefault on dragover
  • hidden removes from view; inert disables interactions
  • aria-hidden hides from screen readers only (keep visible)
Attribute Purpose Values Example
href Link destination URL URL, relative path, anchor (#id) href="page.html"
target Where to open linked document _self, _blank, _parent, _top target="_blank"
rel Relationship between documents Space-separated keywords rel="noopener noreferrer"
download Download file instead of navigate Optional filename download="file.pdf"
hreflang Language of linked document Language code (BCP 47) hreflang="es"
type MIME type of linked document MIME type type="application/pdf"
ping URLs to ping when link clicked Space-separated URLs ping="https://track.com"
referrerpolicy Referrer header policy Various policies referrerpolicy="no-referrer"
Type Example
Absolute URL https://example.com/page
Relative Path ../folder/page.html
Root Relative /about/contact.html
Fragment/Anchor #section-id
Same Page href="#"

Target Values

Value Behavior
_self Same frame/window (default)
_blank New window/tab
_parent Parent frameset
_top Full window body
framename Named iframe/frame
<!-- Basic links -->
<a href="https://example.com">External link</a>
<a href="/about">Root relative link</a>
<a href="../parent/page.html">Relative link</a>

<!-- Fragment/anchor link -->
<a href="#section-2">Jump to Section 2</a>
<h2 id="section-2">Section 2</h2>

<!-- New window with security -->
<a href="https://external.com" target="_blank" rel="noopener noreferrer">
  External site (new tab)
</a>

<!-- Download link -->
<a href="document.pdf" download="my-document.pdf">Download PDF</a>

<!-- With language and type -->
<a href="es/index.html" hreflang="es" type="text/html">Spanish version</a>

<!-- Link wrapping block elements (HTML5) -->
<a href="/article">
  <article>
    <h3>Article Title</h3>
    <p>Article preview text...</p>
  </article>
</a>
Warning: Always use rel="noopener noreferrer" with target="_blank" for security. Without it, the new page can access window.opener and potentially redirect your page.
Scheme Purpose Syntax Example
http/https Web pages https://domain.com/path href="https://example.com"
mailto Email links mailto:email@domain.com href="mailto:info@example.com"
tel Telephone numbers tel:+1234567890 href="tel:+1-555-123-4567"
sms SMS messages sms:+1234567890 href="sms:+1-555-123-4567"
file Local files file:///path/to/file href="file:///C:/docs/file.pdf"
ftp File transfer ftp://server.com/file href="ftp://ftp.example.com"
javascript Execute JavaScript javascript:code href="javascript:void(0)" AVOID
data Inline data data:mime;encoding,data href="data:text/plain;base64,..."
blob Binary data blob:origin/uuid Generated by JavaScript
Fragment Identifier (#):
  • Links to element with matching ID
  • Smooth scroll to section
  • Updates browser history
  • Can be used with any URL
  • href="#top" scrolls to top
  • href="#" stays on same page
Query Parameters (?):
  • ?param=value
  • ?param1=value1&param2=value2
  • Pass data via URL
  • Used for search, filters, tracking
  • Visible to users
  • URL encode special characters

Example: Different URL schemes

<!-- Web links -->
<a href="https://example.com">Secure website</a>
<a href="http://legacy.com">Non-secure (avoid)</a>

<!-- Email with subject and body -->
<a href="mailto:contact@example.com?subject=Hello&body=Message here">
  Email us
</a>

<!-- Phone number (mobile tap to call) -->
<a href="tel:+15551234567">Call: (555) 123-4567</a>

<!-- SMS (mobile tap to text) -->
<a href="sms:+15551234567?body=Hello">Send SMS</a>

<!-- FTP download -->
<a href="ftp://files.example.com/document.zip">Download via FTP</a>

<!-- Fragment identifiers -->
<a href="#footer">Jump to footer</a>
<a href="page.html#section3">Go to Section 3 of other page</a>

<!-- Query parameters -->
<a href="search.html?q=html&category=tutorials">Search Results</a>

<!-- Data URL (small images/files) -->
<a href="data:text/plain;charset=UTF-8,Hello%20World" download="hello.txt">
  Download Text
</a>

<!-- Avoid javascript: links -->
<!-- WRONG: <a href="javascript:alert('Hi')">Click</a> -->
<!-- RIGHT: <button onclick="alert('Hi')">Click</button> -->
Note: Avoid javascript: protocol in href. Use button elements with event handlers instead. It's better for accessibility, SEO, and security (CSP).
Rel Value Purpose Use On SEO/Security
noopener Prevents window.opener access <a> with target="_blank" Security
noreferrer No referrer header sent <a> external links Privacy
nofollow Don't follow link for SEO <a> untrusted content SEO
sponsored Sponsored/paid link <a> advertisements SEO
ugc User-generated content <a> comments, forum posts SEO
alternate Alternative version <link> in <head> RSS, translations
canonical Preferred URL for content <link> in <head> SEO
stylesheet CSS stylesheet <link> in <head> Required for CSS
icon Favicon <link> in <head> Site icon
preload High-priority resource <link> in <head> Performance
prefetch Low-priority future resource <link> in <head> Performance
dns-prefetch DNS lookup ahead of time <link> in <head> Performance
prev/next Pagination navigation <a> or <link> SEO pagination
Security/Privacy Rel Values:
Value Effect
noopener Blocks window.opener API
noreferrer Hides referrer header
nofollow Don't pass PageRank

Combine: rel="noopener noreferrer nofollow"

SEO Rel Values:
  • nofollow: Untrusted links, paid links
  • sponsored: Ads, sponsorships
  • ugc: User comments, forums
  • canonical: Prevent duplicate content
  • prev/next: Paginated series
  • alternate: Language versions, RSS

Example: Rel attribute usage

<!-- External link security (anchor tags) -->
<a href="https://external.com" target="_blank" rel="noopener noreferrer">
  External Link
</a>

<!-- SEO rel attributes -->
<a href="https://ad-site.com" rel="sponsored nofollow">Advertisement</a>
<a href="/user-comment-link" rel="ugc">User Link</a>

<!-- In head: stylesheets and resources -->
<head>
  <link rel="stylesheet" href="styles.css">
  <link rel="icon" href="favicon.ico">
  <link rel="canonical" href="https://example.com/page">
  
  <!-- Alternative versions -->
  <link rel="alternate" hreflang="es" href="https://example.com/es/page">
  <link rel="alternate" type="application/rss+xml" href="feed.xml">
  
  <!-- Performance hints -->
  <link rel="preload" href="font.woff2" as="font" crossorigin>
  <link rel="prefetch" href="next-page.html">
  <link rel="dns-prefetch" href="https://cdn.example.com">
  
  <!-- Pagination -->
  <link rel="prev" href="page-1.html">
  <link rel="next" href="page-3.html">
</head>

<!-- Multiple rel values -->
<a href="https://spam-site.com" rel="nofollow noopener noreferrer external">
  Untrusted External Link
</a>
Feature Syntax Behavior Browser Support
download <a download> Download with original filename All modern browsers
download="name" <a download="file.ext"> Download with custom filename All modern browsers
Same-origin only N/A Download only works for same domain Security restriction
Data URLs data:mime;base64,... Download generated/inline data All browsers
Blob URLs URL.createObjectURL(blob) Download JavaScript-generated files All modern browsers

Common File Types

Extension MIME Type
.pdf application/pdf
.zip application/zip
.jpg image/jpeg
.txt text/plain
.csv text/csv
.json application/json
Download Best Practices:
  • Use meaningful filenames
  • Include file extension
  • Set appropriate MIME type
  • Show file size in link text
  • Indicate file type to users
  • Cross-origin downloads may fail
<!-- Simple download with original filename -->
<a href="document.pdf" download>Download PDF</a>

<!-- Custom filename -->
<a href="report-2025-q4.pdf" download="Q4-Report.pdf">
  Download Q4 Report (2.3 MB)
</a>

<!-- Multiple file types -->
<a href="data.csv" download="export.csv">Download CSV</a>
<a href="archive.zip" download>Download ZIP (15 MB)</a>
<a href="image.jpg" download="photo.jpg">Download Image</a>

<!-- Data URL download (generated content) -->
<a href="data:text/plain;charset=UTF-8,Hello%20World" 
   download="hello.txt">
  Download Generated Text
</a>

<!-- JavaScript-generated download -->
<button onclick="downloadFile()">Generate and Download</button>
<script>
  function downloadFile() {
    const content = "Generated file content";
    const blob = new Blob([content], {type: 'text/plain'});
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = 'generated-file.txt';
    a.click();
    URL.revokeObjectURL(url);
  }
</script>

<!-- CSV download example -->
<a href="data:text/csv;charset=utf-8,Name,Age%0AJohn,30%0AJane,25" 
   download="data.csv">
  Download CSV Data
</a>
Note: Download attribute only works for same-origin URLs. Cross-origin downloads require server CORS headers. Data URLs and Blob URLs work universally.
Type Syntax Parameters Example
Email (mailto) mailto:email@domain.com subject, body, cc, bcc mailto:info@example.com
Phone (tel) tel:+countrycode-number None tel:+1-555-123-4567
SMS (sms) sms:+number body (message text) sms:+15551234567
FaceTime (facetime) facetime:email/number None facetime://user@example.com
Mailto Parameters:
Parameter Purpose
subject Email subject line
body Email message body
cc Carbon copy recipient(s)
bcc Blind carbon copy

Separate with &, URL encode spaces and special chars

Phone Number Format:
  • Include country code: +1
  • Remove spaces in href: +15551234567
  • Display with formatting: (555) 123-4567
  • Mobile: tap to call
  • Desktop: opens default phone app
  • Skype, WhatsApp support
<!-- Simple email link -->
<a href="mailto:contact@example.com">Email Us</a>

<!-- Email with subject -->
<a href="mailto:support@example.com?subject=Support Request">
  Contact Support
</a>

<!-- Email with subject and body -->
<a href="mailto:info@example.com?subject=Inquiry&body=Hello, I would like to know...">
  Send Inquiry
</a>

<!-- Email with CC and BCC -->
<a href="mailto:primary@example.com?cc=secondary@example.com&bcc=hidden@example.com">
  Email with CC/BCC
</a>

<!-- Multiple recipients -->
<a href="mailto:first@example.com,second@example.com?subject=Team Meeting">
  Email Team
</a>

<!-- Phone links -->
<a href="tel:+15551234567">Call: (555) 123-4567</a>
<a href="tel:+442071234567">UK: +44 20 7123 4567</a>

<!-- SMS link -->
<a href="sms:+15551234567">Send SMS</a>
<a href="sms:+15551234567?body=Hello, I'm interested in...">
  SMS with preset message
</a>

<!-- FaceTime (Apple devices) -->
<a href="facetime://user@example.com">FaceTime Call</a>

<!-- WhatsApp (requires specific format) -->
<a href="https://wa.me/15551234567?text=Hello">WhatsApp Chat</a>
Warning: Email addresses in mailto links are visible to spam bots. Consider obfuscation, CAPTCHA contact forms, or JavaScript-based email revelation for public sites.

5.6 Navigation Patterns and Breadcrumbs

Pattern Purpose HTML Structure ARIA/Microdata
Breadcrumbs Show page hierarchy path <nav><ol><li><a> aria-label="Breadcrumb"
Skip Links Jump to main content (a11y) <a href="#main"> First in <body>
Pagination Navigate multi-page content <nav><ul><li><a> aria-label="Pagination"
Main Navigation Primary site menu <nav><ul><li><a> aria-label="Main"
Tabs Switch between views role="tablist" ARIA tab pattern
Breadcrumb Best Practices:
  • Use <nav> with aria-label
  • Ordered list <ol> shows hierarchy
  • Separate with › or / (CSS or text)
  • Current page can be <span> or disabled link
  • Add schema.org markup for SEO
  • Mobile: show only last 2-3 levels
Pagination Patterns:
  • Previous/Next links
  • Numbered page links
  • Ellipsis (...) for skipped pages
  • Highlight current page
  • First/Last links optional
  • Use rel="prev/next"

Example: Navigation patterns

<!-- Skip to main content link (accessibility) -->
<a href="#main-content" class="skip-link">Skip to main content</a>

<!-- Breadcrumb navigation -->
<nav aria-label="Breadcrumb">
  <ol itemscope itemtype="https://schema.org/BreadcrumbList">
    <li itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
      <a itemprop="item" href="/">
        <span itemprop="name">Home</span>
      </a>
      <meta itemprop="position" content="1" />
    </li>
    <li itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
      <a itemprop="item" href="/products">
        <span itemprop="name">Products</span>
      </a>
      <meta itemprop="position" content="2" />
    </li>
    <li itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
      <span itemprop="name">Current Product</span>
      <meta itemprop="position" content="3" />
    </li>
  </ol>
</nav>

<!-- Simple breadcrumb -->
<nav aria-label="Breadcrumb">
  <ol>
    <li><a href="/">Home</a></li>
    <li><a href="/category">Category</a></li>
    <li><a href="/subcategory">Subcategory</a></li>
    <li aria-current="page">Current Page</li>
  </ol>
</nav>

<!-- Pagination navigation -->
<nav aria-label="Pagination">
  <ul>
    <li><a href="?page=1" rel="prev">« Previous</a></li>
    <li><a href="?page=1">1</a></li>
    <li><a href="?page=2" aria-current="page">2</a></li>
    <li><a href="?page=3">3</a></li>
    <li><span>...</span></li>
    <li><a href="?page=10">10</a></li>
    <li><a href="?page=3" rel="next">Next »</a></li>
  </ul>
</nav>

<!-- Main navigation -->
<nav aria-label="Main navigation">
  <ul>
    <li><a href="/" aria-current="page">Home</a></li>
    <li><a href="/about">About</a></li>
    <li><a href="/services">Services</a></li>
    <li><a href="/contact">Contact</a></li>
  </ul>
</nav>
Note: Use aria-current="page" to indicate current location in navigation. For breadcrumbs, add schema.org structured data to improve SEO and rich snippets.

Section 5 Key Takeaways

  • Always use rel="noopener noreferrer" with target="_blank" for security
  • Avoid javascript: protocol; use buttons with event handlers instead
  • Use rel="nofollow" for untrusted links, rel="sponsored" for ads
  • Download attribute works for same-origin URLs only; use data/blob URLs for generated files
  • Mailto links: URL encode parameters, separate with &
  • Phone links: include country code, format as tel:+15551234567
  • Breadcrumbs: use <nav> with <ol> and schema.org markup
  • Skip links improve accessibility by allowing keyboard users to jump to main content

6. Images, Media, and Embedded Content

6.1 Image Elements and Responsive Images

Attribute Purpose Values/Syntax Required
src Image source URL URL or path to image file Yes
alt Alternative text for accessibility Descriptive text (empty for decorative) Yes
width Image width in pixels Number (without unit) Recommended
height Image height in pixels Number (without unit) Recommended
srcset Responsive image sources url 1x, url 2x or url 400w Optional
sizes Image display sizes (with srcset) Media conditions + size With srcset (w)
loading Lazy loading behavior lazy, eager Optional
decoding Image decode timing async, sync, auto Optional
crossorigin CORS settings anonymous, use-credentials For cross-origin images
ismap Server-side image map Boolean Rare use
usemap Client-side image map #mapname With <map>

Srcset Syntax

Type Syntax Use Case
Pixel Density image-2x.jpg 2x Retina displays
Width Descriptor image-800.jpg 800w Responsive layouts

Image Formats

Format Use Case
WebP Modern, best compression
AVIF Next-gen, even better
JPEG Photos, universal support
PNG Transparency, logos
SVG Scalable graphics, icons

Example: Responsive images with srcset and sizes

<!-- Basic image with alt text -->
<img src="photo.jpg" alt="Description of photo" width="800" height="600">

<!-- Responsive with pixel density (Retina) -->
<img src="image.jpg" 
     srcset="image.jpg 1x, image-2x.jpg 2x, image-3x.jpg 3x"
     alt="Responsive image">

<!-- Responsive with width descriptors -->
<img src="photo-800.jpg"
     srcset="photo-400.jpg 400w,
             photo-800.jpg 800w,
             photo-1200.jpg 1200w,
             photo-1600.jpg 1600w"
     sizes="(max-width: 600px) 100vw,
            (max-width: 1200px) 50vw,
            800px"
     alt="Adaptive image">

<!-- Lazy loading -->
<img src="image.jpg" alt="Lazy loaded" loading="lazy">

<!-- Async decoding for performance -->
<img src="large.jpg" alt="Large image" decoding="async">

<!-- Decorative image (empty alt) -->
<img src="decoration.png" alt="" role="presentation">

<!-- Image map -->
<img src="map.jpg" alt="Interactive map" usemap="#regions">
<map name="regions">
  <area shape="rect" coords="0,0,100,100" href="region1.html" alt="Region 1">
  <area shape="circle" coords="200,200,50" href="region2.html" alt="Region 2">
</map>
Note: Always specify width and height to prevent layout shifts (CLS). Use loading="lazy" for below-fold images. Alt text is required for accessibility.

6.2 Picture Element and Source Sets

Element Purpose Attributes Use Case
<picture> Container for multiple sources None Art direction, format selection
<source> Define image source options srcset, media, type, sizes Conditional image loading
<img> Fallback image (required) src, alt Default/fallback display

Source Attributes

Attribute Purpose
srcset Image URL(s) with descriptors
media Media query condition
type MIME type (format detection)
sizes Display size hints
width/height Intrinsic dimensions
Use Cases:
  • Art Direction: Different crops for mobile/desktop
  • Format Selection: WebP/AVIF with JPEG fallback
  • Resolution Switching: Different sizes per viewport
  • Dark mode variations
  • Print vs screen images
  • Locale-specific images

Example: Picture element for art direction and formats

<!-- Art direction: different images for different viewports -->
<picture>
  <source media="(min-width: 1200px)" srcset="hero-wide.jpg">
  <source media="(min-width: 768px)" srcset="hero-medium.jpg">
  <img src="hero-mobile.jpg" alt="Hero image">
</picture>

<!-- Format selection: modern formats with fallbacks -->
<picture>
  <source srcset="image.avif" type="image/avif">
  <source srcset="image.webp" type="image/webp">
  <img src="image.jpg" alt="Optimized image">
</picture>

<!-- Combined: format + resolution -->
<picture>
  <source srcset="photo-800.webp 800w, photo-1200.webp 1200w" 
          type="image/webp">
  <source srcset="photo-800.jpg 800w, photo-1200.jpg 1200w" 
          type="image/jpeg">
  <img src="photo-800.jpg" alt="Photo" loading="lazy">
</picture>

<!-- Dark mode variation -->
<picture>
  <source srcset="logo-dark.png" media="(prefers-color-scheme: dark)">
  <img src="logo-light.png" alt="Company logo">
</picture>

<!-- Print-specific image -->
<picture>
  <source srcset="chart-print.png" media="print">
  <img src="chart-screen.png" alt="Sales chart">
</picture>
Warning: Browser selects first matching source. Order matters! Put most specific media queries first. Always include <img> as fallback.

6.3 SVG Integration and Inline Graphics

Method Syntax Pros Cons
Inline SVG <svg>...</svg> CSS/JS control, no HTTP request Increases HTML size, not cached
IMG tag <img src="image.svg"> Simple, cached, responsive No CSS/JS manipulation
Object <object data="image.svg"> Interactive, scriptable Complex, slower
CSS Background background: url(image.svg) Decorative, cached Not in HTML, no alt text
Data URL <img src="data:image/svg+xml,..."> No HTTP request Not cached, size limit

SVG Viewport Attributes

Attribute Purpose
width SVG width (with unit or viewBox)
height SVG height (with unit or viewBox)
viewBox Coordinate system: min-x min-y width height
preserveAspectRatio Scaling behavior
Common SVG Elements:
  • <circle> - Circle shapes
  • <rect> - Rectangles
  • <path> - Complex paths
  • <line>, <polyline> - Lines
  • <polygon> - Closed shapes
  • <text> - Text content
  • <g> - Group elements

Example: SVG integration methods

<!-- 1. Inline SVG (full control) -->
<svg width="100" height="100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
  <circle cx="50" cy="50" r="40" fill="#007acc" />
  <text x="50" y="55" text-anchor="middle" fill="white" font-size="20">SVG</text>
</svg>

<!-- 2. IMG tag (simple, cached) -->
<img src="icon.svg" alt="Icon" width="32" height="32">

<!-- 3. Object element (interactive) -->
<object data="diagram.svg" type="image/svg+xml" width="400" height="300">
  <img src="diagram-fallback.png" alt="Diagram">
</object>

<!-- 4. Responsive inline SVG -->
<svg viewBox="0 0 200 100" preserveAspectRatio="xMidYMid meet">
  <rect x="10" y="10" width="180" height="80" fill="none" stroke="#333" stroke-width="2"/>
  <circle cx="100" cy="50" r="30" fill="#ff6b6b"/>
</svg>

<!-- 5. SVG with ARIA -->
<svg role="img" aria-labelledby="icon-title">
  <title id="icon-title">Settings Icon</title>
  <path d="M10 10 H 90 V 90 H 10 Z" fill="#666"/>
</svg>

<!-- 6. SVG sprite (reusable icons) -->
<svg style="display:none">
  <symbol id="icon-home" viewBox="0 0 24 24">
    <path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>
  </symbol>
</svg>
<svg width="24" height="24">
  <use href="#icon-home"/>
</svg>
Note: Use inline SVG for icons you want to style with CSS. Use <img> for simple, static SVGs. Add role="img" and <title> for accessibility.

6.4 Audio Elements and Media Controls

Attribute Purpose Values Default
src Audio file URL URL to audio file None
controls Show playback controls Boolean false
autoplay Auto-start playback Boolean false (blocked by browsers)
loop Repeat playback Boolean false
muted Start muted Boolean false
preload Preload strategy none, metadata, auto auto
crossorigin CORS mode anonymous, use-credentials None

Audio Formats

Format MIME Type Support
MP3 audio/mpeg Universal
WAV audio/wav Universal
OGG audio/ogg Most browsers
AAC audio/aac Most browsers
FLAC audio/flac Modern browsers

Preload Values

Value Behavior
none Don't preload anything
metadata Preload duration, dimensions
auto Browser decides (usually preloads)

Example: Audio elements with multiple sources

<!-- Simple audio with controls -->
<audio src="audio.mp3" controls>
  Your browser does not support the audio element.
</audio>

<!-- Multiple formats for compatibility -->
<audio controls>
  <source src="audio.mp3" type="audio/mpeg">
  <source src="audio.ogg" type="audio/ogg">
  <source src="audio.wav" type="audio/wav">
  <p>Your browser doesn't support HTML5 audio. 
     <a href="audio.mp3">Download the audio</a>.</p>
</audio>

<!-- Looping background music (muted by default) -->
<audio loop muted autoplay>
  <source src="background.mp3" type="audio/mpeg">
</audio>

<!-- Preload metadata only -->
<audio controls preload="metadata">
  <source src="podcast.mp3" type="audio/mpeg">
</audio>

<!-- With custom controls via JavaScript -->
<audio id="myAudio" src="audio.mp3"></audio>
<button onclick="document.getElementById('myAudio').play()">Play</button>
<button onclick="document.getElementById('myAudio').pause()">Pause</button>
<button onclick="document.getElementById('myAudio').volume += 0.1">Volume Up</button>
Warning: Autoplay is blocked by most browsers unless video is muted or user has interacted with the page. Use autoplay muted together if autoplay is essential.

6.5 Video Elements and Streaming Support

Attribute Purpose Values Notes
src Video file URL URL to video file Use <source> for multiple formats
poster Thumbnail before playback Image URL Shows until play clicked
controls Show playback controls Boolean Recommended for usability
width/height Video dimensions Pixels (number only) Maintains aspect ratio
autoplay Auto-start playback Boolean Requires muted in most browsers
loop Repeat playback Boolean Useful for background videos
muted Start muted Boolean Required for autoplay
preload Preload strategy none, metadata, auto Affects bandwidth usage
playsinline Inline playback (mobile) Boolean iOS: prevents fullscreen
disablepictureinpicture Disable PiP mode Boolean Optional

Video Formats

Format MIME Type Codec
MP4 video/mp4 H.264, H.265
WebM video/webm VP8, VP9, AV1
OGG video/ogg Theora

Track Element (Subtitles)

Attribute Values
kind subtitles, captions, descriptions, chapters
src WebVTT file URL
srclang Language code (en, es, etc.)
label User-visible label
default Boolean (default track)

Example: Video elements with advanced features

<!-- Basic video with controls -->
<video src="video.mp4" controls width="640" height="360">
  Your browser doesn't support HTML5 video.
</video>

<!-- Multiple formats + poster -->
<video controls width="800" height="450" poster="thumbnail.jpg">
  <source src="video.mp4" type="video/mp4">
  <source src="video.webm" type="video/webm">
  <p>Your browser doesn't support HTML5 video. 
     <a href="video.mp4">Download the video</a>.</p>
</video>

<!-- With subtitles/captions -->
<video controls>
  <source src="movie.mp4" type="video/mp4">
  <track kind="subtitles" src="subs-en.vtt" srclang="en" label="English" default>
  <track kind="subtitles" src="subs-es.vtt" srclang="es" label="Español">
  <track kind="captions" src="captions-en.vtt" srclang="en" label="English Captions">
</video>

<!-- Autoplay background video (muted) -->
<video autoplay muted loop playsinline>
  <source src="background.mp4" type="video/mp4">
  <source src="background.webm" type="video/webm">
</video>

<!-- Responsive video (CSS controlled) -->
<video controls style="max-width: 100%; height: auto;">
  <source src="responsive.mp4" type="video/mp4">
</video>

<!-- Preload metadata only -->
<video controls preload="metadata" poster="poster.jpg">
  <source src="large-video.mp4" type="video/mp4">
</video>
Note: For autoplay to work, video must be muted. Use playsinline on iOS to prevent fullscreen. Always provide poster image for better UX.

6.6 Canvas and Graphics API Integration

Attribute Purpose Values Required
width Canvas width in pixels Number (default: 300) Recommended
height Canvas height in pixels Number (default: 150) Recommended

Canvas Context Types

Context Purpose
2d 2D drawing (shapes, text, images)
webgl 3D graphics (OpenGL ES)
webgl2 Enhanced 3D (OpenGL ES 3.0)
bitmaprenderer ImageBitmap rendering
Common 2D Methods:
  • fillRect() - Draw filled rectangle
  • strokeRect() - Draw rectangle outline
  • fillText() - Draw filled text
  • drawImage() - Draw image
  • beginPath() - Start path
  • arc() - Draw circle/arc
  • lineTo() - Draw line

Example: Canvas element with 2D drawing

<!-- Basic canvas element -->
<canvas id="myCanvas" width="400" height="300">
  Your browser doesn't support canvas.
</canvas>

<script>
  const canvas = document.getElementById('myCanvas');
  const ctx = canvas.getContext('2d');
  
  // Draw rectangle
  ctx.fillStyle = '#007acc';
  ctx.fillRect(50, 50, 100, 80);
  
  // Draw circle
  ctx.beginPath();
  ctx.arc(250, 90, 40, 0, 2 * Math.PI);
  ctx.fillStyle = '#ff6b6b';
  ctx.fill();
  
  // Draw text
  ctx.font = '20px Arial';
  ctx.fillStyle = '#333';
  ctx.fillText('Canvas Drawing', 100, 200);
  
  // Draw line
  ctx.beginPath();
  ctx.moveTo(50, 250);
  ctx.lineTo(350, 250);
  ctx.strokeStyle = '#000';
  ctx.lineWidth = 3;
  ctx.stroke();
</script>

<!-- Responsive canvas -->
<canvas id="responsive" style="width: 100%; height: auto;"></canvas>
<script>
  const canvas = document.getElementById('responsive');
  // Set actual size in pixels
  canvas.width = canvas.offsetWidth;
  canvas.height = canvas.offsetHeight;
</script>

<!-- Export canvas as image -->
<canvas id="exportable" width="200" height="200"></canvas>
<button onclick="exportCanvas()">Download as PNG</button>
<script>
  function exportCanvas() {
    const canvas = document.getElementById('exportable');
    const dataURL = canvas.toDataURL('image/png');
    const link = document.createElement('a');
    link.download = 'canvas-image.png';
    link.href = dataURL;
    link.click();
  }
</script>
Warning: Set canvas dimensions via width/height attributes, not CSS. CSS scales the canvas bitmap, causing blur. Canvas content is not accessible to screen readers.

6.7 Object and Embed Elements

Element Purpose Use Cases Support
<object> Embed external resources PDFs, SVGs, Flash (legacy), plugins Universal
<embed> Embed plugins/external content PDFs, multimedia (legacy) Universal
<iframe> Embed another HTML page External sites, widgets, ads Universal

Object Attributes

Attribute Purpose
data Resource URL
type MIME type
width/height Dimensions
name Element name

Iframe Attributes

Attribute Purpose
src Page URL
sandbox Security restrictions
loading lazy, eager
allow Feature policy

Example: Object, embed, and iframe usage

<!-- Object: PDF embed -->
<object data="document.pdf" type="application/pdf" width="100%" height="600">
  <p>PDF cannot be displayed. <a href="document.pdf">Download PDF</a></p>
</object>

<!-- Object: SVG with fallback -->
<object data="diagram.svg" type="image/svg+xml" width="400" height="300">
  <img src="diagram-fallback.png" alt="Diagram">
</object>

<!-- Embed: PDF (legacy) -->
<embed src="document.pdf" type="application/pdf" width="100%" height="600">

<!-- Iframe: External website -->
<iframe src="https://example.com" width="800" height="600" 
        title="External content">
</iframe>

<!-- Iframe: YouTube embed -->
<iframe width="560" height="315" 
        src="https://www.youtube.com/embed/VIDEO_ID" 
        title="YouTube video player"
        frameborder="0" 
        allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" 
        allowfullscreen>
</iframe>

<!-- Iframe: Sandboxed for security -->
<iframe src="untrusted.html" 
        sandbox="allow-scripts allow-same-origin"
        width="600" height="400">
</iframe>

<!-- Iframe: Lazy loading -->
<iframe src="content.html" loading="lazy" width="100%" height="400">
</iframe>

<!-- Responsive iframe (16:9 aspect ratio) -->
<div style="position: relative; padding-bottom: 56.25%; height: 0;">
  <iframe src="video.html" 
          style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"
          frameborder="0">
  </iframe>
</div>
Warning: Iframes can pose security risks. Use sandbox attribute to restrict capabilities. For untrusted content, use sandbox="" (maximum restrictions) and selectively enable features.

Section 6 Key Takeaways

  • Always include alt text for images; specify width and height to prevent CLS
  • Use srcset and sizes for responsive images based on viewport/density
  • <picture> for art direction (different crops) and format selection (WebP/AVIF fallback)
  • Inline SVG for CSS/JS control; <img> for simple static SVGs
  • Autoplay requires muted attribute; most browsers block unmuted autoplay
  • Use <track> for video subtitles/captions; WebVTT format
  • Canvas dimensions set via attributes, not CSS; content not accessible
  • Use iframe sandbox for security; loading="lazy" for performance

7. HTML Forms and Input Controls

7.1 Form Element and Form Attributes

Attribute Purpose Values Default
action URL to submit form data URL or relative path Current page
method HTTP method for submission GET, POST, DIALOG GET
enctype Form data encoding type application/x-www-form-urlencoded, multipart/form-data, text/plain application/x-www-form-urlencoded
target Where to display response _self, _blank, _parent, _top _self
autocomplete Enable autofill on, off on
novalidate Disable HTML5 validation Boolean false
name Form identifier String None
accept-charset Character encodings Space-separated list (e.g., UTF-8) Page encoding
rel Link relationship noopener, noreferrer None

Method Comparison

Method Use Case Visible in URL
GET Search forms, filters, idempotent actions Yes
POST Create/update data, file uploads, sensitive data No
DIALOG Close dialog without submission N/A

Enctype Values

Type Use Case
application/x-www-form-urlencoded Default, standard forms
multipart/form-data File uploads
text/plain Debugging (not recommended)

Example: Form element with various attributes

<!-- Basic form -->
<form action="/submit" method="POST">
  <!-- Form fields here -->
  <button type="submit">Submit</button>
</form>

<!-- File upload form -->
<form action="/upload" method="POST" enctype="multipart/form-data">
  <input type="file" name="document">
  <button type="submit">Upload</button>
</form>

<!-- Search form (GET) -->
<form action="/search" method="GET">
  <input type="search" name="q" placeholder="Search...">
  <button type="submit">Search</button>
</form>

<!-- Form with autocomplete disabled -->
<form action="/payment" method="POST" autocomplete="off">
  <input type="text" name="card-number">
  <button type="submit">Pay</button>
</form>

<!-- Form without HTML5 validation -->
<form action="/custom" method="POST" novalidate>
  <input type="email" name="email">
  <button type="submit">Submit</button>
</form>

<!-- Form opening in new tab -->
<form action="/external" method="POST" target="_blank" rel="noopener">
  <button type="submit">Open in New Tab</button>
</form>
Note: Use POST for forms that modify data. Use enctype="multipart/form-data" for file uploads. Add rel="noopener" when using target="_blank" for security.

7.2 Input Types and HTML5 Form Controls

Input Type Purpose Mobile Keyboard Validation
text Single-line text Standard Length, pattern
email Email address @ key visible Email format
tel Telephone number Numeric keypad Pattern only
url Web address .com, / keys URL format
number Numeric input Numeric + - keys Min, max, step
range Slider control N/A Min, max, step
date Date picker Date picker Min, max
time Time picker Time picker Min, max, step
datetime-local Date + time picker Date/time picker Min, max
month Month + year Month picker Min, max
week Week number Week picker Min, max
color Color picker Color palette Hex format
search Search field Search button Length, pattern
password Password (hidden) Standard Length, pattern
file File upload File browser Accept types
checkbox Boolean toggle N/A Required
radio Single choice from group N/A Required
submit Submit button N/A N/A
reset Reset form values N/A N/A
button Generic button N/A N/A
hidden Hidden field N/A None
image Image submit button N/A N/A

Example: Common input types

<!-- Text inputs -->
<input type="text" name="username" placeholder="Username">
<input type="email" name="email" placeholder="email@example.com">
<input type="tel" name="phone" placeholder="(555) 123-4567">
<input type="url" name="website" placeholder="https://example.com">
<input type="password" name="password" placeholder="Password">
<input type="search" name="query" placeholder="Search...">

<!-- Number and range -->
<input type="number" name="age" min="0" max="120" step="1">
<input type="range" name="volume" min="0" max="100" value="50">

<!-- Date and time -->
<input type="date" name="birthday" min="1900-01-01" max="2024-12-31">
<input type="time" name="appointment" step="900"> <!-- 15 min steps -->
<input type="datetime-local" name="meeting">
<input type="month" name="start-month">
<input type="week" name="week-number">

<!-- Color picker -->
<input type="color" name="theme-color" value="#007acc">

<!-- File upload -->
<input type="file" name="document" accept=".pdf,.doc,.docx">
<input type="file" name="photos" accept="image/*" multiple>

<!-- Checkboxes -->
<input type="checkbox" name="subscribe" id="subscribe" checked>
<label for="subscribe">Subscribe to newsletter</label>

<!-- Radio buttons -->
<input type="radio" name="plan" id="free" value="free" checked>
<label for="free">Free</label>
<input type="radio" name="plan" id="pro" value="pro">
<label for="pro">Pro</label>

<!-- Hidden field -->
<input type="hidden" name="csrf_token" value="abc123">

<!-- Buttons -->
<input type="submit" value="Submit Form">
<input type="reset" value="Reset">
<input type="button" value="Click Me" onclick="alert('Clicked')">
Warning: Never use type="reset" unless absolutely necessary - users often click it by mistake. Use type="tel" instead of type="number" for phone numbers to avoid spinner controls.

7.3 Select, Option, and Datalist Elements

Element Purpose Key Attributes Use Case
<select> Dropdown menu name, multiple, size, required, disabled Single/multiple choice from list
<option> Dropdown item value, selected, disabled, label Individual choice option
<optgroup> Group options label, disabled Organize related options
<datalist> Autocomplete suggestions id (referenced by input list) Type-ahead suggestions

Select Attributes

Attribute Purpose
name Form field identifier
multiple Allow multiple selections
size Visible options (default: 1)
required Must select an option
disabled Disable entire select
autocomplete Browser autofill behavior

Option Attributes

Attribute Purpose
value Submitted value
selected Pre-selected option
disabled Non-selectable option
label Alternative display text

Example: Select, option, optgroup, and datalist

<!-- Basic select dropdown -->
<select name="country">
  <option value="">Select a country</option>
  <option value="us">United States</option>
  <option value="uk">United Kingdom</option>
  <option value="ca">Canada</option>
</select>

<!-- With optgroup -->
<select name="car">
  <optgroup label="European Cars">
    <option value="bmw">BMW</option>
    <option value="mercedes">Mercedes</option>
  </optgroup>
  <optgroup label="American Cars">
    <option value="ford">Ford</option>
    <option value="tesla">Tesla</option>
  </optgroup>
</select>

<!-- Multiple selection -->
<select name="skills" multiple size="5">
  <option value="html">HTML</option>
  <option value="css">CSS</option>
  <option value="js" selected>JavaScript</option>
  <option value="react">React</option>
  <option value="node">Node.js</option>
</select>

<!-- With disabled options -->
<select name="plan">
  <option value="free">Free</option>
  <option value="pro" selected>Pro</option>
  <option value="enterprise" disabled>Enterprise (Contact Sales)</option>
</select>

<!-- Datalist (autocomplete suggestions) -->
<input type="text" name="browser" list="browsers" placeholder="Choose browser">
<datalist id="browsers">
  <option value="Chrome">
  <option value="Firefox">
  <option value="Safari">
  <option value="Edge">
  <option value="Opera">
</datalist>

<!-- Datalist with descriptions -->
<input type="text" name="city" list="cities">
<datalist id="cities">
  <option value="New York">NY, USA</option>
  <option value="London">UK</option>
  <option value="Tokyo">Japan</option>
</datalist>
Note: Always include an empty <option> with placeholder text for required selects. Use <optgroup> to organize long lists. Datalist provides suggestions but allows custom input.

7.4 Textarea and Multi-line Input

Attribute Purpose Values Default
rows Visible text lines Number (e.g., 4) 2
cols Visible character width Number (e.g., 50) 20
maxlength Maximum character count Number Unlimited
minlength Minimum character count Number 0
placeholder Placeholder text String None
readonly Read-only (not editable) Boolean false
disabled Disabled (not submitted) Boolean false
required Must be filled Boolean false
wrap Text wrapping behavior soft, hard soft
autocomplete Autofill behavior on, off on
spellcheck Spell checking true, false true

Wrap Values

Value Behavior
soft Text wraps visually, no line breaks in submitted data
hard Text wraps and includes line breaks in submission
Sizing Best Practices:
  • Use CSS width and height instead of cols/rows for responsive design
  • Set resize: vertical to allow vertical resizing only
  • Set resize: none to disable resizing
  • Default allows both horizontal and vertical resizing

Example: Textarea configurations

<!-- Basic textarea -->
<textarea name="comments" rows="4" cols="50" placeholder="Enter your comments..."></textarea>

<!-- With character limit -->
<textarea name="bio" rows="3" maxlength="200" placeholder="Bio (max 200 characters)"></textarea>

<!-- Required with minimum length -->
<textarea name="description" rows="5" required minlength="20" 
          placeholder="Provide a detailed description (min 20 characters)"></textarea>

<!-- Read-only textarea -->
<textarea name="terms" rows="10" readonly>
Terms and Conditions...
User must read but cannot edit.
</textarea>

<!-- Disabled textarea -->
<textarea name="disabled-field" rows="3" disabled>
This field is disabled and won't be submitted.
</textarea>

<!-- Hard wrap (preserves line breaks) -->
<textarea name="message" rows="5" wrap="hard" cols="40"></textarea>

<!-- CSS-styled textarea -->
<textarea name="notes" style="width: 100%; max-width: 600px; resize: vertical;"
          rows="6" placeholder="Notes..."></textarea>

<!-- Disable spell check -->
<textarea name="code" rows="10" spellcheck="false" 
          placeholder="Paste code here..."></textarea>

<!-- With character counter (requires JavaScript) -->
<textarea name="tweet" id="tweet" rows="3" maxlength="280" 
          oninput="updateCounter()"></textarea>
<p><span id="counter">0</span>/280 characters</p>
<script>
  function updateCounter() {
    const textarea = document.getElementById('tweet');
    const counter = document.getElementById('counter');
    counter.textContent = textarea.value.length;
  }
</script>
Note: Unlike <input>, textarea content goes between opening and closing tags, not in a value attribute. Use CSS for responsive sizing instead of rows/cols.

7.5 Fieldset, Legend, and Form Grouping

Element Purpose Key Attributes Accessibility
<fieldset> Group related form controls disabled, form, name Creates logical grouping for screen readers
<legend> Caption for fieldset None (first child of fieldset) Announces group name to screen readers

Fieldset Attributes

Attribute Purpose
disabled Disables all controls within fieldset
form Associates fieldset with form (by form id)
name Name for the fieldset
Use Cases:
  • Radio button groups - Group related options
  • Checkbox groups - Related checkboxes
  • Address sections - Street, city, zip
  • Contact info - Phone, email, etc.
  • Payment details - Card, billing address
  • Multi-step forms - Logical sections

Example: Fieldset and legend usage

<!-- Radio button group -->
<fieldset>
  <legend>Select Payment Method</legend>
  <input type="radio" name="payment" id="card" value="card" checked>
  <label for="card">Credit Card</label><br>
  <input type="radio" name="payment" id="paypal" value="paypal">
  <label for="paypal">PayPal</label><br>
  <input type="radio" name="payment" id="bank" value="bank">
  <label for="bank">Bank Transfer</label>
</fieldset>

<!-- Checkbox group -->
<fieldset>
  <legend>Select Interests</legend>
  <input type="checkbox" name="interests" id="coding" value="coding">
  <label for="coding">Coding</label><br>
  <input type="checkbox" name="interests" id="design" value="design">
  <label for="design">Design</label><br>
  <input type="checkbox" name="interests" id="marketing" value="marketing">
  <label for="marketing">Marketing</label>
</fieldset>

<!-- Address form section -->
<fieldset>
  <legend>Shipping Address</legend>
  <label for="street">Street:</label>
  <input type="text" id="street" name="street"><br>
  <label for="city">City:</label>
  <input type="text" id="city" name="city"><br>
  <label for="zip">ZIP:</label>
  <input type="text" id="zip" name="zip">
</fieldset>

<!-- Disabled fieldset -->
<fieldset disabled>
  <legend>Premium Features (Upgrade Required)</legend>
  <input type="checkbox" name="feature1" id="feature1">
  <label for="feature1">Advanced Analytics</label><br>
  <input type="checkbox" name="feature2" id="feature2">
  <label for="feature2">Priority Support</label>
</fieldset>

<!-- Nested fieldsets -->
<form>
  <fieldset>
    <legend>Account Information</legend>
    
    <fieldset>
      <legend>Personal Details</legend>
      <input type="text" name="firstname" placeholder="First Name">
      <input type="text" name="lastname" placeholder="Last Name">
    </fieldset>
    
    <fieldset>
      <legend>Contact Information</legend>
      <input type="email" name="email" placeholder="Email">
      <input type="tel" name="phone" placeholder="Phone">
    </fieldset>
  </fieldset>
</form>
Note: Always use <fieldset> and <legend> for radio button groups - it's essential for accessibility. Screen readers announce the legend when focusing on any control within the fieldset.

7.6 Label Association and Accessibility

Association Method Syntax Use Case Clickable
Explicit (for/id) <label for="inputId">...</label> Separate label and input Yes
Implicit (wrapping) <label>Text <input></label> Label wraps input Yes
aria-label <input aria-label="Description"> No visible label No
aria-labelledby <input aria-labelledby="id1 id2"> Reference existing element(s) No

Label Attributes

Attribute Purpose
for Associates with input id
form Associates with form id
Benefits:
  • Clickable target area - Easier to interact
  • Screen reader support - Announces label
  • Focus management - Clicking label focuses input
  • Better mobile UX - Larger touch targets

Example: Label association methods

<!-- Explicit association (for/id) - Recommended -->
<label for="username">Username:</label>
<input type="text" id="username" name="username">

<!-- Implicit association (wrapping) -->
<label>
  Email:
  <input type="email" name="email">
</label>

<!-- Checkbox with explicit label -->
<input type="checkbox" id="terms" name="terms">
<label for="terms">I agree to the terms and conditions</label>

<!-- Radio buttons with labels -->
<input type="radio" name="size" id="small" value="S">
<label for="small">Small</label>
<input type="radio" name="size" id="medium" value="M">
<label for="medium">Medium</label>
<input type="radio" name="size" id="large" value="L">
<label for="large">Large</label>

<!-- Multiple labels for one input -->
<label for="price">Price:</label>
<input type="number" id="price" name="price" aria-describedby="price-help">
<small id="price-help">Enter amount in USD</small>

<!-- aria-label (no visible label) -->
<input type="search" aria-label="Search products" placeholder="Search...">

<!-- aria-labelledby (reference existing elements) -->
<h3 id="card-heading">Credit Card Information</h3>
<input type="text" aria-labelledby="card-heading" placeholder="Card number">

<!-- Required field indication -->
<label for="email-required">
  Email: <span aria-label="required">*</span>
</label>
<input type="email" id="email-required" name="email" required>

<!-- Label with help text -->
<label for="password">
  Password:
  <span style="font-weight: normal; font-size: 0.9em;">(min 8 characters)</span>
</label>
<input type="password" id="password" name="password" minlength="8">
Warning: Every form input must have an associated label for accessibility. Never rely solely on placeholder - it disappears when typing and isn't read by all screen readers. Use aria-label only when a visible label isn't possible.

7.7 Form Validation Attributes and Patterns

Attribute Applies To Purpose Validation Type
required Most inputs, select, textarea Field must not be empty Presence
minlength text, email, url, tel, password, search, textarea Minimum character count Length
maxlength text, email, url, tel, password, search, textarea Maximum character count Length
min number, range, date, time, datetime-local, month, week Minimum value Range
max number, range, date, time, datetime-local, month, week Maximum value Range
step number, range, date, time, datetime-local, month, week Valid increment intervals Granularity
pattern text, email, url, tel, password, search Regular expression validation Format
type input Built-in validation (email, url, number, etc.) Type-specific
accept file Allowed file types File type
multiple email, file Allow multiple values Quantity

Common Patterns

Pattern Use Case
[0-9]{5} 5-digit ZIP code
[A-Za-z]{3,} 3+ letters only
\d{3}-\d{3}-\d{4} Phone: 555-123-4567
[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$ Email format
^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$ Password: 8+ chars, letter+digit

Validation States (CSS)

Pseudo-class State
:valid Passes validation
:invalid Fails validation
:required Has required attribute
:optional Not required
:in-range Within min/max
:out-of-range Outside min/max

Example: Form validation attributes

<form>
  <!-- Required field -->
  <label for="name">Name (required):</label>
  <input type="text" id="name" name="name" required>

  <!-- Length validation -->
  <label for="username">Username (3-15 characters):</label>
  <input type="text" id="username" name="username" 
         minlength="3" maxlength="15" required>

  <!-- Number range -->
  <label for="age">Age (18-100):</label>
  <input type="number" id="age" name="age" 
         min="18" max="100" required>

  <!-- Step validation -->
  <label for="donation">Donation (multiples of $5):</label>
  <input type="number" id="donation" name="donation" 
         min="5" step="5">

  <!-- Date range -->
  <label for="appointment">Appointment Date:</label>
  <input type="date" id="appointment" name="appointment"
         min="2024-01-01" max="2024-12-31" required>

  <!-- Pattern: ZIP code -->
  <label for="zip">ZIP Code:</label>
  <input type="text" id="zip" name="zip" 
         pattern="[0-9]{5}" 
         title="5-digit ZIP code"
         placeholder="12345" required>

  <!-- Pattern: Phone number -->
  <label for="phone">Phone:</label>
  <input type="tel" id="phone" name="phone"
         pattern="\d{3}-\d{3}-\d{4}"
         title="Format: 555-123-4567"
         placeholder="555-123-4567">

  <!-- Pattern: Username (alphanumeric + underscore) -->
  <label for="user">Username:</label>
  <input type="text" id="user" name="user"
         pattern="[a-zA-Z0-9_]{4,16}"
         title="4-16 characters: letters, numbers, underscore only">

  <!-- Pattern: Strong password -->
  <label for="password">Password:</label>
  <input type="password" id="password" name="password"
         pattern="^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$"
         title="Min 8 chars, at least one letter, number, and special character"
         required>

  <!-- Multiple emails -->
  <label for="emails">Email Addresses (comma-separated):</label>
  <input type="email" id="emails" name="emails" multiple>

  <!-- File type validation -->
  <label for="document">Upload Document:</label>
  <input type="file" id="document" name="document"
         accept=".pdf,.doc,.docx" required>

  <!-- Image files only -->
  <label for="photo">Upload Photo:</label>
  <input type="file" id="photo" name="photo"
         accept="image/*">

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

<!-- CSS for validation states -->
<style>
  input:valid {
    border-color: green;
  }
  input:invalid {
    border-color: red;
  }
  input:required {
    border-left: 3px solid orange;
  }
</style>
Note: Always include title attribute with pattern to provide user-friendly error messages. Browser default messages can be cryptic. Use :invalid CSS pseudo-class carefully - it triggers before user interaction.

Section 7 Key Takeaways

  • Use POST for data modification, GET for search/filtering
  • Set enctype="multipart/form-data" for file uploads
  • Use appropriate input types (email, tel, url, date) for mobile keyboard optimization
  • Always associate labels with inputs using for/id for accessibility
  • Wrap radio button groups in <fieldset> with <legend>
  • Use pattern with title for custom validation with user-friendly messages
  • Avoid type="reset" - users often click it by mistake
  • Provide visible labels - don't rely solely on placeholder text

8. HTML5 Form Validation and User Experience

8.1 Required Fields and Validation States

Attribute/Pseudo-class Purpose Applied To Behavior
required Mark field as mandatory input, select, textarea Prevents submission if empty
:valid Style valid inputs All form controls Matches when validation passes
:invalid Style invalid inputs All form controls Matches when validation fails
:required Style required fields input, select, textarea Matches fields with required attribute
:optional Style optional fields input, select, textarea Matches fields without required
:user-invalid Style invalid after user interaction All form controls Only triggers after user edits/blurs
:placeholder-shown When placeholder is visible input, textarea Empty field showing placeholder
:blank Empty field (experimental) input, textarea Field has no value

Validation Timing

When What Validates
On Submit All fields checked, first error focused
On Blur Individual field (with JS)
On Input Real-time validation (with JS)
Immediately Type-specific validation (email, url)

Validity States (JS)

Property Description
valid All constraints satisfied
valueMissing Required field is empty
typeMismatch Type doesn't match (email, url)
patternMismatch Pattern constraint failed
tooLong Exceeds maxlength
tooShort Below minlength
rangeUnderflow Below min value
rangeOverflow Above max value
stepMismatch Not a valid step

Example: Required fields and validation states

<!-- HTML: Required fields -->
<form id="signupForm">
  <label for="email">Email (required):</label>
  <input type="email" id="email" name="email" required>
  
  <label for="username">Username (required):</label>
  <input type="text" id="username" name="username" required minlength="3">
  
  <label for="age">Age (optional):</label>
  <input type="number" id="age" name="age" min="13">
  
  <button type="submit">Sign Up</button>
</form>

<!-- CSS: Validation state styling -->
<style>
  /* Style all required fields */
  input:required {
    border-left: 3px solid #ff9800;
  }
  
  /* Valid state (avoid on page load) */
  input:not(:placeholder-shown):valid {
    border-color: #4caf50;
    background-image: url('check-icon.svg');
    background-repeat: no-repeat;
    background-position: right 10px center;
  }
  
  /* Invalid state (only after user interaction) */
  input:user-invalid,
  input:not(:placeholder-shown):invalid {
    border-color: #f44336;
    background-color: #ffebee;
  }
  
  /* Focus states */
  input:invalid:focus {
    outline: 2px solid #f44336;
  }
  
  input:valid:focus {
    outline: 2px solid #4caf50;
  }
  
  /* Optional fields (subtle styling) */
  input:optional {
    border-left: 3px solid #ccc;
  }
</style>

<!-- JavaScript: Check validity -->
<script>
  const form = document.getElementById('signupForm');
  const email = document.getElementById('email');
  
  form.addEventListener('submit', (e) => {
    if (!form.checkValidity()) {
      e.preventDefault();
      alert('Please fill in all required fields correctly.');
    }
  });
  
  // Check individual field validity
  email.addEventListener('blur', () => {
    const validity = email.validity;
    
    if (validity.valueMissing) {
      console.log('Email is required');
    } else if (validity.typeMismatch) {
      console.log('Invalid email format');
    } else if (validity.valid) {
      console.log('Email is valid');
    }
  });
</script>
Warning: :invalid triggers immediately on page load for empty required fields. Use :user-invalid or :not(:placeholder-shown):invalid to only style after user interaction.

8.2 Pattern Validation and Regular Expressions

Pattern Regex Use Case Example
ZIP Code (US) [0-9]{5} 5-digit postal code 12345
ZIP+4 [0-9]{5}(-[0-9]{4})? Extended ZIP 12345-6789
Phone (US) \d{3}-\d{3}-\d{4} Formatted phone 555-123-4567
Phone (Flexible) [\d\s\-\(\)]+ Various formats (555) 123-4567
Credit Card \d{4}[\s\-]?\d{4}[\s\-]?\d{4}[\s\-]?\d{4} 16-digit card 1234-5678-9012-3456
Username [a-zA-Z0-9_]{3,16} Alphanumeric + underscore user_name123
Hex Color #?([a-fA-F0-9]{6}|[a-fA-F0-9]{3}) Hex color code #ff5733
URL Slug [a-z0-9]+(?:-[a-z0-9]+)* URL-friendly string my-article-title
IPv4 ^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$ IP address 192.168.1.1
Strong Password ^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$ Min 8, upper, lower, digit, special Pass@123
Date (MM/DD/YYYY) (0[1-9]|1[0-2])\/(0[1-9]|[12][0-9]|3[01])\/\d{4} US date format 12/31/2024
Time (24hr) ([01]?[0-9]|2[0-3]):[0-5][0-9] HH:MM format 14:30

Example: Pattern validation with helpful messages

<!-- ZIP Code -->
<label for="zip">ZIP Code:</label>
<input type="text" id="zip" name="zip"
       pattern="[0-9]{5}"
       title="Please enter a 5-digit ZIP code"
       placeholder="12345"
       required>

<!-- Phone Number -->
<label for="phone">Phone:</label>
<input type="tel" id="phone" name="phone"
       pattern="\d{3}-\d{3}-\d{4}"
       title="Format: 555-123-4567"
       placeholder="555-123-4567">

<!-- Username -->
<label for="username">Username:</label>
<input type="text" id="username" name="username"
       pattern="[a-zA-Z0-9_]{3,16}"
       title="3-16 characters: letters, numbers, and underscores only"
       placeholder="user_name"
       required>

<!-- Strong Password -->
<label for="password">Password:</label>
<input type="password" id="password" name="password"
       pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$"
       title="Minimum 8 characters, at least one uppercase, lowercase, number, and special character"
       required>

<!-- Credit Card -->
<label for="card">Credit Card:</label>
<input type="text" id="card" name="card"
       pattern="\d{4}[\s\-]?\d{4}[\s\-]?\d{4}[\s\-]?\d{4}"
       title="Enter 16-digit card number"
       placeholder="1234-5678-9012-3456"
       maxlength="19">

<!-- URL Slug -->
<label for="slug">Article Slug:</label>
<input type="text" id="slug" name="slug"
       pattern="[a-z0-9]+(?:-[a-z0-9]+)*"
       title="Lowercase letters, numbers, and hyphens only"
       placeholder="my-article-title">

<!-- Hex Color -->
<label for="color">Hex Color:</label>
<input type="text" id="color" name="color"
       pattern="#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3})"
       title="Enter hex color code (e.g., #ff5733 or #fff)"
       placeholder="#ff5733">

<!-- Custom validation message (JavaScript) -->
<script>
  const usernameInput = document.getElementById('username');
  
  usernameInput.addEventListener('invalid', (e) => {
    if (usernameInput.validity.patternMismatch) {
      usernameInput.setCustomValidity('Username must be 3-16 characters and contain only letters, numbers, and underscores.');
    } else {
      usernameInput.setCustomValidity('');
    }
  });
  
  usernameInput.addEventListener('input', () => {
    usernameInput.setCustomValidity('');
  });
</script>
Note: Always include title attribute with pattern - it's shown in the validation message. Regex in pattern is automatically anchored (^...$), so don't add anchors. Use setCustomValidity() for better error messages.

8.3 Custom Validation Messages and Styling

Method/Property Purpose Usage Returns/Effect
checkValidity() Check if element is valid element.checkValidity() Boolean (true/false)
reportValidity() Check and show validation UI element.reportValidity() Boolean + shows browser message
setCustomValidity() Set custom error message element.setCustomValidity('msg') Sets validation message
validationMessage Get current validation message element.validationMessage String (error message)
validity Access validity state object element.validity.valid ValidityState object

ValidityState Properties

Property Triggered By
valueMissing required field empty
typeMismatch Invalid email/url format
patternMismatch pattern not matched
tooLong Exceeds maxlength
tooShort Below minlength
rangeUnderflow Below min
rangeOverflow Above max
stepMismatch Invalid step
customError setCustomValidity() called
Styling Strategies:
  • Use :invalid / :valid for CSS styling
  • Combine with :focus for interactive feedback
  • Use :user-invalid for post-interaction styling
  • Add icons/colors to indicate validation state
  • Show inline error messages below fields
  • Use aria-invalid for accessibility

Example: Custom validation messages and styling

<!-- HTML: Form with custom validation -->
<form id="customForm" novalidate>
  <div class="form-group">
    <label for="email">Email:</label>
    <input type="email" id="email" name="email" required>
    <span class="error-message" id="email-error"></span>
  </div>
  
  <div class="form-group">
    <label for="password">Password:</label>
    <input type="password" id="password" name="password" required minlength="8">
    <span class="error-message" id="password-error"></span>
  </div>
  
  <div class="form-group">
    <label for="confirm-password">Confirm Password:</label>
    <input type="password" id="confirm-password" name="confirm-password" required>
    <span class="error-message" id="confirm-error"></span>
  </div>
  
  <button type="submit">Submit</button>
</form>

<!-- CSS: Custom validation styling -->
<style>
  .form-group {
    margin-bottom: 20px;
    position: relative;
  }
  
  input {
    width: 100%;
    padding: 10px;
    border: 2px solid #ddd;
    border-radius: 4px;
    transition: border-color 0.3s;
  }
  
  /* Valid state */
  input:valid:not(:placeholder-shown) {
    border-color: #4caf50;
  }
  
  input:valid:not(:placeholder-shown)::after {
    content: '✓';
    color: #4caf50;
    position: absolute;
    right: 10px;
  }
  
  /* Invalid state */
  input:invalid:not(:placeholder-shown),
  input[aria-invalid="true"] {
    border-color: #f44336;
    background-color: #ffebee;
  }
  
  /* Error message styling */
  .error-message {
    display: none;
    color: #f44336;
    font-size: 0.875em;
    margin-top: 5px;
  }
  
  .error-message.visible {
    display: block;
  }
  
  /* Focus states */
  input:focus {
    outline: none;
    border-color: #2196f3;
    box-shadow: 0 0 0 3px rgba(33, 150, 243, 0.1);
  }
</style>

<!-- JavaScript: Custom validation logic -->
<script>
  const form = document.getElementById('customForm');
  const email = document.getElementById('email');
  const password = document.getElementById('password');
  const confirmPassword = document.getElementById('confirm-password');
  
  // Custom error messages
  const errorMessages = {
    valueMissing: 'This field is required.',
    typeMismatch: {
      email: 'Please enter a valid email address.',
      url: 'Please enter a valid URL.'
    },
    tooShort: (minLength) => `Please enter at least ${minLength} characters.`,
    patternMismatch: 'Please match the requested format.',
  };
  
  // Validate individual field
  function validateField(field) {
    const errorElement = document.getElementById(`${field.id}-error`);
    const validity = field.validity;
    let errorMessage = '';
    
    if (validity.valueMissing) {
      errorMessage = errorMessages.valueMissing;
    } else if (validity.typeMismatch) {
      errorMessage = errorMessages.typeMismatch[field.type] || 'Invalid format.';
    } else if (validity.tooShort) {
      errorMessage = errorMessages.tooShort(field.minLength);
    } else if (validity.patternMismatch) {
      errorMessage = field.title || errorMessages.patternMismatch;
    }
    
    // Custom password match validation
    if (field === confirmPassword && field.value !== password.value) {
      errorMessage = 'Passwords do not match.';
      field.setCustomValidity(errorMessage);
    } else if (field === confirmPassword) {
      field.setCustomValidity('');
    }
    
    // Display error
    if (errorMessage) {
      errorElement.textContent = errorMessage;
      errorElement.classList.add('visible');
      field.setAttribute('aria-invalid', 'true');
      return false;
    } else {
      errorElement.textContent = '';
      errorElement.classList.remove('visible');
      field.removeAttribute('aria-invalid');
      return true;
    }
  }
  
  // Validate on blur
  [email, password, confirmPassword].forEach(field => {
    field.addEventListener('blur', () => validateField(field));
    field.addEventListener('input', () => {
      if (field.getAttribute('aria-invalid') === 'true') {
        validateField(field);
      }
    });
  });
  
  // Validate on submit
  form.addEventListener('submit', (e) => {
    e.preventDefault();
    
    const fields = [email, password, confirmPassword];
    const isValid = fields.every(validateField);
    
    if (isValid) {
      console.log('Form is valid, submitting...');
      // form.submit();
    } else {
      // Focus first invalid field
      const firstInvalid = fields.find(field => !field.validity.valid);
      firstInvalid?.focus();
    }
  });
</script>
Warning: When using setCustomValidity(), you must clear it with setCustomValidity('') when the field becomes valid, or the field will remain invalid even if the constraint is satisfied.

8.4 HTML5 Input Constraints and Limits

Constraint Attribute Applies To Example
Required required Most inputs, select, textarea <input required>
Min Length minlength="N" text, email, url, tel, password, search, textarea <input minlength="3">
Max Length maxlength="N" text, email, url, tel, password, search, textarea <input maxlength="50">
Min Value min="N" number, range, date, time, datetime-local, month, week <input type="number" min="0">
Max Value max="N" number, range, date, time, datetime-local, month, week <input type="number" max="100">
Step step="N" number, range, date, time, datetime-local, month, week <input type="number" step="0.01">
Pattern pattern="regex" text, email, url, tel, password, search <input pattern="[0-9]{5}">
Accept accept="types" file <input type="file" accept="image/*">
Multiple multiple email, file, select <input type="email" multiple>

Step Values

Step Use Case
1 Integers only (default)
0.01 Decimal values (currency)
0.1 Single decimal place
5 Multiples of 5
any No step constraint
900 (time) 15-minute intervals

Accept MIME Types

Value Accepts
image/* Any image type
video/* Any video type
audio/* Any audio type
.pdf PDF files only
.jpg,.png Specific extensions
image/png Specific MIME type

Example: Input constraints in practice

<!-- Length constraints -->
<label for="username">Username (3-15 characters):</label>
<input type="text" id="username" name="username"
       minlength="3" maxlength="15" required>

<!-- Number range with step -->
<label for="price">Price ($):</label>
<input type="number" id="price" name="price"
       min="0" max="10000" step="0.01" 
       placeholder="0.00">

<!-- Age restriction -->
<label for="age">Age (must be 18+):</label>
<input type="number" id="age" name="age"
       min="18" max="120" required>

<!-- Date range (current year only) -->
<label for="event-date">Event Date:</label>
<input type="date" id="event-date" name="event-date"
       min="2024-01-01" max="2024-12-31" required>

<!-- Time with 15-minute intervals -->
<label for="appointment">Appointment Time:</label>
<input type="time" id="appointment" name="appointment"
       min="09:00" max="17:00" step="900"> <!-- 900 seconds = 15 min -->

<!-- Percentage (0-100 with 0.1 precision) -->
<label for="discount">Discount (%):</label>
<input type="number" id="discount" name="discount"
       min="0" max="100" step="0.1">

<!-- File upload (images only) -->
<label for="avatar">Profile Picture:</label>
<input type="file" id="avatar" name="avatar"
       accept="image/png, image/jpeg, image/webp"
       required>

<!-- Multiple file upload -->
<label for="documents">Upload Documents:</label>
<input type="file" id="documents" name="documents"
       accept=".pdf,.doc,.docx"
       multiple>

<!-- Multiple emails -->
<label for="recipients">Email Recipients:</label>
<input type="email" id="recipients" name="recipients"
       multiple
       placeholder="email1@example.com, email2@example.com">

<!-- Range slider with step -->
<label for="volume">Volume (0-100, steps of 5):</label>
<input type="range" id="volume" name="volume"
       min="0" max="100" step="5" value="50">
<output for="volume">50</output>

<!-- No step constraint (any decimal) -->
<label for="precise">Precise Value:</label>
<input type="number" id="precise" name="precise"
       step="any">
Note: maxlength prevents typing beyond the limit, while minlength only validates on submit. Use step="any" to allow any decimal value. For time inputs, step is in seconds (900 = 15 minutes).

8.5 Form Submission and Method Handling

Method Use Case Data in URL Cacheable
GET Search, filter, idempotent operations Yes (query string) Yes
POST Create, update, delete, file uploads, sensitive data No (request body) No
DIALOG Close dialog without submission N/A N/A

Form Submission Events

Event When Fired
submit Form submitted (before send)
formdata FormData object created
invalid Validation fails on field
reset Form reset triggered

Submit Methods (JS)

Method Description
form.submit() Submit programmatically (no validation)
form.requestSubmit() Submit with validation (fires submit event)
form.reset() Reset to default values
new FormData(form) Extract form data as FormData object

Example: Form submission handling

<!-- GET form (search) -->
<form action="/search" method="GET">
  <input type="search" name="q" placeholder="Search...">
  <input type="checkbox" name="category" value="products"> Products
  <input type="checkbox" name="category" value="articles"> Articles
  <button type="submit">Search</button>
</form>
<!-- Submits to: /search?q=keyword&category=products&category=articles -->

<!-- POST form (user registration) -->
<form action="/register" method="POST" id="registerForm">
  <input type="text" name="username" required>
  <input type="email" name="email" required>
  <input type="password" name="password" required>
  <button type="submit">Register</button>
</form>

<!-- JavaScript: Intercept and handle submission -->
<script>
  const form = document.getElementById('registerForm');
  
  // Method 1: Prevent default and use Fetch API
  form.addEventListener('submit', async (e) => {
    e.preventDefault(); // Prevent traditional form submission
    
    // Get form data
    const formData = new FormData(form);
    
    // Convert to JSON (if needed)
    const data = Object.fromEntries(formData.entries());
    
    try {
      const response = await fetch('/register', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(data),
      });
      
      if (response.ok) {
        const result = await response.json();
        console.log('Success:', result);
        // Redirect or show success message
      } else {
        console.error('Error:', response.statusText);
      }
    } catch (error) {
      console.error('Network error:', error);
    }
  });
  
  // Method 2: FormData with all form fields
  form.addEventListener('submit', async (e) => {
    e.preventDefault();
    
    const formData = new FormData(form);
    
    // Send as multipart/form-data (good for file uploads)
    const response = await fetch('/register', {
      method: 'POST',
      body: formData, // Browser sets correct Content-Type
    });
  });
  
  // Method 3: Programmatic submission
  function submitForm() {
    if (form.checkValidity()) {
      // Triggers validation and submit event
      form.requestSubmit();
    } else {
      // Show validation errors
      form.reportValidity();
    }
  }
</script>

<!-- Multiple submit buttons with different actions -->
<form method="POST">
  <input type="text" name="content" required>
  
  <button type="submit" name="action" value="save">Save Draft</button>
  <button type="submit" name="action" value="publish">Publish</button>
  <button type="submit" name="action" value="delete" formnovalidate>Delete</button>
</form>

<!-- Override form attributes on button -->
<form action="/default" method="POST">
  <input type="email" name="email" required>
  
  <button type="submit">Submit to Default</button>
  <button type="submit" 
          formaction="/alternative"
          formmethod="GET"
          formnovalidate>
    Submit to Alternative (Skip Validation)
  </button>
</form>
Warning: form.submit() bypasses validation and doesn't fire the submit event. Use form.requestSubmit() instead to trigger validation. Use formnovalidate on submit buttons to skip validation for actions like "Save Draft" or "Delete".

8.6 Form Data Encoding and File Uploads

Encoding Type (enctype) Value Use Case Content-Type Header
URL Encoded application/x-www-form-urlencoded Standard forms (default) application/x-www-form-urlencoded
Multipart multipart/form-data File uploads multipart/form-data; boundary=...
Plain Text text/plain Debugging (not recommended) text/plain

File Input Attributes

Attribute Purpose
accept Filter file types in picker
multiple Allow multiple file selection
capture Use camera (mobile): user, environment
files FileList object (read-only, JS)

FormData Methods

Method Description
append() Add field (allows duplicates)
set() Set field (overwrites existing)
get() Get single value
getAll() Get all values for key
delete() Remove field
has() Check if field exists

Example: File uploads and form data encoding

<!-- File upload form -->
<form action="/upload" method="POST" enctype="multipart/form-data" id="uploadForm">
  <label for="file">Select File:</label>
  <input type="file" id="file" name="file" accept="image/*" required>
  
  <label for="description">Description:</label>
  <input type="text" id="description" name="description">
  
  <button type="submit">Upload</button>
</form>

<!-- Multiple file upload with preview -->
<form id="multiUploadForm">
  <input type="file" id="files" name="files" multiple accept="image/*">
  <div id="preview"></div>
  <button type="submit">Upload All</button>
</form>

<script>
  // Single file upload
  const uploadForm = document.getElementById('uploadForm');
  
  uploadForm.addEventListener('submit', async (e) => {
    e.preventDefault();
    
    const formData = new FormData(uploadForm);
    
    try {
      const response = await fetch('/upload', {
        method: 'POST',
        body: formData, // Browser automatically sets multipart/form-data
      });
      
      if (response.ok) {
        const result = await response.json();
        console.log('Upload successful:', result);
      }
    } catch (error) {
      console.error('Upload failed:', error);
    }
  });
  
  // Multiple files with preview
  const filesInput = document.getElementById('files');
  const preview = document.getElementById('preview');
  
  filesInput.addEventListener('change', (e) => {
    preview.innerHTML = ''; // Clear previous previews
    
    const files = e.target.files;
    
    for (let i = 0; i < files.length; i++) {
      const file = files[i];
      
      // Validate file
      if (!file.type.startsWith('image/')) {
        alert(`${file.name} is not an image`);
        continue;
      }
      
      if (file.size > 5 * 1024 * 1024) { // 5MB limit
        alert(`${file.name} is too large (max 5MB)`);
        continue;
      }
      
      // Create preview
      const reader = new FileReader();
      reader.onload = (event) => {
        const img = document.createElement('img');
        img.src = event.target.result;
        img.style.width = '100px';
        img.style.margin = '5px';
        preview.appendChild(img);
      };
      reader.readAsDataURL(file);
    }
  });
  
  // Multi-file upload
  const multiUploadForm = document.getElementById('multiUploadForm');
  
  multiUploadForm.addEventListener('submit', async (e) => {
    e.preventDefault();
    
    const formData = new FormData();
    const files = filesInput.files;
    
    // Add each file
    for (let i = 0; i < files.length; i++) {
      formData.append('files', files[i]);
    }
    
    // Add additional data
    formData.append('userId', '12345');
    formData.append('category', 'photos');
    
    const response = await fetch('/upload-multiple', {
      method: 'POST',
      body: formData,
    });
  });
  
  // Manual FormData construction
  const manualFormData = new FormData();
  
  // Add text fields
  manualFormData.append('username', 'john_doe');
  manualFormData.append('email', 'john@example.com');
  
  // Add file (from input element)
  const fileInput = document.getElementById('file');
  manualFormData.append('avatar', fileInput.files[0]);
  
  // Add file (blob/programmatically)
  const blob = new Blob(['Hello, world!'], { type: 'text/plain' });
  manualFormData.append('note', blob, 'note.txt');
  
  // Get values
  console.log(manualFormData.get('username')); // 'john_doe'
  console.log(manualFormData.getAll('username')); // ['john_doe']
  
  // Check if field exists
  console.log(manualFormData.has('email')); // true
  
  // Delete field
  manualFormData.delete('note');
  
  // Iterate over entries
  for (const [key, value] of manualFormData.entries()) {
    console.log(`${key}:`, value);
  }
</script>

<!-- Camera capture (mobile) -->
<form>
  <!-- Use front camera -->
  <input type="file" accept="image/*" capture="user">
  
  <!-- Use back camera -->
  <input type="file" accept="image/*" capture="environment">
  
  <!-- Video capture -->
  <input type="file" accept="video/*" capture>
</form>
Note: Always set enctype="multipart/form-data" for file uploads. When using FormData with fetch(), don't set Content-Type header - browser adds it automatically with boundary. Use capture attribute on mobile to directly access camera.

Section 8 Key Takeaways

  • Use :user-invalid or :not(:placeholder-shown):invalid to avoid styling empty fields as invalid
  • Always include title with pattern for user-friendly error messages
  • Use setCustomValidity() for custom validation, but remember to clear it with setCustomValidity('')
  • Use form.requestSubmit() instead of form.submit() to trigger validation
  • maxlength prevents typing, minlength validates on submit
  • Set enctype="multipart/form-data" for file uploads
  • Use formnovalidate on buttons for actions that should skip validation (e.g., "Save Draft")
  • FormData automatically handles multipart encoding - don't manually set Content-Type when using fetch()

9. Tables and Tabular Data

9.1 Table Structure (table, thead, tbody, tfoot)

Element Purpose Parent Required
<table> Root table container Any block element Yes
<caption> Table title/description <table> (first child) Recommended
<thead> Table header section <table> Optional
<tbody> Table body section (main data) <table> Optional (implicit)
<tfoot> Table footer section (summary) <table> Optional
<tr> Table row <thead>, <tbody>, <tfoot>, <table> Yes
<th> Header cell <tr> In header rows
<td> Data cell <tr> In data rows
<colgroup> Group of columns for styling <table> (after caption, before thead) Optional
<col> Single column definition <colgroup> Optional

Table Attributes

Attribute Element Purpose
border <table> Border width (deprecated, use CSS)
cellpadding <table> Cell padding (deprecated, use CSS)
cellspacing <table> Cell spacing (deprecated, use CSS)
width <table>, <col> Table/column width (use CSS)
Section Order:
  1. <caption> (optional, first)
  2. <colgroup> (optional)
  3. <thead> (optional)
  4. <tbody> (optional, can have multiple)
  5. <tfoot> (optional, displays at bottom)

Note: <tfoot> can be placed before <tbody> in HTML but will render at the bottom.

Example: Proper table structure

<!-- Complete table structure -->
<table>
  <caption>Quarterly Sales Report 2024</caption>
  
  <colgroup>
    <col style="background-color: #f0f0f0;">
    <col span="3" style="background-color: #fff;">
  </colgroup>
  
  <thead>
    <tr>
      <th>Quarter</th>
      <th>Revenue</th>
      <th>Expenses</th>
      <th>Profit</th>
    </tr>
  </thead>
  
  <tbody>
    <tr>
      <th>Q1</th>
      <td>$100,000</td>
      <td>$60,000</td>
      <td>$40,000</td>
    </tr>
    <tr>
      <th>Q2</th>
      <td>$120,000</td>
      <td>$65,000</td>
      <td>$55,000</td>
    </tr>
    <tr>
      <th>Q3</th>
      <td>$110,000</td>
      <td>$62,000</td>
      <td>$48,000</td>
    </tr>
    <tr>
      <th>Q4</th>
      <td>$150,000</td>
      <td>$70,000</td>
      <td>$80,000</td>
    </tr>
  </tbody>
  
  <tfoot>
    <tr>
      <th>Total</th>
      <td>$480,000</td>
      <td>$257,000</td>
      <td>$223,000</td>
    </tr>
  </tfoot>
</table>

<!-- Simple table (minimal structure) -->
<table>
  <caption>Employee Contact List</caption>
  <tr>
    <th>Name</th>
    <th>Email</th>
    <th>Phone</th>
  </tr>
  <tr>
    <td>John Doe</td>
    <td>john@example.com</td>
    <td>555-1234</td>
  </tr>
  <tr>
    <td>Jane Smith</td>
    <td>jane@example.com</td>
    <td>555-5678</td>
  </tr>
</table>

<!-- Multiple tbody sections -->
<table>
  <caption>Departments</caption>
  <thead>
    <tr>
      <th>Employee</th>
      <th>Role</th>
    </tr>
  </thead>
  
  <tbody>
    <tr>
      <th colspan="2">Engineering</th>
    </tr>
    <tr>
      <td>Alice</td>
      <td>Developer</td>
    </tr>
    <tr>
      <td>Bob</td>
      <td>Designer</td>
    </tr>
  </tbody>
  
  <tbody>
    <tr>
      <th colspan="2">Sales</th>
    </tr>
    <tr>
      <td>Charlie</td>
      <td>Manager</td>
    </tr>
    <tr>
      <td>Diana</td>
      <td>Representative</td>
    </tr>
  </tbody>
</table>
Note: Use <thead>, <tbody>, and <tfoot> for accessibility and to enable features like fixed headers on scroll. Browser implicitly creates <tbody> if omitted.

9.2 Table Rows and Cells (tr, td, th)

Element Attributes Purpose Use Case
<tr> class, id, style Define table row Container for cells
<th> scope, colspan, rowspan, headers Header cell (bold, centered) Column/row headers
<td> colspan, rowspan, headers Data cell Table data values

TH Scope Attribute

Value Meaning
col Header for column
row Header for row
colgroup Header for column group
rowgroup Header for row group

Cell Alignment (CSS)

Property Values
text-align left, center, right
vertical-align top, middle, bottom

Example: Table rows and cells with scope

<!-- Column headers with scope -->
<table>
  <thead>
    <tr>
      <th scope="col">Product</th>
      <th scope="col">Price</th>
      <th scope="col">Quantity</th>
      <th scope="col">Total</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Widget</td>
      <td>$10.00</td>
      <td>5</td>
      <td>$50.00</td>
    </tr>
    <tr>
      <td>Gadget</td>
      <td>$25.00</td>
      <td>2</td>
      <td>$50.00</td>
    </tr>
  </tbody>
</table>

<!-- Row headers with scope -->
<table>
  <caption>Monthly Expenses</caption>
  <thead>
    <tr>
      <th></th>
      <th scope="col">January</th>
      <th scope="col">February</th>
      <th scope="col">March</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">Rent</th>
      <td>$1,500</td>
      <td>$1,500</td>
      <td>$1,500</td>
    </tr>
    <tr>
      <th scope="row">Utilities</th>
      <td>$200</td>
      <td>$220</td>
      <td>$180</td>
    </tr>
    <tr>
      <th scope="row">Food</th>
      <td>$600</td>
      <td>$650</td>
      <td>$580</td>
    </tr>
  </tbody>
</table>

<!-- Complex table with headers attribute -->
<table>
  <tr>
    <th id="name">Name</th>
    <th id="math">Math</th>
    <th id="science">Science</th>
  </tr>
  <tr>
    <td headers="name">Alice</td>
    <td headers="math">95</td>
    <td headers="science">88</td>
  </tr>
  <tr>
    <td headers="name">Bob</td>
    <td headers="math">87</td>
    <td headers="science">92</td>
  </tr>
</table>

<!-- Styled cells -->
<style>
  table {
    border-collapse: collapse;
    width: 100%;
  }
  
  th, td {
    border: 1px solid #ddd;
    padding: 12px;
    text-align: left;
  }
  
  th {
    background-color: #4caf50;
    color: white;
    text-align: center;
  }
  
  tr:nth-child(even) {
    background-color: #f2f2f2;
  }
  
  tr:hover {
    background-color: #ddd;
  }
</style>
Warning: Always use scope attribute on <th> elements for accessibility. Screen readers use this to associate headers with data cells. Use scope="col" for column headers and scope="row" for row headers.

9.3 Cell Spanning (colspan, rowspan)

Attribute Applies To Purpose Default
colspan <td>, <th> Span across multiple columns 1
rowspan <td>, <th> Span across multiple rows 1

Colspan Example (visual)

Spanning 3 Columns
Col 1 Col 2 Col 3

Rowspan Example (visual)

Spanning 2 Rows Row 1
Row 2

Example: Colspan and rowspan usage

<!-- Colspan: Header spanning columns -->
<table>
  <thead>
    <tr>
      <th colspan="3">Student Performance</th>
    </tr>
    <tr>
      <th>Name</th>
      <th>Math</th>
      <th>Science</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Alice</td>
      <td>95</td>
      <td>88</td>
    </tr>
  </tbody>
</table>

<!-- Rowspan: Merged rows -->
<table>
  <tr>
    <th rowspan="2">Name</th>
    <th colspan="2">Scores</th>
  </tr>
  <tr>
    <!-- Name cell continues from above -->
    <th>Test 1</th>
    <th>Test 2</th>
  </tr>
  <tr>
    <td>John</td>
    <td>85</td>
    <td>90</td>
  </tr>
</table>

<!-- Combined colspan and rowspan -->
<table>
  <caption>Complex Schedule</caption>
  <thead>
    <tr>
      <th>Time</th>
      <th>Monday</th>
      <th>Tuesday</th>
      <th>Wednesday</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>9:00 AM</th>
      <td rowspan="2">Team Meeting</td>
      <td>Project A</td>
      <td>Project B</td>
    </tr>
    <tr>
      <th>10:00 AM</th>
      <!-- Team Meeting continues -->
      <td>Code Review</td>
      <td>Planning</td>
    </tr>
    <tr>
      <th>11:00 AM</th>
      <td colspan="3">Lunch Break</td>
    </tr>
  </tbody>
</table>

<!-- Pricing table with spanning -->
<table>
  <thead>
    <tr>
      <th rowspan="2">Feature</th>
      <th colspan="3">Plans</th>
    </tr>
    <tr>
      <th>Basic</th>
      <th>Pro</th>
      <th>Enterprise</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Storage</td>
      <td>10 GB</td>
      <td>100 GB</td>
      <td>Unlimited</td>
    </tr>
    <tr>
      <td>Users</td>
      <td>1</td>
      <td>5</td>
      <td>Unlimited</td>
    </tr>
    <tr>
      <td>Support</td>
      <td>Email</td>
      <td colspan="2">24/7 Phone & Email</td>
    </tr>
  </tbody>
</table>
Note: When using rowspan, omit cells in subsequent rows that are spanned. When using colspan, ensure the total columns in each row match. Complex spanning can make tables difficult to maintain and less accessible.

9.4 Table Captions and Summaries

Element/Attribute Purpose Placement Accessibility
<caption> Table title/description First child of <table> Announced by screen readers
summary (deprecated) Table description for screen readers <table summary="..."> Use <caption> or aria-describedby instead
aria-label Accessible name for table <table aria-label="..."> Alternative to caption
aria-describedby Reference to description element <table aria-describedby="id"> Link to external description

Caption Styling (CSS)

Property Values
caption-side top (default), bottom
text-align left, center, right
font-weight normal, bold
Best Practices:
  • Use <caption> for all data tables
  • Keep captions concise and descriptive
  • Position caption at top for visibility
  • Use aria-describedby for longer descriptions
  • Avoid summary attribute (deprecated)

Example: Table captions and descriptions

<!-- Basic caption -->
<table>
  <caption>Monthly Sales Report - Q4 2024</caption>
  <thead>
    <tr>
      <th>Month</th>
      <th>Revenue</th>
      <th>Growth</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>October</td>
      <td>$150,000</td>
      <td>+15%</td>
    </tr>
  </tbody>
</table>

<!-- Styled caption -->
<style>
  caption {
    caption-side: top;
    font-size: 1.2em;
    font-weight: bold;
    margin-bottom: 10px;
    text-align: left;
    color: #333;
  }
</style>

<table>
  <caption>Employee Directory</caption>
  <!-- table content -->
</table>

<!-- Caption at bottom -->
<style>
  .bottom-caption caption {
    caption-side: bottom;
    font-style: italic;
    margin-top: 10px;
  }
</style>

<table class="bottom-caption">
  <caption>Data as of December 2024</caption>
  <!-- table content -->
</table>

<!-- Using aria-describedby for detailed description -->
<p id="table-desc">
  This table shows quarterly performance metrics including revenue, 
  expenses, and profit margins for each department. Data is sorted 
  by revenue in descending order.
</p>

<table aria-describedby="table-desc">
  <caption>2024 Quarterly Performance by Department</caption>
  <thead>
    <tr>
      <th>Department</th>
      <th>Revenue</th>
      <th>Expenses</th>
      <th>Profit Margin</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Sales</td>
      <td>$500,000</td>
      <td>$200,000</td>
      <td>60%</td>
    </tr>
  </tbody>
</table>

<!-- Using aria-label (when caption is not desired visually) -->
<table aria-label="Customer feedback ratings by product">
  <!-- No visible caption, but screen readers announce the label -->
  <thead>
    <tr>
      <th>Product</th>
      <th>Rating</th>
      <th>Reviews</th>
    </tr>
  </thead>
</table>

<!-- Caption with formatting -->
<table>
  <caption>
    <strong>Financial Summary</strong><br>
    <small>All amounts in USD</small>
  </caption>
  <!-- table content -->
</table>
Warning: The summary attribute is deprecated in HTML5. Use <caption> for short descriptions or aria-describedby to reference a detailed description elsewhere in the page.

9.5 Sortable Tables and Interactive Features

Feature Implementation Use Case Accessibility
Sortable Columns JavaScript + click events on <th> Sort data by column Add aria-sort attribute
Filterable Rows JavaScript + input field Search/filter table data Announce filtered count
Expandable Rows JavaScript + detail rows Show/hide additional details Use aria-expanded
Selectable Rows Checkboxes in first column Multi-select for bulk actions Proper label association
Pagination JavaScript + page controls Handle large datasets Announce current page

ARIA Attributes for Tables

Attribute Purpose
aria-sort ascending, descending, none
aria-expanded true/false for expandable rows
aria-selected true/false for selected rows
aria-rowcount Total rows (for pagination)
aria-rowindex Current row index
JavaScript Table Libraries:
  • DataTables: Feature-rich table plugin
  • Tabulator: Interactive tables with filtering
  • AG Grid: Enterprise data grid
  • TanStack Table: Headless table library
  • SortableJS: Drag-and-drop sorting

Example: Sortable table with JavaScript

<!-- Sortable table -->
<table id="sortableTable">
  <caption>Employee List (Click headers to sort)</caption>
  <thead>
    <tr>
      <th onclick="sortTable(0)" style="cursor: pointer;" aria-sort="none">
        Name ↕
      </th>
      <th onclick="sortTable(1)" style="cursor: pointer;" aria-sort="none">
        Department ↕
      </th>
      <th onclick="sortTable(2)" style="cursor: pointer;" aria-sort="none">
        Salary ↕
      </th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Alice</td>
      <td>Engineering</td>
      <td>90000</td>
    </tr>
    <tr>
      <td>Bob</td>
      <td>Sales</td>
      <td>75000</td>
    </tr>
    <tr>
      <td>Charlie</td>
      <td>Marketing</td>
      <td>80000</td>
    </tr>
  </tbody>
</table>

<script>
  function sortTable(columnIndex) {
    const table = document.getElementById('sortableTable');
    const tbody = table.querySelector('tbody');
    const rows = Array.from(tbody.querySelectorAll('tr'));
    const header = table.querySelectorAll('th')[columnIndex];
    
    // Determine sort direction
    const currentSort = header.getAttribute('aria-sort');
    const ascending = currentSort !== 'ascending';
    
    // Sort rows
    rows.sort((a, b) => {
      const aValue = a.cells[columnIndex].textContent;
      const bValue = b.cells[columnIndex].textContent;
      
      // Numeric comparison for salary column
      if (columnIndex === 2) {
        return ascending 
          ? Number(aValue) - Number(bValue)
          : Number(bValue) - Number(aValue);
      }
      
      // String comparison
      return ascending
        ? aValue.localeCompare(bValue)
        : bValue.localeCompare(aValue);
    });
    
    // Re-append sorted rows
    rows.forEach(row => tbody.appendChild(row));
    
    // Update ARIA attributes
    table.querySelectorAll('th').forEach(th => {
      th.setAttribute('aria-sort', 'none');
    });
    header.setAttribute('aria-sort', ascending ? 'ascending' : 'descending');
  }
</script>

<!-- Filterable table -->
<input type="text" id="searchInput" placeholder="Search table..." 
       onkeyup="filterTable()">
<table id="filterableTable">
  <thead>
    <tr>
      <th>Name</th>
      <th>Email</th>
      <th>City</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>John Doe</td>
      <td>john@example.com</td>
      <td>New York</td>
    </tr>
    <tr>
      <td>Jane Smith</td>
      <td>jane@example.com</td>
      <td>Los Angeles</td>
    </tr>
  </tbody>
</table>

<script>
  function filterTable() {
    const input = document.getElementById('searchInput');
    const filter = input.value.toLowerCase();
    const table = document.getElementById('filterableTable');
    const rows = table.querySelectorAll('tbody tr');
    
    rows.forEach(row => {
      const text = row.textContent.toLowerCase();
      row.style.display = text.includes(filter) ? '' : 'none';
    });
  }
</script>

<!-- Selectable rows with checkboxes -->
<table>
  <thead>
    <tr>
      <th>
        <input type="checkbox" id="selectAll" onclick="toggleAll(this)">
      </th>
      <th>Task</th>
      <th>Status</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><input type="checkbox" class="row-select"></td>
      <td>Design mockup</td>
      <td>Complete</td>
    </tr>
    <tr>
      <td><input type="checkbox" class="row-select"></td>
      <td>Write tests</td>
      <td>In Progress</td>
    </tr>
  </tbody>
</table>

<script>
  function toggleAll(source) {
    const checkboxes = document.querySelectorAll('.row-select');
    checkboxes.forEach(cb => cb.checked = source.checked);
  }
</script>
Note: When implementing sortable tables, update aria-sort attributes to indicate sort state. Use role="button" on sortable headers and ensure keyboard accessibility (Enter/Space to activate).

9.6 Responsive Table Techniques

Technique CSS/HTML Pros Cons
Horizontal Scroll overflow-x: auto Simple, preserves layout Hidden content, poor UX
Stacked Cells Display: block on mobile All data visible Loses table semantics
Hidden Columns Hide less important columns Keeps key data visible Data loss on mobile
Flip Layout Transpose rows/columns Better for narrow screens Complex CSS
Cards/List View Convert to card layout Mobile-friendly Loses table semantics

Example: Responsive table implementations

<!-- Method 1: Horizontal scroll wrapper -->
<div style="overflow-x: auto;">
  <table style="min-width: 600px;">
    <thead>
      <tr>
        <th>Name</th>
        <th>Email</th>
        <th>Phone</th>
        <th>Department</th>
        <th>Location</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>John Doe</td>
        <td>john@example.com</td>
        <td>555-1234</td>
        <td>Engineering</td>
        <td>New York</td>
      </tr>
    </tbody>
  </table>
</div>

<!-- Method 2: Stacked cells on mobile -->
<style>
  @media (max-width: 768px) {
    .responsive-table thead {
      display: none;
    }
    
    .responsive-table tr {
      display: block;
      margin-bottom: 20px;
      border: 1px solid #ddd;
    }
    
    .responsive-table td {
      display: block;
      text-align: right;
      padding: 10px;
      border-bottom: 1px solid #eee;
    }
    
    .responsive-table td::before {
      content: attr(data-label);
      float: left;
      font-weight: bold;
    }
    
    .responsive-table td:last-child {
      border-bottom: none;
    }
  }
</style>

<table class="responsive-table">
  <thead>
    <tr>
      <th>Product</th>
      <th>Price</th>
      <th>Stock</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td data-label="Product">Widget</td>
      <td data-label="Price">$19.99</td>
      <td data-label="Stock">50</td>
    </tr>
    <tr>
      <td data-label="Product">Gadget</td>
      <td data-label="Price">$29.99</td>
      <td data-label="Stock">25</td>
    </tr>
  </tbody>
</table>

<!-- Method 3: Hide columns on mobile -->
<style>
  @media (max-width: 768px) {
    .hide-mobile {
      display: none;
    }
  }
</style>

<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Email</th>
      <th class="hide-mobile">Phone</th>
      <th class="hide-mobile">Address</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>John</td>
      <td>john@example.com</td>
      <td class="hide-mobile">555-1234</td>
      <td class="hide-mobile">123 Main St</td>
    </tr>
  </tbody>
</table>

<!-- Method 4: Card layout on mobile -->
<style>
  .card-table {
    width: 100%;
    border-collapse: collapse;
  }
  
  @media (max-width: 768px) {
    .card-table,
    .card-table thead,
    .card-table tbody,
    .card-table tr,
    .card-table th,
    .card-table td {
      display: block;
    }
    
    .card-table thead {
      display: none;
    }
    
    .card-table tr {
      margin-bottom: 15px;
      background: #f9f9f9;
      border-radius: 8px;
      padding: 15px;
      box-shadow: 0 2px 4px rgba(0,0,0,0.1);
    }
    
    .card-table td {
      padding: 5px 0;
      border: none;
    }
    
    .card-table td::before {
      content: attr(data-label) ": ";
      font-weight: bold;
      display: inline-block;
      min-width: 100px;
    }
  }
</style>

<table class="card-table">
  <thead>
    <tr>
      <th>Name</th>
      <th>Role</th>
      <th>Status</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td data-label="Name">Alice Johnson</td>
      <td data-label="Role">Developer</td>
      <td data-label="Status">Active</td>
    </tr>
  </tbody>
</table>

<!-- Method 5: Flip table (transpose) -->
<style>
  @media (max-width: 768px) {
    .flip-table {
      display: block;
    }
    
    .flip-table thead {
      float: left;
    }
    
    .flip-table tbody {
      display: block;
      overflow-x: auto;
      white-space: nowrap;
    }
    
    .flip-table tr {
      display: inline-block;
      vertical-align: top;
    }
    
    .flip-table th,
    .flip-table td {
      display: block;
    }
  }
</style>
Warning: When converting tables to cards or lists on mobile, ensure you maintain semantic HTML and add appropriate ARIA attributes. Use data-label attributes to preserve context when hiding headers.

Section 9 Key Takeaways

  • Use <thead>, <tbody>, and <tfoot> for proper table structure and accessibility
  • Always include scope attribute on <th> elements (scope="col" or scope="row")
  • Use <caption> for table titles - essential for screen readers
  • When using colspan or rowspan, ensure row/column totals remain consistent
  • Add aria-sort attributes to sortable column headers
  • For responsive tables, use data-label attributes when stacking cells on mobile
  • Wrap tables in <div style="overflow-x: auto"> for horizontal scrolling on small screens
  • The summary attribute is deprecated - use <caption> or aria-describedby instead

10. Interactive and Dynamic Elements

10.1 Button Elements and Button Types

Type Behavior Use Case Default
submit Submits parent form Form submission Yes (in forms)
button No default action Custom JavaScript actions No
reset Resets form to defaults Clear form (use sparingly) No

Button vs Input Button

Element Pros Cons
<button> Can contain HTML, more flexible styling Different default in old IE
<input type="button"> Simpler, consistent behavior Text only, less flexible

Button Attributes

Attribute Purpose
type submit, button, reset
disabled Disable button
name Form field name
value Submitted value
form Associate with form by id
formaction Override form action
formmethod Override form method
formnovalidate Skip validation

Example: Button types and usage

<!-- Submit button (default in forms) -->
<form action="/submit">
  <button type="submit">Submit</button>
</form>

<!-- Explicit button type (no form submission) -->
<button type="button" onclick="alert('Clicked!')">Click Me</button>

<!-- Reset button (clears form) -->
<form>
  <input type="text" name="name">
  <button type="reset">Clear Form</button>
</form>

<!-- Button with HTML content -->
<button type="button">
  <svg width="16" height="16">
    <circle cx="8" cy="8" r="7" fill="green"/>
  </svg>
  Save Draft
</button>

<!-- Disabled button -->
<button type="submit" disabled>Processing...</button>

<!-- Button with value (for multiple submits) -->
<form method="POST">
  <button type="submit" name="action" value="save">Save</button>
  <button type="submit" name="action" value="publish">Publish</button>
  <button type="submit" name="action" value="delete" formnovalidate>Delete</button>
</form>

<!-- Button outside form (using form attribute) -->
<form id="myForm">
  <input type="text" name="username">
</form>
<button type="submit" form="myForm">Submit from Outside</button>

<!-- Override form attributes -->
<form action="/default" method="POST">
  <button type="submit">Normal Submit</button>
  <button type="submit" 
          formaction="/alternative" 
          formmethod="GET">
    Alternative Submit
  </button>
</form>

<!-- Styled buttons with states -->
<style>
  button {
    padding: 10px 20px;
    font-size: 16px;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    transition: background-color 0.3s;
  }
  
  button[type="submit"] {
    background-color: #4caf50;
    color: white;
  }
  
  button[type="submit"]:hover {
    background-color: #45a049;
  }
  
  button:disabled {
    background-color: #ccc;
    cursor: not-allowed;
    opacity: 0.6;
  }
  
  button:focus {
    outline: 2px solid #2196f3;
    outline-offset: 2px;
  }
</style>
Warning: Always specify type attribute on buttons. Default is type="submit" inside forms, which can cause unexpected submissions. Use type="button" for JavaScript-only buttons.

10.2 Details and Summary Disclosure Widgets

Element Purpose Required Behavior
<details> Disclosure widget container Yes Expandable/collapsible content
<summary> Visible heading/toggle Optional (defaults to "Details") Clickable toggle control

Details Attributes

Attribute Purpose
open Initially expanded state
name Accordion group (exclusive open)

Events

Event When Fired
toggle When open state changes

Example: Details and summary widgets

<!-- Basic details/summary -->
<details>
  <summary>Click to expand</summary>
  <p>This content is hidden until the user clicks the summary.</p>
</details>

<!-- Initially open -->
<details open>
  <summary>Already Expanded</summary>
  <p>This content is visible by default.</p>
</details>

<!-- FAQ accordion -->
<details>
  <summary>What is HTML?</summary>
  <p>HTML (HyperText Markup Language) is the standard markup language for creating web pages.</p>
</details>

<details>
  <summary>What is CSS?</summary>
  <p>CSS (Cascading Style Sheets) is used to style HTML elements.</p>
</details>

<details>
  <summary>What is JavaScript?</summary>
  <p>JavaScript is a programming language that adds interactivity to web pages.</p>
</details>

<!-- Exclusive accordion (only one open at a time) -->
<details name="accordion">
  <summary>Section 1</summary>
  <p>Content for section 1.</p>
</details>

<details name="accordion">
  <summary>Section 2</summary>
  <p>Content for section 2. Opening this closes Section 1.</p>
</details>

<details name="accordion">
  <summary>Section 3</summary>
  <p>Content for section 3.</p>
</details>

<!-- Nested details -->
<details>
  <summary>Parent Level</summary>
  <p>Parent content.</p>
  
  <details>
    <summary>Child Level</summary>
    <p>Nested content.</p>
  </details>
</details>

<!-- With complex content -->
<details>
  <summary>View Product Details</summary>
  <table>
    <tr>
      <th>Specification</th>
      <th>Value</th>
    </tr>
    <tr>
      <td>Weight</td>
      <td>1.5 kg</td>
    </tr>
    <tr>
      <td>Dimensions</td>
      <td>30 x 20 x 10 cm</td>
    </tr>
  </table>
</details>

<!-- JavaScript event handling -->
<details id="myDetails">
  <summary>Track Toggle Events</summary>
  <p>Content here.</p>
</details>

<script>
  const details = document.getElementById('myDetails');
  
  details.addEventListener('toggle', (e) => {
    if (details.open) {
      console.log('Details expanded');
    } else {
      console.log('Details collapsed');
    }
  });
</script>

<!-- Styled details/summary -->
<style>
  details {
    border: 1px solid #ddd;
    border-radius: 4px;
    padding: 10px;
    margin: 10px 0;
  }
  
  summary {
    font-weight: bold;
    cursor: pointer;
    padding: 5px;
    user-select: none;
  }
  
  summary:hover {
    background-color: #f0f0f0;
  }
  
  details[open] summary {
    margin-bottom: 10px;
    border-bottom: 1px solid #ddd;
  }
  
  /* Custom marker */
  summary::marker {
    content: '▶ ';
  }
  
  details[open] summary::marker {
    content: '▼ ';
  }
</style>
Note: The name attribute creates an exclusive accordion where only one details element with the same name can be open at a time (modern browsers only). Use the toggle event to detect state changes.

10.3 Dialog and Modal Elements

Attribute/Method Purpose Type Modal Behavior
open (attr) Show dialog (non-modal) Attribute No backdrop, no focus trap
show() Show dialog (non-modal) Method No backdrop, no focus trap
showModal() Show dialog (modal) Method Backdrop, focus trap, Esc closes
close() Close dialog Method Returns returnValue

Dialog Properties

Property Description
open Boolean (read-only)
returnValue Value from close()

Dialog Events

Event When Fired
close When dialog closes
cancel When Esc pressed (modal only)

Example: Dialog and modal implementations

<!-- Basic modal dialog -->
<dialog id="myDialog">
  <h2>Dialog Title</h2>
  <p>This is a modal dialog.</p>
  <button onclick="document.getElementById('myDialog').close()">Close</button>
</dialog>

<button onclick="document.getElementById('myDialog').showModal()">Open Modal</button>

<!-- Dialog with form -->
<dialog id="formDialog">
  <form method="dialog">
    <h2>Enter Your Name</h2>
    <input type="text" name="username" required>
    <div>
      <button type="submit" value="cancel">Cancel</button>
      <button type="submit" value="confirm">Confirm</button>
    </div>
  </form>
</dialog>

<button onclick="openFormDialog()">Open Form Dialog</button>

<script>
  const formDialog = document.getElementById('formDialog');
  
  function openFormDialog() {
    formDialog.showModal();
  }
  
  formDialog.addEventListener('close', () => {
    console.log('Dialog closed with:', formDialog.returnValue);
  });
</script>

<!-- Non-modal dialog (show vs showModal) -->
<dialog id="nonModal">
  <p>This is a non-modal dialog. You can still interact with the page.</p>
  <button onclick="document.getElementById('nonModal').close()">Close</button>
</dialog>

<button onclick="document.getElementById('nonModal').show()">Open Non-Modal</button>

<!-- Confirmation dialog -->
<dialog id="confirmDialog">
  <h2>Confirm Action</h2>
  <p>Are you sure you want to delete this item?</p>
  <form method="dialog">
    <button value="no">Cancel</button>
    <button value="yes">Delete</button>
  </form>
</dialog>

<button onclick="showConfirmDialog()">Delete Item</button>

<script>
  const confirmDialog = document.getElementById('confirmDialog');
  
  function showConfirmDialog() {
    confirmDialog.showModal();
  }
  
  confirmDialog.addEventListener('close', () => {
    if (confirmDialog.returnValue === 'yes') {
      console.log('Item deleted');
    } else {
      console.log('Action cancelled');
    }
  });
</script>

<!-- Dialog with backdrop click to close -->
<dialog id="backdropDialog">
  <h2>Click backdrop to close</h2>
  <p>Content here.</p>
  <button onclick="document.getElementById('backdropDialog').close()">Close</button>
</dialog>

<script>
  const backdropDialog = document.getElementById('backdropDialog');
  
  backdropDialog.addEventListener('click', (e) => {
    const rect = backdropDialog.getBoundingClientRect();
    if (
      e.clientX < rect.left ||
      e.clientX > rect.right ||
      e.clientY < rect.top ||
      e.clientY > rect.bottom
    ) {
      backdropDialog.close();
    }
  });
</script>

<!-- Prevent Esc key closing (for critical dialogs) -->
<dialog id="criticalDialog">
  <h2>Critical Action Required</h2>
  <p>You must make a choice.</p>
  <button onclick="document.getElementById('criticalDialog').close('done')">I Understand</button>
</dialog>

<script>
  const criticalDialog = document.getElementById('criticalDialog');
  
  criticalDialog.addEventListener('cancel', (e) => {
    e.preventDefault(); // Prevent Esc from closing
  });
</script>

<!-- Styled dialog -->
<style>
  dialog {
    border: none;
    border-radius: 8px;
    padding: 20px;
    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
    max-width: 500px;
  }
  
  dialog::backdrop {
    background-color: rgba(0, 0, 0, 0.5);
    backdrop-filter: blur(3px);
  }
  
  dialog h2 {
    margin-top: 0;
  }
  
  dialog button {
    margin: 5px;
    padding: 10px 20px;
  }
</style>
Warning: Use showModal() for true modal behavior (backdrop + focus trap + Esc). show() opens dialog without modal behavior. Forms with method="dialog" automatically close the dialog on submit and set returnValue to the clicked button's value.

10.4 Progress and Meter Indicators

Element Purpose Use Case Value Type
<progress> Show task progress File upload, loading, completion % Dynamic (0 to max)
<meter> Show measurement in range Disk usage, ratings, gauge Static measurement
Progress Attributes:
Attribute Purpose
value Current progress (0 to max)
max Maximum value (default: 1)

Note: Without value, shows indeterminate state.

Meter Attributes

Attribute Purpose
value Current value
min Minimum value (default: 0)
max Maximum value (default: 1)
low Low threshold
high High threshold
optimum Optimal value

Example: Progress and meter elements

<!-- Progress bar (determinate) -->
<label for="fileProgress">File Upload:</label>
<progress id="fileProgress" value="70" max="100">70%</progress>
<span>70%</span>

<!-- Progress bar (indeterminate) -->
<label>Loading...</label>
<progress></progress>

<!-- Updating progress with JavaScript -->
<progress id="dynamicProgress" value="0" max="100"></progress>
<button onclick="updateProgress()">Start Progress</button>

<script>
  function updateProgress() {
    const progress = document.getElementById('dynamicProgress');
    let value = 0;
    
    const interval = setInterval(() => {
      value += 10;
      progress.value = value;
      
      if (value >= 100) {
        clearInterval(interval);
        alert('Complete!');
      }
    }, 500);
  }
</script>

<!-- Meter: Disk usage (low is bad) -->
<label>Disk Usage:</label>
<meter value="70" min="0" max="100" 
       low="30" high="80" optimum="20">
  70%
</meter>
<span>70 GB of 100 GB used</span>

<!-- Meter: Battery level (high is good) -->
<label>Battery:</label>
<meter value="85" min="0" max="100"
       low="20" high="80" optimum="100">
  85%
</meter>

<!-- Meter: Temperature (middle is optimal) -->
<label>Temperature:</label>
<meter value="72" min="32" max="212"
       low="60" high="80" optimum="70">
  72°F
</meter>

<!-- Rating meter -->
<label>Product Rating:</label>
<meter value="4.5" min="0" max="5"
       low="2" high="4" optimum="5">
  4.5 out of 5
</meter>

<!-- Multiple progress bars -->
<div>
  <label>HTML Skills:</label>
  <progress value="90" max="100"></progress> 90%
</div>
<div>
  <label>CSS Skills:</label>
  <progress value="85" max="100"></progress> 85%
</div>
<div>
  <label>JavaScript Skills:</label>
  <progress value="75" max="100"></progress> 75%
</div>

<!-- Styled progress and meter -->
<style>
  progress, meter {
    width: 200px;
    height: 20px;
    border-radius: 10px;
  }
  
  /* Progress bar styling */
  progress {
    appearance: none;
  }
  
  progress::-webkit-progress-bar {
    background-color: #f0f0f0;
    border-radius: 10px;
  }
  
  progress::-webkit-progress-value {
    background-color: #4caf50;
    border-radius: 10px;
    transition: width 0.3s;
  }
  
  progress::-moz-progress-bar {
    background-color: #4caf50;
    border-radius: 10px;
  }
  
  /* Meter styling */
  meter {
    appearance: none;
  }
  
  meter::-webkit-meter-bar {
    background-color: #f0f0f0;
    border-radius: 10px;
  }
  
  /* Green when optimal */
  meter::-webkit-meter-optimum-value {
    background-color: #4caf50;
  }
  
  /* Yellow when suboptimal */
  meter::-webkit-meter-suboptimum-value {
    background-color: #ffc107;
  }
  
  /* Red when sub-suboptimal */
  meter::-webkit-meter-even-less-good-value {
    background-color: #f44336;
  }
</style>
Note: Use <progress> for tasks that change over time (uploads, downloads). Use <meter> for static measurements (disk usage, ratings). Meter automatically colors based on low, high, and optimum values.

10.5 Content Editable and Rich Text Editing

Attribute Values Behavior Use Case
contenteditable true, false, plaintext-only Makes element editable Inline editing, WYSIWYG editors
spellcheck true, false Enable/disable spell check Control spell checking

Document.execCommand (deprecated)

Command Action
bold Toggle bold
italic Toggle italic
underline Toggle underline
formatBlock Change block type
insertHTML Insert HTML
createLink Create hyperlink

Selection API (modern)

Method/Property Purpose
getSelection() Get current selection
getRangeAt() Get range object
insertNode() Insert node at selection
deleteContents() Delete selected content

Example: Content editable implementations

<!-- Basic contenteditable -->
<div contenteditable="true" style="border: 1px solid #ccc; padding: 10px;">
  Click here to edit this text.
</div>

<!-- Plaintext only (no formatting) -->
<div contenteditable="plaintext-only" style="border: 1px solid #ccc; padding: 10px;">
  This can only be edited as plain text.
</div>

<!-- Editable with spell check disabled -->
<div contenteditable="true" spellcheck="false">
  Code example without spell checking.
</div>

<!-- Simple WYSIWYG editor -->
<div id="toolbar">
  <button onclick="document.execCommand('bold')"><b>B</b></button>
  <button onclick="document.execCommand('italic')"><i>I</i></button>
  <button onclick="document.execCommand('underline')"><u>U</u></button>
  <button onclick="document.execCommand('insertOrderedList')">OL</button>
  <button onclick="document.execCommand('insertUnorderedList')">UL</button>
  <button onclick="insertLink()">Link</button>
</div>

<div id="editor" contenteditable="true" 
     style="border: 1px solid #ccc; min-height: 200px; padding: 10px;">
  Start typing here...
</div>

<script>
  function insertLink() {
    const url = prompt('Enter URL:');
    if (url) {
      document.execCommand('createLink', false, url);
    }
  }
</script>

<!-- Save and restore content -->
<div id="editableDiv" contenteditable="true" 
     style="border: 1px solid #ccc; padding: 10px;">
  Edit this content and save it.
</div>

<button onclick="saveContent()">Save</button>
<button onclick="loadContent()">Load</button>
<button onclick="clearContent()">Clear</button>

<script>
  function saveContent() {
    const content = document.getElementById('editableDiv').innerHTML;
    localStorage.setItem('savedContent', content);
    alert('Content saved!');
  }
  
  function loadContent() {
    const content = localStorage.getItem('savedContent');
    if (content) {
      document.getElementById('editableDiv').innerHTML = content;
    } else {
      alert('No saved content found.');
    }
  }
  
  function clearContent() {
    document.getElementById('editableDiv').innerHTML = '';
  }
</script>

<!-- Track changes -->
<div id="trackedEditor" contenteditable="true"
     style="border: 1px solid #ccc; padding: 10px; min-height: 100px;">
  Edit me...
</div>
<p>Character count: <span id="charCount">0</span></p>

<script>
  const trackedEditor = document.getElementById('trackedEditor');
  const charCount = document.getElementById('charCount');
  
  trackedEditor.addEventListener('input', () => {
    const text = trackedEditor.textContent;
    charCount.textContent = text.length;
  });
</script>

<!-- Modern Selection API usage -->
<div id="modernEditor" contenteditable="true"
     style="border: 1px solid #ccc; padding: 10px; min-height: 100px;">
  Select text and use buttons below.
</div>

<button onclick="makeSelectionBold()">Make Bold</button>
<button onclick="wrapInSpan()">Wrap in Span</button>

<script>
  function makeSelectionBold() {
    const selection = window.getSelection();
    if (selection.rangeCount > 0) {
      const range = selection.getRangeAt(0);
      const bold = document.createElement('strong');
      range.surroundContents(bold);
    }
  }
  
  function wrapInSpan() {
    const selection = window.getSelection();
    if (selection.rangeCount > 0) {
      const range = selection.getRangeAt(0);
      const span = document.createElement('span');
      span.style.backgroundColor = 'yellow';
      range.surroundContents(span);
    }
  }
</script>

<!-- Prevent certain keys -->
<div contenteditable="true"
     onkeydown="return event.key !== 'Enter'"
     style="border: 1px solid #ccc; padding: 10px;">
  Single line only (Enter disabled)
</div>
Warning: document.execCommand() is deprecated but still widely used. For new projects, use the Selection API and DOM manipulation. Always sanitize user input from contenteditable elements to prevent XSS attacks.

10.6 Drag and Drop API Integration

Attribute/Event Purpose Applied To Required
draggable Make element draggable Any element Yes (for non-default draggable)
dragstart Drag begins Draggable element Set data
drag While dragging Draggable element Optional
dragend Drag finished Draggable element Cleanup
dragenter Drag enters drop zone Drop target Visual feedback
dragover Drag over drop zone Drop target preventDefault() required
dragleave Drag leaves drop zone Drop target Remove feedback
drop Item dropped Drop target Handle drop

DataTransfer Methods

Method Purpose
setData(type, data) Set drag data
getData(type) Get drag data
clearData() Clear drag data
setDragImage() Custom drag preview

EffectAllowed Values

Value Meaning
copy Copy operation
move Move operation
link Link operation
copyMove Copy or move
all Any operation

Example: Drag and drop implementations

<!-- Basic drag and drop -->
<div id="draggable" draggable="true"
     style="width: 100px; height: 100px; background: #4caf50; color: white;
            display: flex; align-items: center; justify-content: center; cursor: move;">
  Drag Me
</div>

<div id="dropzone"
     style="width: 300px; height: 200px; border: 2px dashed #ccc;
            margin-top: 20px; padding: 20px;">
  Drop Here
</div>

<script>
  const draggable = document.getElementById('draggable');
  const dropzone = document.getElementById('dropzone');
  
  // Drag events
  draggable.addEventListener('dragstart', (e) => {
    e.dataTransfer.setData('text/plain', 'Dragged element');
    e.dataTransfer.effectAllowed = 'move';
    draggable.style.opacity = '0.5';
  });
  
  draggable.addEventListener('dragend', (e) => {
    draggable.style.opacity = '1';
  });
  
  // Drop zone events
  dropzone.addEventListener('dragover', (e) => {
    e.preventDefault(); // Required to allow drop
    e.dataTransfer.dropEffect = 'move';
    dropzone.style.backgroundColor = '#f0f0f0';
  });
  
  dropzone.addEventListener('dragleave', (e) => {
    dropzone.style.backgroundColor = '';
  });
  
  dropzone.addEventListener('drop', (e) => {
    e.preventDefault();
    const data = e.dataTransfer.getData('text/plain');
    dropzone.appendChild(draggable);
    dropzone.style.backgroundColor = '';
  });
</script>

<!-- Sortable list -->
<ul id="sortableList" style="list-style: none; padding: 0;">
  <li draggable="true" style="padding: 10px; margin: 5px; background: #f0f0f0; cursor: move;">Item 1</li>
  <li draggable="true" style="padding: 10px; margin: 5px; background: #f0f0f0; cursor: move;">Item 2</li>
  <li draggable="true" style="padding: 10px; margin: 5px; background: #f0f0f0; cursor: move;">Item 3</li>
  <li draggable="true" style="padding: 10px; margin: 5px; background: #f0f0f0; cursor: move;">Item 4</li>
</ul>

<script>
  const list = document.getElementById('sortableList');
  let draggedItem = null;
  
  list.addEventListener('dragstart', (e) => {
    draggedItem = e.target;
    e.dataTransfer.effectAllowed = 'move';
    e.target.style.opacity = '0.5';
  });
  
  list.addEventListener('dragend', (e) => {
    e.target.style.opacity = '1';
  });
  
  list.addEventListener('dragover', (e) => {
    e.preventDefault();
    const afterElement = getDragAfterElement(list, e.clientY);
    if (afterElement == null) {
      list.appendChild(draggedItem);
    } else {
      list.insertBefore(draggedItem, afterElement);
    }
  });
  
  function getDragAfterElement(container, y) {
    const draggableElements = [...container.querySelectorAll('li:not(.dragging)')];
    
    return draggableElements.reduce((closest, child) => {
      const box = child.getBoundingClientRect();
      const offset = y - box.top - box.height / 2;
      
      if (offset < 0 && offset > closest.offset) {
        return { offset: offset, element: child };
      } else {
        return closest;
      }
    }, { offset: Number.NEGATIVE_INFINITY }).element;
  }
</script>

<!-- File drop zone -->
<div id="fileDropzone"
     style="width: 400px; height: 200px; border: 2px dashed #ccc;
            display: flex; align-items: center; justify-content: center;
            font-size: 18px; color: #999;">
  Drop files here
</div>
<div id="fileList"></div>

<script>
  const fileDropzone = document.getElementById('fileDropzone');
  const fileList = document.getElementById('fileList');
  
  fileDropzone.addEventListener('dragover', (e) => {
    e.preventDefault();
    fileDropzone.style.borderColor = '#4caf50';
    fileDropzone.style.backgroundColor = '#f0f9f0';
  });
  
  fileDropzone.addEventListener('dragleave', (e) => {
    fileDropzone.style.borderColor = '#ccc';
    fileDropzone.style.backgroundColor = '';
  });
  
  fileDropzone.addEventListener('drop', (e) => {
    e.preventDefault();
    fileDropzone.style.borderColor = '#ccc';
    fileDropzone.style.backgroundColor = '';
    
    const files = e.dataTransfer.files;
    fileList.innerHTML = '<h3>Dropped Files:</h3>';
    
    for (let i = 0; i < files.length; i++) {
      const file = files[i];
      fileList.innerHTML += `<p>${file.name} (${file.size} bytes)</p>`;
    }
  });
</script>

<!-- Custom drag image -->
<div id="customDrag" draggable="true"
     style="width: 100px; height: 100px; background: #2196f3; color: white;
            display: flex; align-items: center; justify-content: center; cursor: move;">
  Custom Preview
</div>

<script>
  const customDrag = document.getElementById('customDrag');
  
  customDrag.addEventListener('dragstart', (e) => {
    const dragImage = document.createElement('div');
    dragImage.style.width = '150px';
    dragImage.style.height = '50px';
    dragImage.style.backgroundColor = '#ff5722';
    dragImage.style.color = 'white';
    dragImage.style.display = 'flex';
    dragImage.style.alignItems = 'center';
    dragImage.style.justifyContent = 'center';
    dragImage.textContent = 'Custom Drag Image';
    document.body.appendChild(dragImage);
    
    e.dataTransfer.setDragImage(dragImage, 75, 25);
    
    setTimeout(() => {
      document.body.removeChild(dragImage);
    }, 0);
  });
</script>
Note: Must call preventDefault() in dragover event to allow drop. Use dataTransfer.files to access dropped files. Images, links, and text are draggable by default; other elements need draggable="true".

Section 10 Key Takeaways

  • Always specify type attribute on buttons (submit, button, reset) to avoid unexpected behavior
  • Use <button> over <input type="button"> for more flexible content (HTML, icons)
  • Details/summary provides native accordion without JavaScript; use name attribute for exclusive groups
  • Use showModal() for true modal dialogs (backdrop + focus trap); show() for non-modal
  • Forms with method="dialog" automatically close dialog and set returnValue
  • Use <progress> for dynamic tasks, <meter> for static measurements
  • document.execCommand() is deprecated; use Selection API for modern rich text editing
  • Always sanitize contenteditable content to prevent XSS attacks
  • Must call preventDefault() in dragover event to enable dropping

11. HTML5 APIs and Advanced Features

11.1 Web Storage (localStorage, sessionStorage)

Storage Type Scope Lifetime Capacity Use Cases
localStorage Origin (protocol + domain + port) Permanent (until cleared) ~5-10MB per origin User preferences, app state, cached data
sessionStorage Tab/window per origin Until tab/window closed ~5-10MB per origin Form data, session state, temp data
Cookies Origin (+ path config) Configurable expiry ~4KB per cookie Auth tokens, tracking, server access
Method/Property Syntax Description Returns
setItem() storage.setItem(key, value) Store key-value pair (strings only) undefined
getItem() storage.getItem(key) Retrieve value by key string | null
removeItem() storage.removeItem(key) Delete specific key-value pair undefined
clear() storage.clear() Remove all items from storage undefined
key() storage.key(index) Get key name at index position string | null
length storage.length Number of stored items number

Example: Web Storage operations with JSON serialization

// Store simple values (localStorage)
localStorage.setItem('username', 'john_doe');
localStorage.setItem('theme', 'dark');

// Retrieve values
const username = localStorage.getItem('username'); // "john_doe"
const theme = localStorage.getItem('theme'); // "dark"

// Store complex objects (must stringify)
const user = {
  id: 123,
  name: 'John Doe',
  preferences: { theme: 'dark', lang: 'en' }
};
localStorage.setItem('user', JSON.stringify(user));

// Retrieve and parse objects
const storedUser = JSON.parse(localStorage.getItem('user'));
console.log(storedUser.preferences.theme); // "dark"

// Remove specific item
localStorage.removeItem('theme');

// Clear all storage
localStorage.clear();

// sessionStorage (same API, different lifetime)
sessionStorage.setItem('tempData', 'session-only');

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

// Storage event listener (fires on other tabs/windows)
window.addEventListener('storage', (e) => {
  console.log('Key:', e.key);
  console.log('Old value:', e.oldValue);
  console.log('New value:', e.newValue);
  console.log('URL:', e.url);
  console.log('Storage area:', e.storageArea);
});
Security Warning: Never store sensitive data (passwords, credit cards, tokens) in localStorage/sessionStorage - it's accessible via JavaScript and vulnerable to XSS attacks. Data is stored as plain text. Use httpOnly cookies or secure server-side sessions for sensitive information.
Best Practices: Always use try-catch for storage operations (can fail if quota exceeded or disabled). Use JSON for objects. Implement versioning for data schema changes. Clear old data periodically. Consider IndexedDB for large datasets (>10MB). Use compression for large strings.

11.2 Geolocation API Integration

Method Syntax Description Requires Permission
getCurrentPosition() navigator.geolocation.getCurrentPosition(success, error, options) Get one-time location ✅ Yes
watchPosition() navigator.geolocation.watchPosition(success, error, options) Monitor location changes ✅ Yes
clearWatch() navigator.geolocation.clearWatch(watchId) Stop monitoring location ❌ No
Position Property Type Description Always Available
coords.latitude number Latitude in decimal degrees (-90 to 90) ✅ Yes
coords.longitude number Longitude in decimal degrees (-180 to 180) ✅ Yes
coords.accuracy number Accuracy in meters ✅ Yes
coords.altitude number | null Height above sea level in meters ❌ No (GPS only)
coords.altitudeAccuracy number | null Altitude accuracy in meters ❌ No
coords.heading number | null Direction in degrees (0-360, 0=North) ❌ No (moving only)
coords.speed number | null Speed in meters per second ❌ No (moving only)
timestamp DOMTimeStamp Time when position was acquired ✅ Yes
Options Property Type Default Description
enableHighAccuracy boolean false Use GPS (slower, more battery, accurate)
timeout number Infinity Max wait time in milliseconds
maximumAge number 0 Accept cached position (milliseconds old)

Example: Geolocation with error handling and options

// Check if geolocation is supported
if ('geolocation' in navigator) {
  // Get current position (one-time)
  navigator.geolocation.getCurrentPosition(
    // Success callback
    (position) => {
      const lat = position.coords.latitude;
      const lng = position.coords.longitude;
      const accuracy = position.coords.accuracy;
      
      console.log(`Location: ${lat}, ${lng}`);
      console.log(`Accuracy: ${accuracy} meters`);
      
      // Use coordinates (e.g., show on map)
      showOnMap(lat, lng);
    },
    // Error callback
    (error) => {
      switch(error.code) {
        case error.PERMISSION_DENIED:
          console.error('User denied geolocation');
          break;
        case error.POSITION_UNAVAILABLE:
          console.error('Location unavailable');
          break;
        case error.TIMEOUT:
          console.error('Request timeout');
          break;
        default:
          console.error('Unknown error');
      }
    },
    // Options
    {
      enableHighAccuracy: true,  // Use GPS
      timeout: 5000,             // 5 second timeout
      maximumAge: 60000          // Accept 1-minute-old cache
    }
  );
  
  // Watch position (continuous monitoring)
  const watchId = navigator.geolocation.watchPosition(
    (position) => {
      updateMap(position.coords.latitude, position.coords.longitude);
      
      // Check if device is moving
      if (position.coords.speed !== null) {
        console.log(`Speed: ${position.coords.speed} m/s`);
      }
      if (position.coords.heading !== null) {
        console.log(`Heading: ${position.coords.heading}°`);
      }
    },
    (error) => console.error(error),
    { enableHighAccuracy: true }
  );
  
  // Stop watching after 10 seconds
  setTimeout(() => {
    navigator.geolocation.clearWatch(watchId);
    console.log('Stopped watching position');
  }, 10000);
  
} else {
  console.error('Geolocation not supported');
}
Browser Support: All modern browsers - Requires HTTPS (except localhost). Mobile devices generally more accurate than desktops. User must grant permission. GPS (enableHighAccuracy) drains battery significantly.

11.3 File API and File Handling

Interface Purpose Key Properties/Methods
File Represents a file name, size, type, lastModified
FileList Collection of File objects length, indexed access
FileReader Read file contents asynchronously readAsText(), readAsDataURL(), readAsArrayBuffer()
Blob Raw binary data size, type, slice(), text()
URL.createObjectURL() Create temporary URL for file/blob Returns blob: URL for preview/download
FileReader Method Result Format Use Case
readAsText(file, encoding) Plain text string Read text/JSON/CSV files
readAsDataURL(file) Base64 data URL Image previews, inline embedding
readAsArrayBuffer(file) ArrayBuffer (binary) Binary processing, encryption, WebGL
readAsBinaryString(file) Binary string DEPRECATED Use readAsArrayBuffer instead
FileReader Event When Fired Event Data
loadstart Reading starts ProgressEvent
progress During reading (periodic) loaded, total bytes
load Reading completed successfully target.result contains data
error Reading failed target.error
loadend Reading finished (success or failure) Always fires after load/error
abort Reading cancelled via abort() AbortEvent

Example: File input with preview and validation

<input type="file" id="fileInput" multiple accept="image/*">
<div id="preview"></div>

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

fileInput.addEventListener('change', (e) => {
  const files = e.target.files; // FileList
  
  // Validate and process each file
  Array.from(files).forEach(file => {
    // File properties
    console.log('Name:', file.name);
    console.log('Size:', file.size, 'bytes');
    console.log('Type:', file.type);
    console.log('Modified:', new Date(file.lastModified));
    
    // Validation
    const maxSize = 5 * 1024 * 1024; // 5MB
    if (file.size > maxSize) {
      alert(`${file.name} is too large`);
      return;
    }
    
    if (!file.type.startsWith('image/')) {
      alert(`${file.name} is not an image`);
      return;
    }
    
    // Method 1: FileReader for preview
    const reader = new FileReader();
    
    reader.onload = (e) => {
      const img = document.createElement('img');
      img.src = e.target.result; // Data URL
      img.style.maxWidth = '200px';
      preview.appendChild(img);
    };
    
    reader.onerror = () => {
      console.error('Error reading file');
    };
    
    reader.onprogress = (e) => {
      if (e.lengthComputable) {
        const percent = (e.loaded / e.total) * 100;
        console.log(`Loading: ${percent.toFixed(0)}%`);
      }
    };
    
    reader.readAsDataURL(file); // Start reading
    
    // Method 2: Object URL (more efficient for large files)
    const objectURL = URL.createObjectURL(file);
    const img2 = document.createElement('img');
    img2.src = objectURL;
    preview.appendChild(img2);
    
    // Important: Revoke object URL when done to free memory
    img2.onload = () => URL.revokeObjectURL(objectURL);
  });
});
</script>

Example: Read text file and parse JSON

<input type="file" id="jsonFile" accept=".json,.txt">

<script>
document.getElementById('jsonFile').addEventListener('change', (e) => {
  const file = e.target.files[0];
  
  if (file) {
    const reader = new FileReader();
    
    reader.onload = (e) => {
      try {
        // Parse JSON content
        const data = JSON.parse(e.target.result);
        console.log('Parsed JSON:', data);
      } catch (error) {
        console.error('Invalid JSON:', error);
      }
    };
    
    reader.readAsText(file, 'UTF-8');
  }
});

// Alternative: Modern Promise-based approach
async function readFileAsync(file) {
  try {
    const text = await file.text(); // Built-in method
    const data = JSON.parse(text);
    return data;
  } catch (error) {
    console.error('Error:', error);
  }
}
</script>

Example: Drag and drop file upload

<div id="dropZone" style="border: 2px dashed #ccc; padding: 50px;">
  Drop files here
</div>

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

dropZone.addEventListener('dragover', (e) => {
  e.preventDefault();
  dropZone.style.background = '#e3f2fd';
});

dropZone.addEventListener('dragleave', () => {
  dropZone.style.background = '';
});

dropZone.addEventListener('drop', (e) => {
  e.preventDefault();
  dropZone.style.background = '';
  
  const files = e.dataTransfer.files;
  
  // Process dropped files
  Array.from(files).forEach(file => {
    console.log('Dropped:', file.name);
    
    // Upload to server
    uploadFile(file);
  });
});

function uploadFile(file) {
  const formData = new FormData();
  formData.append('file', file);
  
  fetch('/upload', {
    method: 'POST',
    body: formData
  })
  .then(response => response.json())
  .then(data => console.log('Uploaded:', data))
  .catch(error => console.error('Upload failed:', error));
}
</script>
Security Note: Always validate file type and size on both client and server. Never trust file extensions - check MIME type and magic numbers. Sanitize file names before saving. Use virus scanning for user uploads. Limit file sizes to prevent DoS attacks.

11.4 History API and Navigation Management

Method Syntax Description Updates URL
pushState() history.pushState(state, title, url) Add new history entry (creates new entry in stack) ✅ Yes
replaceState() history.replaceState(state, title, url) Modify current history entry (no new entry) ✅ Yes
back() history.back() Navigate to previous page (like browser back) ✅ Yes
forward() history.forward() Navigate to next page (like browser forward) ✅ Yes
go() history.go(delta) Navigate by offset (-1 = back, 1 = forward, 0 = reload) ✅ Yes
Property Type Description Writable
history.state any State object of current history entry ❌ Read-only
history.length number Number of entries in session history ❌ Read-only
history.scrollRestoration 'auto' | 'manual' Control scroll position restoration on navigation ✅ Yes
Event When Fired Event Properties
popstate User navigates (back/forward), or history.back/forward/go() called event.state contains state object
hashchange URL hash (#) changes oldURL, newURL

Example: Single Page Application (SPA) routing with History API

// SPA Router implementation
const routes = {
  '/': renderHome,
  '/about': renderAbout,
  '/contact': renderContact
};

// Navigate to new page without reload
function navigateTo(path) {
  // Add to history
  history.pushState({ path }, '', path);
  
  // Render new content
  renderPage(path);
}

// Render page based on path
function renderPage(path) {
  const render = routes[path] || render404;
  render();
  
  // Update active nav links
  document.querySelectorAll('nav a').forEach(link => {
    link.classList.toggle('active', link.pathname === path);
  });
}

// Handle browser back/forward
window.addEventListener('popstate', (e) => {
  const path = e.state?.path || '/';
  renderPage(path);
});

// Intercept link clicks
document.addEventListener('click', (e) => {
  if (e.target.matches('a[href^="/"]')) {
    e.preventDefault();
    navigateTo(e.target.pathname);
  }
});

// Example: Update URL without navigation
function updateFilters(filters) {
  const url = new URL(window.location);
  url.searchParams.set('filter', filters);
  
  // Replace current history entry (no new back button entry)
  history.replaceState({ filters }, '', url);
}

// Example: Handle state changes
window.addEventListener('popstate', (e) => {
  console.log('State:', e.state);
  console.log('URL:', location.pathname);
  
  if (e.state?.filters) {
    applyFilters(e.state.filters);
  }
});

// Control scroll restoration
history.scrollRestoration = 'manual'; // Manually handle scroll position

// Save scroll position before navigation
window.addEventListener('beforeunload', () => {
  const scrollPos = { x: window.scrollX, y: window.scrollY };
  history.replaceState({ scrollPos }, '');
});

// Restore scroll position
window.addEventListener('popstate', (e) => {
  if (e.state?.scrollPos) {
    window.scrollTo(e.state.scrollPos.x, e.state.scrollPos.y);
  }
});
Important: pushState() and replaceState() don't trigger popstate event. Only user navigation (back/forward buttons) or history.back/forward/go() trigger popstate. The title parameter is currently ignored by most browsers. URLs must be same-origin (protocol + domain + port).

11.5 Web Workers and Shared Workers

Worker Type Scope Communication Use Case
Web Worker (Dedicated) Single page/script postMessage (1-to-1) Heavy computation, data processing
Shared Worker Multiple pages/tabs (same origin) postMessage via ports (many-to-1) Shared state, cross-tab communication
Service Worker Application-level (all pages) Events, postMessage Offline support, caching, push notifications
Worker API Syntax Description
new Worker() const worker = new Worker('worker.js') Create dedicated worker from script file
postMessage() worker.postMessage(data) Send data to worker (structured clone)
onmessage worker.onmessage = (e) => {} Receive messages from worker
onerror worker.onerror = (e) => {} Handle worker errors
terminate() worker.terminate() Immediately stop worker (from main thread)
close() self.close() Stop worker (from inside worker)
Worker Context Available Not Available
Global Scope self, importScripts(), navigator, location window, document, parent
APIs fetch, setTimeout, setInterval, IndexedDB, Web Crypto DOM manipulation, localStorage, sessionStorage
Data Transfer Structured clone, Transferable objects (ArrayBuffer) Functions, DOM nodes, native objects with methods

Example: Web Worker for heavy computation

<!-- Main page (main.html) -->
<script>
// Create worker
const worker = new Worker('worker.js');

// Send data to worker
worker.postMessage({ 
  operation: 'fibonacci',
  number: 40 
});

// Receive results from worker
worker.onmessage = (e) => {
  console.log('Result from worker:', e.data);
  // { result: 102334155, time: 1523 }
};

// Handle worker errors
worker.onerror = (e) => {
  console.error('Worker error:', e.message);
  console.error('File:', e.filename, 'Line:', e.lineno);
};

// Send multiple messages
worker.postMessage({ operation: 'sort', data: [5, 2, 8, 1, 9] });

// Terminate worker when done
setTimeout(() => {
  worker.terminate();
  console.log('Worker terminated');
}, 5000);
</script>

<!-- Worker file (worker.js) -->
<script>
// Listen for messages from main thread
self.onmessage = (e) => {
  const { operation, number, data } = e.data;
  const startTime = performance.now();
  
  let result;
  
  switch(operation) {
    case 'fibonacci':
      result = fibonacci(number);
      break;
    case 'sort':
      result = data.sort((a, b) => a - b);
      break;
    default:
      throw new Error('Unknown operation');
  }
  
  const time = performance.now() - startTime;
  
  // Send result back to main thread
  self.postMessage({ result, time });
};

function fibonacci(n) {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
}

// Import other scripts
importScripts('math-utils.js', 'helpers.js');

// Handle errors
self.onerror = (e) => {
  console.error('Error in worker:', e);
};

// Worker can close itself
// self.close();
</script>

Example: Shared Worker for cross-tab communication

<!-- Main page (any tab) -->
<script>
// Create/connect to shared worker
const sharedWorker = new SharedWorker('shared.js');

// Get message port
const port = sharedWorker.port;

// Start the port
port.start();

// Send message
port.postMessage({ type: 'subscribe', channel: 'chat' });

// Receive messages
port.onmessage = (e) => {
  console.log('Message from shared worker:', e.data);
};

// Send broadcast to all connected tabs
port.postMessage({
  type: 'broadcast',
  message: 'Hello from tab ' + Date.now()
});
</script>

<!-- Shared Worker (shared.js) -->
<script>
const connections = [];

// Handle new connections
self.onconnect = (e) => {
  const port = e.ports[0];
  connections.push(port);
  
  console.log('New connection. Total:', connections.length);
  
  // Listen for messages from this port
  port.onmessage = (e) => {
    const { type, message, channel } = e.data;
    
    if (type === 'broadcast') {
      // Send to all connected tabs
      connections.forEach(p => {
        if (p !== port) { // Don't send back to sender
          p.postMessage(message);
        }
      });
    } else if (type === 'subscribe') {
      port.postMessage({ status: 'subscribed', channel });
    }
  };
  
  // Start the port
  port.start();
  
  // Send welcome message
  port.postMessage({ type: 'welcome', connections: connections.length });
};
</script>
Performance Tips: Workers run on separate threads - perfect for CPU-intensive tasks (parsing, image processing, data analysis). Use Transferable objects (ArrayBuffer, MessagePort, ImageBitmap) to transfer large data without copying. Workers have startup cost - reuse workers instead of creating many short-lived ones.

11.6 Service Workers and Offline Functionality

Feature Description Requirement
Offline Support Cache assets and serve when offline HTTPS required (except localhost)
Background Sync Defer actions until network available Service Worker API
Push Notifications Receive server push messages User permission + HTTPS
Cache Strategies Network first, Cache first, Stale-while-revalidate Cache API
Service Worker Lifecycle Event When Fired Actions
1. Registration register() navigator.serviceWorker.register() called Download and parse worker script
2. Installation install Worker first installed or updated Cache static assets
3. Activation activate Worker becomes active (old versions removed) Clean up old caches
4. Operation fetch, message Network requests, messages from pages Intercept requests, serve from cache
5. Termination - Browser terminates idle worker Auto-restart on next event
Cache Strategy Approach Best For
Cache First Try cache, fallback to network Static assets (CSS, JS, images)
Network First Try network, fallback to cache API calls, dynamic content
Stale While Revalidate Serve cache, update in background Frequently updated content
Network Only Always fetch from network Real-time data, analytics
Cache Only Only serve from cache Offline-first apps

Example: Service Worker registration and basic caching

<!-- Main page (index.html) -->
<script>
// Check if service workers are supported
if ('serviceWorker' in navigator) {
  // Register service worker
  navigator.serviceWorker.register('/service-worker.js')
    .then(registration => {
      console.log('SW registered:', registration);
      console.log('Scope:', registration.scope);
      
      // Check for updates
      registration.update();
      
      // Listen for updates
      registration.addEventListener('updatefound', () => {
        const newWorker = registration.installing;
        console.log('New service worker installing');
        
        newWorker.addEventListener('statechange', () => {
          if (newWorker.state === 'installed') {
            if (navigator.serviceWorker.controller) {
              // New worker available, prompt user to reload
              console.log('New version available!');
              showUpdateNotification();
            } else {
              console.log('Content cached for offline use');
            }
          }
        });
      });
    })
    .catch(error => {
      console.error('SW registration failed:', error);
    });
  
  // Listen for messages from service worker
  navigator.serviceWorker.addEventListener('message', (e) => {
    console.log('Message from SW:', e.data);
  });
  
  // Send message to service worker
  navigator.serviceWorker.ready.then(registration => {
    registration.active.postMessage({ type: 'SKIP_WAITING' });
  });
}
</script>

Example: Service Worker with caching strategies (service-worker.js)

const CACHE_NAME = 'my-app-v1';
const STATIC_CACHE = 'static-v1';
const DYNAMIC_CACHE = 'dynamic-v1';

// Files to cache immediately
const STATIC_ASSETS = [
  '/',
  '/index.html',
  '/styles.css',
  '/app.js',
  '/logo.png',
  '/offline.html'
];

// Install event - cache static assets
self.addEventListener('install', (e) => {
  console.log('Service Worker installing...');
  
  e.waitUntil(
    caches.open(STATIC_CACHE)
      .then(cache => {
        console.log('Caching static assets');
        return cache.addAll(STATIC_ASSETS);
      })
      .then(() => {
        // Skip waiting to activate immediately
        return self.skipWaiting();
      })
  );
});

// Activate event - clean up old caches
self.addEventListener('activate', (e) => {
  console.log('Service Worker activating...');
  
  e.waitUntil(
    caches.keys()
      .then(cacheNames => {
        return Promise.all(
          cacheNames
            .filter(name => name !== STATIC_CACHE && name !== DYNAMIC_CACHE)
            .map(name => {
              console.log('Deleting old cache:', name);
              return caches.delete(name);
            })
        );
      })
      .then(() => {
        // Take control of all pages immediately
        return self.clients.claim();
      })
  );
});

// Fetch event - implement caching strategies
self.addEventListener('fetch', (e) => {
  const { request } = e;
  const url = new URL(request.url);
  
  // Strategy 1: Cache First (for static assets)
  if (request.destination === 'image' || request.destination === 'style' || request.destination === 'script') {
    e.respondWith(
      caches.match(request)
        .then(cached => {
          if (cached) {
            return cached; // Return from cache
          }
          // Not in cache, fetch and cache
          return fetch(request).then(response => {
            return caches.open(DYNAMIC_CACHE).then(cache => {
              cache.put(request, response.clone());
              return response;
            });
          });
        })
        .catch(() => {
          // Return fallback image if offline
          return caches.match('/placeholder.png');
        })
    );
  }
  
  // Strategy 2: Network First (for API calls)
  else if (url.pathname.startsWith('/api/')) {
    e.respondWith(
      fetch(request)
        .then(response => {
          // Cache successful response
          const responseClone = response.clone();
          caches.open(DYNAMIC_CACHE).then(cache => {
            cache.put(request, responseClone);
          });
          return response;
        })
        .catch(() => {
          // Network failed, try cache
          return caches.match(request);
        })
    );
  }
  
  // Strategy 3: Stale While Revalidate (for HTML pages)
  else if (request.destination === 'document') {
    e.respondWith(
      caches.match(request)
        .then(cached => {
          const fetchPromise = fetch(request).then(response => {
            const responseClone = response.clone();
            caches.open(DYNAMIC_CACHE).then(cache => {
              cache.put(request, responseClone);
            });
            return response;
          });
          
          // Return cached version immediately, update in background
          return cached || fetchPromise;
        })
        .catch(() => {
          // Show offline page
          return caches.match('/offline.html');
        })
    );
  }
  
  // Default: Network only
  else {
    e.respondWith(fetch(request));
  }
});

// Message event - handle messages from pages
self.addEventListener('message', (e) => {
  if (e.data.type === 'SKIP_WAITING') {
    self.skipWaiting();
  }
  
  // Send message back to client
  e.ports[0].postMessage({ status: 'received' });
});

// Background Sync (requires registration.sync.register())
self.addEventListener('sync', (e) => {
  if (e.tag === 'sync-data') {
    e.waitUntil(syncDataToServer());
  }
});

// Push notification
self.addEventListener('push', (e) => {
  const data = e.data.json();
  
  e.waitUntil(
    self.registration.showNotification(data.title, {
      body: data.body,
      icon: '/icon.png',
      badge: '/badge.png',
      data: { url: data.url }
    })
  );
});

// Notification click
self.addEventListener('notificationclick', (e) => {
  e.notification.close();
  
  e.waitUntil(
    clients.openWindow(e.notification.data.url)
  );
});
Important Limitations: Service Workers only work over HTTPS (except localhost). Cannot access DOM directly. Runs on separate thread. Cache storage has quota limits (varies by browser). Must handle cache versioning carefully to avoid serving stale content.

Section 11 Key Takeaways

  • localStorage persists across sessions; sessionStorage clears when tab closes - both ~5-10MB limit
  • Always JSON.stringify/parse for storing objects in Web Storage; use try-catch for quota errors
  • Geolocation requires HTTPS and user permission; enableHighAccuracy uses GPS (drains battery)
  • FileReader.readAsDataURL() for image previews; URL.createObjectURL() more efficient for large files
  • Always validate file type and size on both client and server; never trust file extensions
  • history.pushState() creates new entry; replaceState() modifies current; only popstate on back/forward
  • Web Workers run on separate thread - perfect for CPU-intensive tasks; cannot access DOM
  • Use Transferable objects (ArrayBuffer) to transfer large data without copying between worker threads
  • Service Workers enable offline support, caching, and push notifications; HTTPS required
  • Cache strategies: Cache First (static assets), Network First (API), Stale-While-Revalidate (dynamic)

12. Custom Elements and Web Components

12.1 Custom Element Registration and Lifecycle

Element Type Extends Tag Name Use Case
Autonomous Element HTMLElement Must contain hyphen (e.g., my-button) Completely new element with custom behavior
Customized Built-in Specific HTML element (e.g., HTMLButtonElement) Use is attribute on standard element Extend existing element with extra features
Lifecycle Callback When Called Use For
constructor() Element created or upgraded Initialize state, create Shadow DOM, event listeners setup (don't touch attributes/children)
connectedCallback() Element added to DOM Setup, fetch data, start timers, render content (can be called multiple times)
disconnectedCallback() Element removed from DOM Cleanup, remove listeners, cancel timers/requests
attributeChangedCallback() Observed attribute changed React to attribute changes, update UI
adoptedCallback() Element moved to new document Rare - handle document adoption (iframe, document.adoptNode)
Static Property Type Purpose
observedAttributes string[] List of attributes to watch for changes (triggers attributeChangedCallback)

Example: Autonomous custom element with lifecycle

// Define custom element class
class MyCounter extends HTMLElement {
  // Define which attributes to observe
  static get observedAttributes() {
    return ['count', 'step'];
  }
  
  constructor() {
    super(); // Always call first
    
    // Initialize private state
    this._count = 0;
    this._step = 1;
    
    // Create shadow DOM for encapsulation
    this.attachShadow({ mode: 'open' });
    
    // Setup initial structure
    this.shadowRoot.innerHTML = `
      <style>
        :host {
          display: inline-block;
          padding: 10px;
          border: 2px solid #333;
        }
        button {
          margin: 0 5px;
          padding: 5px 10px;
        }
        .count {
          font-size: 24px;
          font-weight: bold;
        }
      </style>
      <div>
        <button class="decrement">-</button>
        <span class="count">0</span>
        <button class="increment">+</button>
      </div>
    `;
    
    // Store references (in shadow DOM)
    this._countDisplay = this.shadowRoot.querySelector('.count');
    this._incrementBtn = this.shadowRoot.querySelector('.increment');
    this._decrementBtn = this.shadowRoot.querySelector('.decrement');
  }
  
  // Called when element added to DOM
  connectedCallback() {
    console.log('Counter connected to DOM');
    
    // Attach event listeners
    this._incrementBtn.addEventListener('click', this._handleIncrement);
    this._decrementBtn.addEventListener('click', this._handleDecrement);
    
    // Read initial attributes
    if (this.hasAttribute('count')) {
      this._count = parseInt(this.getAttribute('count'));
      this._updateDisplay();
    }
    if (this.hasAttribute('step')) {
      this._step = parseInt(this.getAttribute('step'));
    }
  }
  
  // Called when element removed from DOM
  disconnectedCallback() {
    console.log('Counter disconnected from DOM');
    
    // Clean up event listeners
    this._incrementBtn.removeEventListener('click', this._handleIncrement);
    this._decrementBtn.removeEventListener('click', this._handleDecrement);
  }
  
  // Called when observed attribute changes
  attributeChangedCallback(name, oldValue, newValue) {
    console.log(`Attribute ${name} changed from ${oldValue} to ${newValue}`);
    
    if (name === 'count') {
      this._count = parseInt(newValue);
      this._updateDisplay();
    } else if (name === 'step') {
      this._step = parseInt(newValue);
    }
  }
  
  // Private methods (arrow functions to preserve 'this')
  _handleIncrement = () => {
    this._count += this._step;
    this._updateDisplay();
    this._dispatchEvent();
  }
  
  _handleDecrement = () => {
    this._count -= this._step;
    this._updateDisplay();
    this._dispatchEvent();
  }
  
  _updateDisplay() {
    this._countDisplay.textContent = this._count;
  }
  
  _dispatchEvent() {
    // Dispatch custom event
    this.dispatchEvent(new CustomEvent('countchange', {
      detail: { count: this._count },
      bubbles: true,
      composed: true // Allow event to cross shadow DOM boundary
    }));
  }
  
  // Public API (getters/setters)
  get count() {
    return this._count;
  }
  
  set count(value) {
    this.setAttribute('count', value);
  }
  
  get step() {
    return this._step;
  }
  
  set step(value) {
    this.setAttribute('step', value);
  }
  
  // Public methods
  reset() {
    this.count = 0;
  }
}

// Register custom element
customElements.define('my-counter', MyCounter);

// Usage in HTML:
// <my-counter count="5" step="2"></my-counter>

// Usage in JavaScript:
const counter = document.querySelector('my-counter');
counter.addEventListener('countchange', (e) => {
  console.log('Count changed to:', e.detail.count);
});
counter.count = 10; // Set count
counter.reset(); // Call method

Example: Customized built-in element

// Extend existing button element
class FancyButton extends HTMLButtonElement {
  constructor() {
    super();
    this.addEventListener('click', this._addRipple);
  }
  
  connectedCallback() {
    this.style.position = 'relative';
    this.style.overflow = 'hidden';
  }
  
  _addRipple = (e) => {
    const ripple = document.createElement('span');
    ripple.classList.add('ripple');
    ripple.style.left = e.offsetX + 'px';
    ripple.style.top = e.offsetY + 'px';
    this.appendChild(ripple);
    
    setTimeout(() => ripple.remove(), 600);
  }
}

// Register as customized built-in (note the 'extends' option)
customElements.define('fancy-button', FancyButton, { extends: 'button' });

// Usage with 'is' attribute:
// <button is="fancy-button">Click Me</button>

// Or create programmatically:
const btn = document.createElement('button', { is: 'fancy-button' });
btn.textContent = 'Click Me';
document.body.appendChild(btn);
Best Practices: Always call super() first in constructor. Don't access attributes or children in constructor - wait for connectedCallback. Always remove event listeners in disconnectedCallback to prevent memory leaks. Use underscore prefix for private properties/methods.

12.2 Shadow DOM and Encapsulation

Shadow DOM Mode Access from Outside Use Case
open ✅ Accessible via element.shadowRoot Most common - allows external access for testing/styling
closed ❌ Returns null Complete encapsulation (rare, harder to test)
Feature Benefit Example
Style Encapsulation CSS doesn't leak in/out Component styles won't affect page, page styles won't affect component
DOM Encapsulation Internal structure hidden querySelector() from outside can't find shadow DOM elements
:host Selector Style the host element :host { display: block; }
:host() Function Conditional host styling :host(.active) { color: red; }
:host-context() Style based on ancestor :host-context(.dark-theme) { color: white; }
::slotted() Style slotted content ::slotted(p) { margin: 0; }
CSS Custom Properties Behavior Use Case
Inherit into Shadow DOM ✅ Yes Allow external theming via CSS variables
Regular styles ❌ Blocked Component styles are isolated

Example: Shadow DOM with style encapsulation

class StyledCard extends HTMLElement {
  constructor() {
    super();
    
    // Create shadow DOM (open mode)
    const shadow = this.attachShadow({ mode: 'open' });
    
    // Add styles (scoped to shadow DOM)
    shadow.innerHTML = `
      <style>
        /* :host styles the custom element itself */
        :host {
          display: block;
          border: 1px solid var(--card-border, #ddd);
          border-radius: 8px;
          padding: 16px;
          background: var(--card-bg, white);
        }
        
        /* :host() with selector - style host when it has class */
        :host(.featured) {
          border-color: gold;
          border-width: 3px;
        }
        
        /* :host-context() - style based on ancestor */
        :host-context(.dark-theme) {
          background: #333;
          color: white;
        }
        
        /* Regular styles (only affect shadow DOM) */
        h2 {
          margin: 0 0 10px 0;
          color: var(--card-title-color, #333);
        }
        
        p {
          color: #666;
          line-height: 1.5;
        }
        
        /* This won't affect outside elements */
        .button {
          background: blue;
          color: white;
          padding: 8px 16px;
          border: none;
          border-radius: 4px;
        }
      </style>
      
      <div class="card-content">
        <h2><slot name="title">Default Title</slot></h2>
        <p><slot>Default content</slot></p>
        <button class="button">Action</button>
      </div>
    `;
  }
}

customElements.define('styled-card', StyledCard);

// Usage - styles won't leak in or out:
// <style>
//   /* This CSS variable WILL pass through shadow boundary */
//   styled-card {
//     --card-bg: #f0f0f0;
//     --card-title-color: #0066cc;
//   }
//   
//   /* These styles WON'T affect shadow DOM */
//   h2 { color: red; } /* Won't affect h2 in shadow DOM */
//   .button { background: green; } /* Won't affect .button in shadow DOM */
// </style>
//
// <styled-card class="featured">
//   <span slot="title">Card Title</span>
//   This is the card content
// </styled-card>

Example: Access and manipulation

class MyElement extends HTMLElement {
  constructor() {
    super();
    this.shadow = this.attachShadow({ mode: 'open' });
    this.shadow.innerHTML = `
      <div class="inner">Shadow content</div>
    `;
  }
}
customElements.define('my-element', MyElement);

const el = document.querySelector('my-element');

// Access shadow DOM (only works with mode: 'open')
console.log(el.shadowRoot); // ShadowRoot object
console.log(el.shadowRoot.querySelector('.inner')); // <div class="inner">

// From outside, can't access shadow DOM with regular queries
console.log(document.querySelector('.inner')); // null (not found)

// With mode: 'closed', shadowRoot would be null
const closedShadow = this.attachShadow({ mode: 'closed' });
console.log(el.shadowRoot); // null
Browser Support: All modern browsers - Shadow DOM is well supported but some CSS pseudo-classes may have limited support. Use feature detection: if ('attachShadow' in Element.prototype). Polyfills available for older browsers.

12.3 HTML Templates and Content Cloning

Element Purpose Parsed Rendered
<template> Hold inert HTML for cloning ✅ Yes (valid HTML) ❌ No (not in DOM tree)
template.content DocumentFragment containing template contents ✅ Available immediately Only when cloned and appended
Cloning Method Returns Deep Clone Use Case
cloneNode(true) Node (element clone) ✅ Yes (with descendants) Clone single element and children
cloneNode(false) Node (element only) ❌ No (shallow) Clone element without children
importNode(node, true) Node (from another document) ✅ Yes Import from template or iframe

Example: Template with cloning and population

<!-- Define template (not rendered) -->
<template id="card-template">
  <style>
    .card {
      border: 1px solid #ddd;
      border-radius: 8px;
      padding: 16px;
      margin: 10px;
    }
    .card-title {
      font-size: 18px;
      font-weight: bold;
      margin-bottom: 8px;
    }
    .card-body {
      color: #666;
    }
  </style>
  <div class="card">
    <div class="card-title"></div>
    <div class="card-body"></div>
    <button class="card-action">Action</button>
  </div>
</template>

<div id="cards-container"></div>

<script>
// Get template
const template = document.getElementById('card-template');

// Create cards from data
const cardsData = [
  { title: 'Card 1', body: 'Content for card 1' },
  { title: 'Card 2', body: 'Content for card 2' },
  { title: 'Card 3', body: 'Content for card 3' }
];

const container = document.getElementById('cards-container');

cardsData.forEach(data => {
  // Clone template content (deep clone)
  const clone = template.content.cloneNode(true);
  
  // Populate cloned content
  clone.querySelector('.card-title').textContent = data.title;
  clone.querySelector('.card-body').textContent = data.body;
  
  // Add event listener
  clone.querySelector('.card-action').addEventListener('click', () => {
    alert(`Action for: ${data.title}`);
  });
  
  // Append to DOM
  container.appendChild(clone);
});
</script>

Example: Template in Web Component

class UserCard extends HTMLElement {
  constructor() {
    super();
    
    // Create shadow DOM
    const shadow = this.attachShadow({ mode: 'open' });
    
    // Create template programmatically
    const template = document.createElement('template');
    template.innerHTML = `
      <style>
        :host {
          display: block;
          border: 2px solid #0066cc;
          border-radius: 8px;
          padding: 16px;
          max-width: 300px;
        }
        .avatar {
          width: 80px;
          height: 80px;
          border-radius: 50%;
          object-fit: cover;
        }
        .name {
          font-size: 20px;
          font-weight: bold;
          margin: 10px 0 5px 0;
        }
        .email {
          color: #666;
        }
      </style>
      <div class="user-card">
        <img class="avatar" alt="User avatar">
        <div class="name"></div>
        <div class="email"></div>
      </div>
    `;
    
    // Clone and append template
    shadow.appendChild(template.content.cloneNode(true));
    
    // Store references
    this._avatar = shadow.querySelector('.avatar');
    this._name = shadow.querySelector('.name');
    this._email = shadow.querySelector('.email');
  }
  
  connectedCallback() {
    this._render();
  }
  
  static get observedAttributes() {
    return ['name', 'email', 'avatar'];
  }
  
  attributeChangedCallback() {
    this._render();
  }
  
  _render() {
    this._name.textContent = this.getAttribute('name') || 'Unknown';
    this._email.textContent = this.getAttribute('email') || '';
    this._avatar.src = this.getAttribute('avatar') || 'default-avatar.png';
  }
}

customElements.define('user-card', UserCard);

// Usage:
// <user-card 
//   name="John Doe" 
//   email="john@example.com"
//   avatar="john.jpg">
// </user-card>
Template Benefits: Content is parsed but not rendered - efficient for repeated structures. Scripts don't execute, images don't load until cloned and inserted. Can contain any valid HTML including <style> and <script>. Perfect for component blueprints.

12.4 Slot Elements and Content Projection

Slot Type Attribute Matches Priority
Named Slot <slot name="header"> Elements with slot="header" High - explicit match
Default Slot <slot> (no name) All unslotted content Low - catches remaining content
Fallback Content Content inside <slot> Shown when slot is empty N/A - default
Slot API Returns Description
slot.assignedNodes() Node[] Get nodes assigned to slot (includes text nodes)
slot.assignedNodes({flatten: true}) Node[] Get flattened assigned nodes (including nested slots)
slot.assignedElements() Element[] Get only element nodes (no text nodes)
element.assignedSlot HTMLSlotElement | null Get slot element is assigned to
Slot Event When Fired Event Target
slotchange Slot's assigned nodes change The <slot> element

Example: Named and default slots

// Component definition
class BlogPost extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'open' });
    
    shadow.innerHTML = `
      <style>
        :host {
          display: block;
          border: 1px solid #ddd;
          border-radius: 8px;
          overflow: hidden;
        }
        .header {
          background: #0066cc;
          color: white;
          padding: 20px;
        }
        .content {
          padding: 20px;
          line-height: 1.6;
        }
        .footer {
          background: #f5f5f5;
          padding: 10px 20px;
          border-top: 1px solid #ddd;
        }
        /* Style slotted content */
        ::slotted(h1) {
          margin: 0;
          font-size: 24px;
        }
        ::slotted(p) {
          margin: 10px 0;
        }
      </style>
      
      <div class="header">
        <!-- Named slot for title -->
        <slot name="title">Default Title</slot>
        <!-- Named slot for subtitle -->
        <slot name="subtitle"></slot>
      </div>
      
      <div class="content">
        <!-- Default slot for main content -->
        <slot>No content provided</slot>
      </div>
      
      <div class="footer">
        <!-- Named slot for metadata -->
        <slot name="meta"></slot>
      </div>
    `;
  }
}

customElements.define('blog-post', BlogPost);

// HTML Usage:
<blog-post>
  <!-- Content projected into named slots -->
  <h1 slot="title">My Blog Post</h1>
  <span slot="subtitle">A brief introduction</span>
  
  <!-- Content without slot attr goes to default slot -->
  <p>This is the main content of the blog post.</p>
  <p>It can contain multiple paragraphs.</p>
  
  <!-- Another named slot -->
  <div slot="meta">
    <span>Author: John Doe</span>
    <span>Date: 2025-12-22</span>
  </div>
</blog-post>

// Result: Content is projected into respective slots
// - h1 and span appear in header
// - p elements appear in content area
// - div appears in footer

Example: Slot API and slotchange event

class DynamicList extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'open' });
    
    shadow.innerHTML = `
      <style>
        .count { 
          font-weight: bold; 
          margin-bottom: 10px; 
        }
        ::slotted(li) {
          padding: 5px;
          border-bottom: 1px solid #ddd;
        }
      </style>
      <div class="count">Items: 0</div>
      <ul>
        <slot></slot>
      </ul>
    `;
    
    this._slot = shadow.querySelector('slot');
    this._countDisplay = shadow.querySelector('.count');
    
    // Listen for slot changes
    this._slot.addEventListener('slotchange', () => {
      this._updateCount();
    });
  }
  
  connectedCallback() {
    this._updateCount();
  }
  
  _updateCount() {
    // Get assigned elements (only <li> elements)
    const items = this._slot.assignedElements();
    this._countDisplay.textContent = `Items: ${items.length}`;
    
    // Log assigned nodes
    console.log('Assigned nodes:', this._slot.assignedNodes());
    console.log('Assigned elements:', items);
    
    // You can also manipulate slotted content
    items.forEach((item, index) => {
      item.setAttribute('data-index', index);
    });
  }
  
  // Public method to get items
  getItems() {
    return this._slot.assignedElements();
  }
}

customElements.define('dynamic-list', DynamicList);

// Usage:
<dynamic-list id="myList">
  <li>Item 1</li>
  <li>Item 2</li>
  <li>Item 3</li>
</dynamic-list>

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

// Add new item dynamically
const newItem = document.createElement('li');
newItem.textContent = 'Item 4';
list.appendChild(newItem); // Triggers slotchange event

// Get items from component
console.log(list.getItems()); // [li, li, li, li]

// Check which slot an element is in
const firstItem = list.querySelector('li');
console.log(firstItem.assignedSlot); // <slot> element
</script>

Example: Multiple named slots with fallback

class MediaCard extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' }).innerHTML = `
      <style>
        :host { display: flex; gap: 15px; }
        .media { flex: 0 0 200px; }
        .body { flex: 1; }
        img { width: 100%; height: auto; }
      </style>
      
      <div class="media">
        <!-- Fallback content shown if no image provided -->
        <slot name="image">
          <img src="placeholder.png" alt="Placeholder">
        </slot>
      </div>
      
      <div class="body">
        <slot name="title">
          <h3>Untitled</h3>
        </slot>
        <slot></slot>
      </div>
    `;
  }
}

customElements.define('media-card', MediaCard);

// With image:
<media-card>
  <img slot="image" src="photo.jpg" alt="Photo">
  <h2 slot="title">Card Title</h2>
  <p>Card description</p>
</media-card>

// Without image (shows fallback):
<media-card>
  <h2 slot="title">Card Title</h2>
  <p>Card description</p>
</media-card>
Slot Styling: Use ::slotted(selector) to style slotted content from within shadow DOM. Can only target direct children of the host element. Slotted content maintains its original styles from light DOM. CSS custom properties pierce shadow boundary for theming.

12.5 Custom Attributes and Properties

Type Syntax Reflected Use Case
HTML Attribute <my-el attr="value"> Always strings Initial configuration, serialization, declarative
JS Property element.prop = value Any type Programmatic API, complex data, methods
Reflected Property Synced attribute ↔ property Both ways Keep HTML and JS in sync
Pattern Implementation Benefits
Getter/Setter get prop() { } set prop(val) { } Validation, side effects, computed values
Boolean Attributes Presence = true, absence = false Match native HTML behavior (disabled, hidden)
Data Attributes data-* for custom data Valid HTML, CSS selectors, dataset API

Example: Reflected properties and boolean attributes

class ToggleSwitch extends HTMLElement {
  // Define observed attributes
  static get observedAttributes() {
    return ['checked', 'disabled', 'label'];
  }
  
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'open' });
    
    shadow.innerHTML = `
      <style>
        :host {
          display: inline-block;
          user-select: none;
        }
        .switch {
          display: flex;
          align-items: center;
          gap: 10px;
          cursor: pointer;
        }
        .switch.disabled {
          opacity: 0.5;
          cursor: not-allowed;
        }
        .toggle {
          width: 50px;
          height: 24px;
          background: #ccc;
          border-radius: 12px;
          position: relative;
          transition: background 0.3s;
        }
        .toggle.checked {
          background: #4caf50;
        }
        .slider {
          width: 20px;
          height: 20px;
          background: white;
          border-radius: 50%;
          position: absolute;
          top: 2px;
          left: 2px;
          transition: left 0.3s;
        }
        .toggle.checked .slider {
          left: 28px;
        }
      </style>
      
      <div class="switch">
        <div class="toggle">
          <div class="slider"></div>
        </div>
        <span class="label"></span>
      </div>
    `;
    
    this._switch = shadow.querySelector('.switch');
    this._toggle = shadow.querySelector('.toggle');
    this._label = shadow.querySelector('.label');
    
    this._switch.addEventListener('click', this._handleClick);
  }
  
  // BOOLEAN ATTRIBUTE: 'checked'
  get checked() {
    return this.hasAttribute('checked');
  }
  
  set checked(value) {
    if (value) {
      this.setAttribute('checked', '');
    } else {
      this.removeAttribute('checked');
    }
  }
  
  // BOOLEAN ATTRIBUTE: 'disabled'
  get disabled() {
    return this.hasAttribute('disabled');
  }
  
  set disabled(value) {
    if (value) {
      this.setAttribute('disabled', '');
    } else {
      this.removeAttribute('disabled');
    }
  }
  
  // STRING ATTRIBUTE: 'label'
  get label() {
    return this.getAttribute('label') || '';
  }
  
  set label(value) {
    if (value) {
      this.setAttribute('label', value);
    } else {
      this.removeAttribute('label');
    }
  }
  
  // NUMBER PROPERTY (not reflected to attribute)
  get value() {
    return this._value || 0;
  }
  
  set value(val) {
    this._value = Number(val);
  }
  
  // OBJECT PROPERTY (cannot be attribute)
  get data() {
    return this._data;
  }
  
  set data(obj) {
    this._data = obj;
  }
  
  // Handle attribute changes
  attributeChangedCallback(name, oldValue, newValue) {
    if (name === 'checked') {
      this._toggle.classList.toggle('checked', this.checked);
    } else if (name === 'disabled') {
      this._switch.classList.toggle('disabled', this.disabled);
    } else if (name === 'label') {
      this._label.textContent = newValue;
    }
  }
  
  _handleClick = () => {
    if (this.disabled) return;
    
    // Toggle checked state
    this.checked = !this.checked;
    
    // Dispatch event
    this.dispatchEvent(new CustomEvent('toggle', {
      detail: { checked: this.checked },
      bubbles: true
    }));
  }
  
  // Public methods
  toggle() {
    this.checked = !this.checked;
  }
}

customElements.define('toggle-switch', ToggleSwitch);

// HTML Usage:
<toggle-switch 
  checked 
  label="Enable notifications"
  data-user-id="123">
</toggle-switch>

// JavaScript Usage:
const toggle = document.querySelector('toggle-switch');

// Boolean attributes/properties
console.log(toggle.checked); // true
toggle.checked = false; // Removes 'checked' attribute

console.log(toggle.disabled); // false
toggle.disabled = true; // Adds 'disabled' attribute

// String property
toggle.label = 'New label'; // Updates attribute

// Number property (not reflected)
toggle.value = 42;
console.log(toggle.getAttribute('value')); // null (not reflected)

// Object property
toggle.data = { userId: 123, settings: {} };

// Event listener
toggle.addEventListener('toggle', (e) => {
  console.log('Toggled:', e.detail.checked);
});

Example: Data attributes and dataset API

class ProductCard extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' }).innerHTML = `
      <style>
        .card { padding: 20px; border: 1px solid #ddd; }
        .price { font-size: 24px; font-weight: bold; color: #0066cc; }
        .sale { color: #ff0000; }
      </style>
      <div class="card">
        <h3 class="name"></h3>
        <p class="price"></p>
        <button class="buy">Buy Now</button>
      </div>
    `;
  }
  
  connectedCallback() {
    // Access data attributes via dataset
    const name = this.dataset.productName;
    const price = this.dataset.price;
    const onSale = this.dataset.sale !== undefined;
    
    const shadow = this.shadowRoot;
    shadow.querySelector('.name').textContent = name;
    shadow.querySelector('.price').textContent = `${price}`;
    
    if (onSale) {
      shadow.querySelector('.price').classList.add('sale');
    }
    
    shadow.querySelector('.buy').addEventListener('click', () => {
      this.dispatchEvent(new CustomEvent('purchase', {
        detail: {
          productId: this.dataset.productId,
          name: this.dataset.productName,
          price: this.dataset.price
        }
      }));
    });
  }
}

customElements.define('product-card', ProductCard);

// HTML with data attributes:
<product-card
  data-product-id="12345"
  data-product-name="Laptop"
  data-price="999"
  data-sale>
</product-card>

// JavaScript access:
const card = document.querySelector('product-card');

// Read data attributes
console.log(card.dataset.productId); // "12345"
console.log(card.dataset.productName); // "Laptop"

// Write data attributes
card.dataset.price = "899";
card.dataset.category = "electronics";

// Check existence
if ('sale' in card.dataset) {
  console.log('On sale!');
}
Best Practices: Use attributes for simple, serializable values (strings, numbers, booleans). Use properties for complex data (objects, arrays, functions). Reflect important properties to attributes for CSS selectors and HTML serialization. Follow HTML conventions: boolean attributes (presence = true), lowercase names with hyphens.

12.6 Web Component Best Practices

Category Best Practice Reason
Naming Use hyphenated names (min 2 words) Required by spec, avoids conflicts with native elements
Encapsulation Always use Shadow DOM Style isolation, implementation hiding
Performance Defer heavy work to connectedCallback Constructor must be lightweight
Cleanup Remove listeners in disconnectedCallback Prevent memory leaks
Events Use CustomEvent with composed: true Allow events to cross shadow boundary
Theming Expose CSS custom properties Enable external styling without breaking encapsulation
Accessibility Add ARIA attributes, keyboard support Screen readers, keyboard navigation
Error Handling Validate inputs, provide fallbacks Graceful degradation, better UX
Anti-Pattern Why Avoid Better Approach
Modifying attributes in constructor Not yet in DOM, parser conflicts Wait for connectedCallback
Accessing children in constructor Children not yet parsed Use connectedCallback or slotchange
Single-word tag names Invalid, conflicts with native elements Use hyphenated names (my-element)
Not calling super() Breaks inheritance chain Always call super() first in constructor
Modifying light DOM from component Breaks user expectations, conflicts Use slots, dispatch events instead
Global styles in component Pollutes global namespace Use Shadow DOM styles or CSS custom properties

Example: Production-ready component with best practices

/**
 * Accessible rating component
 * @element star-rating
 * @attr {number} value - Current rating (0-5)
 * @attr {number} max - Maximum rating (default: 5)
 * @attr {boolean} readonly - Disable interaction
 * @fires rating-change - When rating changes
 * @cssprop --star-size - Size of stars (default: 24px)
 * @cssprop --star-color - Color of filled stars (default: gold)
 */
class StarRating extends HTMLElement {
  static get observedAttributes() {
    return ['value', 'max', 'readonly'];
  }
  
  constructor() {
    super();
    
    // Initialize state
    this._value = 0;
    this._max = 5;
    this._readonly = false;
    
    // Create shadow DOM
    const shadow = this.attachShadow({ mode: 'open' });
    shadow.innerHTML = `
      <style>
        :host {
          display: inline-flex;
          gap: 4px;
          --star-size: 24px;
          --star-color: gold;
          --star-empty: #ddd;
        }
        
        :host([readonly]) {
          pointer-events: none;
        }
        
        .star {
          width: var(--star-size);
          height: var(--star-size);
          cursor: pointer;
          fill: var(--star-empty);
          transition: fill 0.2s;
        }
        
        .star.filled {
          fill: var(--star-color);
        }
        
        .star:hover,
        .star.preview {
          fill: var(--star-color);
          opacity: 0.7;
        }
        
        :host([readonly]) .star {
          cursor: default;
        }
        
        /* Focus styles for accessibility */
        .star:focus {
          outline: 2px solid #0066cc;
          outline-offset: 2px;
        }
      </style>
      <div class="stars" role="radiogroup" aria-label="Rating"></div>
    `;
    
    this._starsContainer = shadow.querySelector('.stars');
  }
  
  connectedCallback() {
    // Read attributes
    this._value = parseFloat(this.getAttribute('value')) || 0;
    this._max = parseInt(this.getAttribute('max')) || 5;
    this._readonly = this.hasAttribute('readonly');
    
    // Build UI
    this._render();
    
    // Add event listeners (if not readonly)
    if (!this._readonly) {
      this._starsContainer.addEventListener('click', this._handleClick);
      this._starsContainer.addEventListener('mouseover', this._handleHover);
      this._starsContainer.addEventListener('mouseout', this._handleMouseOut);
      this._starsContainer.addEventListener('keydown', this._handleKeydown);
    }
  }
  
  disconnectedCallback() {
    // Clean up event listeners
    this._starsContainer.removeEventListener('click', this._handleClick);
    this._starsContainer.removeEventListener('mouseover', this._handleHover);
    this._starsContainer.removeEventListener('mouseout', this._handleMouseOut);
    this._starsContainer.removeEventListener('keydown', this._handleKeydown);
  }
  
  attributeChangedCallback(name, oldValue, newValue) {
    if (oldValue === newValue) return;
    
    switch(name) {
      case 'value':
        this._value = parseFloat(newValue) || 0;
        this._updateStars();
        break;
      case 'max':
        this._max = parseInt(newValue) || 5;
        this._render();
        break;
      case 'readonly':
        this._readonly = this.hasAttribute('readonly');
        break;
    }
  }
  
  // Render stars
  _render() {
    this._starsContainer.innerHTML = '';
    
    for (let i = 1; i <= this._max; i++) {
      const star = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
      star.setAttribute('class', 'star');
      star.setAttribute('viewBox', '0 0 24 24');
      star.setAttribute('data-value', i);
      star.setAttribute('role', 'radio');
      star.setAttribute('aria-checked', i <= this._value);
      star.setAttribute('tabindex', i === Math.ceil(this._value) ? 0 : -1);
      
      const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
      path.setAttribute('d', 'M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z');
      
      star.appendChild(path);
      this._starsContainer.appendChild(star);
    }
    
    this._updateStars();
  }
  
  // Update visual state
  _updateStars() {
    const stars = this._starsContainer.querySelectorAll('.star');
    stars.forEach((star, index) => {
      const value = index + 1;
      star.classList.toggle('filled', value <= this._value);
      star.setAttribute('aria-checked', value <= this._value);
    });
  }
  
  // Event handlers
  _handleClick = (e) => {
    const star = e.target.closest('.star');
    if (!star) return;
    
    const value = parseInt(star.dataset.value);
    this.value = value;
  }
  
  _handleHover = (e) => {
    const star = e.target.closest('.star');
    if (!star) return;
    
    const value = parseInt(star.dataset.value);
    const stars = this._starsContainer.querySelectorAll('.star');
    
    stars.forEach((s, index) => {
      s.classList.toggle('preview', index < value);
    });
  }
  
  _handleMouseOut = () => {
    const stars = this._starsContainer.querySelectorAll('.star');
    stars.forEach(s => s.classList.remove('preview'));
  }
  
  _handleKeydown = (e) => {
    let newValue;
    
    switch(e.key) {
      case 'ArrowRight':
      case 'ArrowUp':
        newValue = Math.min(this._value + 1, this._max);
        break;
      case 'ArrowLeft':
      case 'ArrowDown':
        newValue = Math.max(this._value - 1, 0);
        break;
      case 'Home':
        newValue = 0;
        break;
      case 'End':
        newValue = this._max;
        break;
      default:
        return;
    }
    
    e.preventDefault();
    this.value = newValue;
    
    // Update focus
    const stars = this._starsContainer.querySelectorAll('.star');
    stars[newValue - 1]?.focus();
  }
  
  // Public API
  get value() {
    return this._value;
  }
  
  set value(val) {
    const newValue = Math.max(0, Math.min(val, this._max));
    if (newValue === this._value) return;
    
    const oldValue = this._value;
    this._value = newValue;
    this.setAttribute('value', newValue);
    
    // Dispatch event
    this.dispatchEvent(new CustomEvent('rating-change', {
      detail: { value: newValue, oldValue },
      bubbles: true,
      composed: true // Cross shadow boundary
    }));
  }
  
  get max() {
    return this._max;
  }
  
  set max(val) {
    this.setAttribute('max', val);
  }
  
  get readonly() {
    return this._readonly;
  }
  
  set readonly(val) {
    if (val) {
      this.setAttribute('readonly', '');
    } else {
      this.removeAttribute('readonly');
    }
  }
}

customElements.define('star-rating', StarRating);

// Usage:
<star-rating 
  value="3.5" 
  max="5"
  style="--star-size: 32px; --star-color: #ff9800">
</star-rating>

<script>
const rating = document.querySelector('star-rating');

rating.addEventListener('rating-change', (e) => {
  console.log('New rating:', e.detail.value);
});

// Make readonly after selection
rating.addEventListener('rating-change', () => {
  rating.readonly = true;
});
</script>

Section 12 Key Takeaways

  • Custom element names must contain hyphen (my-element); autonomous extend HTMLElement, customized built-ins extend specific elements
  • Constructor initializes state; connectedCallback for DOM manipulation; disconnectedCallback for cleanup
  • Shadow DOM provides style and DOM encapsulation; use mode: 'open' for testability
  • :host styles the custom element; ::slotted() styles projected content; CSS custom properties pierce shadow boundary
  • <template> content is parsed but not rendered; perfect for component blueprints and repeated structures
  • Named slots (<slot name="x">) for specific content; default slot for remaining; slotchange event for dynamic updates
  • Reflect important properties to attributes for CSS selectors; use properties for complex data
  • Boolean attributes: presence = true (checked, disabled); use dataset for custom data
  • Always call super() first; validate inputs; dispatch CustomEvent with composed: true to cross shadow boundary
  • Follow accessibility: ARIA attributes, keyboard navigation, focus management, semantic HTML

13. SEO and Metadata Optimization

13.1 Essential Meta Tags and Document Info

Meta Tag Syntax Purpose Required
charset <meta charset="UTF-8"> Character encoding (must be in first 1024 bytes) ✅ Yes
viewport <meta name="viewport" content="width=device-width, initial-scale=1.0"> Responsive design, mobile optimization ✅ Yes (for mobile)
description <meta name="description" content="..."> Page summary in search results (150-160 chars) ✅ Highly recommended
keywords <meta name="keywords" content="..."> Search keywords DEPRECATED ❌ No (ignored by Google)
author <meta name="author" content="..."> Document author information ❌ Optional
robots <meta name="robots" content="index, follow"> Search engine crawling instructions ❌ Optional
theme-color <meta name="theme-color" content="#ffffff"> Browser UI color (mobile) ❌ Optional
Robots Values Meaning Use Case
index Allow indexing page in search results Default - public content
noindex Prevent indexing (hide from search) Private pages, duplicates, staging
follow Follow links on page Default - pass link equity
nofollow Don't follow links User-generated content, paid links
noarchive Don't cache page Frequently updated content
nosnippet Don't show text snippet in results Control preview display
noimageindex Don't index images Protect image content
Link Tag Syntax Purpose
icon/favicon <link rel="icon" href="favicon.ico"> Browser tab icon, bookmark icon
apple-touch-icon <link rel="apple-touch-icon" sizes="180x180" href="icon.png"> iOS home screen icon
manifest <link rel="manifest" href="/manifest.json"> PWA manifest for app info
alternate <link rel="alternate" hreflang="es" href="..."> Language/regional variants

Example: Complete SEO-optimized head section

<!DOCTYPE html>
<html lang="en">
<head>
  <!-- Character encoding (must be first) -->
  <meta charset="UTF-8">
  
  <!-- Viewport for responsive design -->
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  
  <!-- Page title (50-60 chars, unique per page) -->
  <title>Best Practices for HTML SEO | Complete Guide 2025</title>
  
  <!-- Meta description (150-160 chars, unique per page) -->
  <meta name="description" content="Learn essential HTML SEO best practices including meta tags, structured data, and optimization techniques to improve search rankings.">
  
  <!-- Author information -->
  <meta name="author" content="John Doe">
  
  <!-- Robots directives (optional - default is index,follow) -->
  <meta name="robots" content="index, follow, max-image-preview:large">
  
  <!-- Specific bot directives -->
  <meta name="googlebot" content="index, follow">
  
  <!-- Theme color for mobile browsers -->
  <meta name="theme-color" content="#ffffff">
  
  <!-- Favicons -->
  <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
  <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
  
  <!-- PWA Manifest -->
  <link rel="manifest" href="/manifest.json">
  
  <!-- Language alternatives -->
  <link rel="alternate" hreflang="en" href="https://example.com/en/">
  <link rel="alternate" hreflang="es" href="https://example.com/es/">
  <link rel="alternate" hreflang="x-default" href="https://example.com/">
  
  <!-- Canonical URL (prevent duplicate content) -->
  <link rel="canonical" href="https://example.com/seo-guide">
</head>
<body>
  <!-- Content here -->
</body>
</html>
Best Practices: Keep title under 60 characters. Description should be 150-160 chars, unique per page. Use UTF-8 encoding. Include viewport meta for mobile. Don't use keywords meta (ignored). Use semantic HTML for better crawling. Title and description are most important for SEO.

13.2 Open Graph Protocol Implementation

Property Required Example Purpose
og:title ✅ Yes "The Best HTML Guide" Title when shared (can differ from <title>)
og:type ✅ Yes "article", "website", "video" Content type classification
og:url ✅ Yes "https://example.com/page" Canonical URL for this page
og:image ✅ Yes "https://example.com/image.jpg" Preview image (1200x630px recommended)
og:description ❌ Optional "Article summary..." Description when shared
og:site_name ❌ Optional "My Website" Name of overall site
og:locale ❌ Optional "en_US" Content language/region
Article Properties Syntax Purpose
article:published_time <meta property="article:published_time" content="2025-12-22T10:00:00Z"> Publication date (ISO 8601)
article:modified_time <meta property="article:modified_time" content="2025-12-23T15:30:00Z"> Last modified date
article:author <meta property="article:author" content="https://facebook.com/author"> Author profile URL
article:section <meta property="article:section" content="Technology"> Content category/section
article:tag <meta property="article:tag" content="HTML"> Keywords/tags (multiple allowed)
Image Properties Purpose Recommended
og:image:width Image width in pixels 1200px
og:image:height Image height in pixels 630px
og:image:alt Alt text for image Descriptive text
og:image:type MIME type image/jpeg, image/png

Example: Complete Open Graph implementation

<head>
  <!-- Basic Open Graph (required) -->
  <meta property="og:title" content="Complete Guide to HTML SEO in 2025">
  <meta property="og:type" content="article">
  <meta property="og:url" content="https://example.com/html-seo-guide">
  <meta property="og:image" content="https://example.com/images/seo-guide-share.jpg">
  
  <!-- Optional but recommended -->
  <meta property="og:description" content="Learn everything about HTML SEO including meta tags, structured data, and optimization techniques.">
  <meta property="og:site_name" content="Web Development Hub">
  <meta property="og:locale" content="en_US">
  <meta property="og:locale:alternate" content="es_ES">
  
  <!-- Image metadata -->
  <meta property="og:image:width" content="1200">
  <meta property="og:image:height" content="630">
  <meta property="og:image:alt" content="HTML SEO Guide Cover Image">
  <meta property="og:image:type" content="image/jpeg">
  
  <!-- Article-specific metadata -->
  <meta property="article:published_time" content="2025-12-22T10:00:00Z">
  <meta property="article:modified_time" content="2025-12-23T15:30:00Z">
  <meta property="article:author" content="https://facebook.com/johndoe">
  <meta property="article:section" content="Web Development">
  <meta property="article:tag" content="HTML">
  <meta property="article:tag" content="SEO">
  <meta property="article:tag" content="Web Development">
  
  <!-- Facebook App ID (for insights) -->
  <meta property="fb:app_id" content="123456789">
</head>

Example: Different content types

<!-- Video content -->
<meta property="og:type" content="video.movie">
<meta property="og:video" content="https://example.com/video.mp4">
<meta property="og:video:type" content="video/mp4">
<meta property="og:video:width" content="1280">
<meta property="og:video:height" content="720">

<!-- Music content -->
<meta property="og:type" content="music.song">
<meta property="music:duration" content="240">
<meta property="music:musician" content="https://example.com/artist">

<!-- Product -->
<meta property="og:type" content="product">
<meta property="product:price:amount" content="29.99">
<meta property="product:price:currency" content="USD">

<!-- Profile/Person -->
<meta property="og:type" content="profile">
<meta property="profile:first_name" content="John">
<meta property="profile:last_name" content="Doe">
<meta property="profile:username" content="johndoe">
Testing: Use Facebook Sharing Debugger (developers.facebook.com/tools/debug/) to test Open Graph tags. LinkedIn also uses OG tags. Image should be at least 1200x630px (1.91:1 ratio). URL must be absolute, not relative. Content in property attribute, not name.

13.3 Twitter Card Metadata

Twitter Card Type Use Case Required Tags
summary Default card with small image title, description, image (120x120 min)
summary_large_image Large image card (most common) title, description, image (300x157 min, 2:1 ratio)
app Mobile app promotion app name, ID, country
player Video/audio player player URL, width, height
Twitter Meta Tag Syntax Purpose
twitter:card <meta name="twitter:card" content="summary_large_image"> Card type selection
twitter:site <meta name="twitter:site" content="@username"> Site's Twitter handle
twitter:creator <meta name="twitter:creator" content="@author"> Content author's Twitter handle
twitter:title <meta name="twitter:title" content="..."> Title (falls back to og:title)
twitter:description <meta name="twitter:description" content="..."> Description (falls back to og:description)
twitter:image <meta name="twitter:image" content="..."> Image URL (falls back to og:image)
twitter:image:alt <meta name="twitter:image:alt" content="..."> Image alt text for accessibility

Example: Twitter Card with fallback to Open Graph

<head>
  <!-- Open Graph tags (used by Twitter as fallback) -->
  <meta property="og:title" content="Complete HTML SEO Guide 2025">
  <meta property="og:description" content="Master HTML SEO with meta tags, structured data, and best practices.">
  <meta property="og:image" content="https://example.com/images/share-image.jpg">
  <meta property="og:url" content="https://example.com/seo-guide">
  <meta property="og:type" content="article">
  
  <!-- Twitter-specific tags -->
  <meta name="twitter:card" content="summary_large_image">
  <meta name="twitter:site" content="@mywebsite">
  <meta name="twitter:creator" content="@johndoe">
  
  <!-- Optional: Override OG values for Twitter -->
  <meta name="twitter:title" content="HTML SEO Guide - Twitter Optimized">
  <meta name="twitter:description" content="Everything you need to know about HTML SEO in this comprehensive guide.">
  <meta name="twitter:image" content="https://example.com/images/twitter-card.jpg">
  <meta name="twitter:image:alt" content="Colorful diagram showing HTML SEO concepts">
</head>

Example: Twitter Player Card for video

<head>
  <!-- Player card for video content -->
  <meta name="twitter:card" content="player">
  <meta name="twitter:site" content="@myvideosite">
  <meta name="twitter:title" content="HTML Tutorial - Part 1">
  <meta name="twitter:description" content="Introduction to HTML basics">
  
  <!-- Player configuration -->
  <meta name="twitter:player" content="https://example.com/player.html">
  <meta name="twitter:player:width" content="1280">
  <meta name="twitter:player:height" content="720">
  
  <!-- Player stream (optional) -->
  <meta name="twitter:player:stream" content="https://example.com/video.mp4">
  <meta name="twitter:player:stream:content_type" content="video/mp4">
  
  <!-- Fallback image -->
  <meta name="twitter:image" content="https://example.com/thumbnail.jpg">
</head>

Example: Twitter App Card

<head>
  <!-- App card for mobile app promotion -->
  <meta name="twitter:card" content="app">
  <meta name="twitter:site" content="@myapp">
  <meta name="twitter:description" content="Download our amazing app!">
  
  <!-- iOS app info -->
  <meta name="twitter:app:name:iphone" content="My App">
  <meta name="twitter:app:id:iphone" content="123456789">
  <meta name="twitter:app:url:iphone" content="myapp://action">
  
  <!-- Android app info -->
  <meta name="twitter:app:name:googleplay" content="My App">
  <meta name="twitter:app:id:googleplay" content="com.example.myapp">
  <meta name="twitter:app:url:googleplay" content="myapp://action">
  
  <!-- iPad app info (optional) -->
  <meta name="twitter:app:name:ipad" content="My App HD">
  <meta name="twitter:app:id:ipad" content="987654321">
  <meta name="twitter:app:url:ipad" content="myapp://action">
</head>
Testing & Best Practices: Use Twitter Card Validator (cards-dev.twitter.com/validator) to test. Twitter falls back to Open Graph if Twitter tags missing. summary_large_image is most popular (2:1 ratio, min 300x157px). Include twitter:image:alt for accessibility. Use name attribute, not property.

13.4 Structured Data (JSON-LD, Microdata)

Format Syntax Placement Recommendation
JSON-LD <script type="application/ld+json"> In <head> or <body> ✅ Google's preferred format
Microdata itemscope, itemprop attributes Inline with HTML elements ⚠️ Valid but verbose
RDFa vocab, property attributes Inline with HTML elements ❌ Less common
Schema.org Type Use Case Key Properties
Article Blog posts, news articles headline, author, datePublished, image
Product E-commerce items name, image, offers, aggregateRating
Organization Company information name, logo, url, contactPoint, sameAs
Person Individual profiles name, image, jobTitle, worksFor, sameAs
BreadcrumbList Navigation breadcrumbs itemListElement, position, name, item
Recipe Cooking recipes name, recipeIngredient, recipeInstructions
Event Concerts, conferences name, startDate, location, offers
FAQPage FAQ sections mainEntity (Question/Answer pairs)
LocalBusiness Physical businesses name, address, telephone, openingHours

Example: Article structured data (JSON-LD)

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Article",
  "headline": "Complete Guide to HTML SEO in 2025",
  "alternativeHeadline": "Master HTML SEO Best Practices",
  "image": [
    "https://example.com/images/article-1x1.jpg",
    "https://example.com/images/article-4x3.jpg",
    "https://example.com/images/article-16x9.jpg"
  ],
  "datePublished": "2025-12-22T08:00:00+00:00",
  "dateModified": "2025-12-23T09:30:00+00:00",
  "author": {
    "@type": "Person",
    "name": "John Doe",
    "url": "https://example.com/author/john-doe",
    "image": "https://example.com/images/john-doe.jpg"
  },
  "publisher": {
    "@type": "Organization",
    "name": "Web Dev Hub",
    "logo": {
      "@type": "ImageObject",
      "url": "https://example.com/logo.png",
      "width": 600,
      "height": 60
    }
  },
  "description": "Learn everything about HTML SEO including meta tags, structured data, and optimization techniques.",
  "mainEntityOfPage": {
    "@type": "WebPage",
    "@id": "https://example.com/html-seo-guide"
  },
  "articleSection": "Web Development",
  "keywords": ["HTML", "SEO", "Web Development", "Meta Tags"],
  "wordCount": 2500,
  "inLanguage": "en-US"
}
</script>

Example: Product with reviews (JSON-LD)

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "Premium Wireless Headphones",
  "image": [
    "https://example.com/products/headphones-front.jpg",
    "https://example.com/products/headphones-side.jpg"
  ],
  "description": "High-quality wireless headphones with noise cancellation",
  "sku": "WH-12345",
  "mpn": "925872",
  "brand": {
    "@type": "Brand",
    "name": "AudioPro"
  },
  "offers": {
    "@type": "Offer",
    "url": "https://example.com/products/headphones",
    "priceCurrency": "USD",
    "price": "299.99",
    "priceValidUntil": "2025-12-31",
    "availability": "https://schema.org/InStock",
    "seller": {
      "@type": "Organization",
      "name": "Example Store"
    },
    "itemCondition": "https://schema.org/NewCondition"
  },
  "aggregateRating": {
    "@type": "AggregateRating",
    "ratingValue": "4.7",
    "reviewCount": "234",
    "bestRating": "5",
    "worstRating": "1"
  },
  "review": [
    {
      "@type": "Review",
      "reviewRating": {
        "@type": "Rating",
        "ratingValue": "5",
        "bestRating": "5"
      },
      "author": {
        "@type": "Person",
        "name": "Jane Smith"
      },
      "datePublished": "2025-12-15",
      "reviewBody": "Excellent sound quality and comfortable to wear!"
    }
  ]
}
</script>

Example: Breadcrumb navigation (JSON-LD)

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {
      "@type": "ListItem",
      "position": 1,
      "name": "Home",
      "item": "https://example.com"
    },
    {
      "@type": "ListItem",
      "position": 2,
      "name": "Electronics",
      "item": "https://example.com/electronics"
    },
    {
      "@type": "ListItem",
      "position": 3,
      "name": "Headphones",
      "item": "https://example.com/electronics/headphones"
    },
    {
      "@type": "ListItem",
      "position": 4,
      "name": "Wireless Headphones"
      // No 'item' for current page
    }
  ]
}
</script>

Example: FAQ page (JSON-LD)

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "mainEntity": [
    {
      "@type": "Question",
      "name": "What is HTML?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "HTML (HyperText Markup Language) is the standard markup language for creating web pages. It describes the structure of a web page semantically."
      }
    },
    {
      "@type": "Question",
      "name": "How do I add meta tags to HTML?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "<p>Meta tags are added in the <code>&lt;head&gt;</code> section of your HTML document:</p><pre>&lt;meta name=&quot;description&quot; content=&quot;Page description&quot;&gt;</pre>"
      }
    },
    {
      "@type": "Question",
      "name": "Why is SEO important?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "SEO helps your website rank higher in search engine results, increasing visibility and organic traffic to your site."
      }
    }
  ]
}
</script>

Example: Microdata format (alternative to JSON-LD)

<!-- Article using Microdata (inline) -->
<article itemscope itemtype="https://schema.org/Article">
  <h1 itemprop="headline">Complete Guide to HTML SEO</h1>
  
  <img itemprop="image" src="article-image.jpg" alt="SEO Guide">
  
  <p>By <span itemprop="author" itemscope itemtype="https://schema.org/Person">
    <span itemprop="name">John Doe</span>
  </span></p>
  
  <meta itemprop="datePublished" content="2025-12-22T08:00:00+00:00">
  <p>Published: <time datetime="2025-12-22">December 22, 2025</time></p>
  
  <div itemprop="articleBody">
    <p>Article content goes here...</p>
  </div>
  
  <div itemprop="publisher" itemscope itemtype="https://schema.org/Organization">
    <meta itemprop="name" content="Web Dev Hub">
    <div itemprop="logo" itemscope itemtype="https://schema.org/ImageObject">
      <meta itemprop="url" content="https://example.com/logo.png">
    </div>
  </div>
</article>
Testing: Use Google's Rich Results Test (search.google.com/test/rich-results) and Schema Markup Validator (validator.schema.org). JSON-LD is easier to maintain than Microdata. Multiple schema types can coexist on one page. Keep structured data in sync with visible content.

13.5 Canonical URLs and Duplicate Content

Tag/Method Syntax Purpose
Canonical Link <link rel="canonical" href="https://example.com/page"> Specify preferred version of page
Self-referencing Canonical points to itself Prevent parameter-based duplicates
Cross-domain Canonical points to different domain Syndicated content attribution
Duplicate Content Scenario Solution Example
URL Parameters Use canonical to main version ?sort=price, ?page=2 → canonical to base URL
WWW vs non-WWW Canonical + 301 redirect www.example.com → example.com
HTTP vs HTTPS Canonical to HTTPS + redirect http:// → https://
Trailing Slash Choose one version /page/ → /page or vice versa
Paginated Content Self-referencing canonical OR rel="next/prev" Each page canonical to itself
Print/Mobile Versions Canonical to desktop version /article?print=1 → /article
Syndicated Content Canonical to original source Republished article → original publisher

Example: Canonical URL implementations

<!-- Self-referencing canonical (best practice for all pages) -->
<link rel="canonical" href="https://example.com/products/shoes">

<!-- URL with parameters points to clean version -->
<!-- On page: https://example.com/products?category=shoes&sort=price -->
<link rel="canonical" href="https://example.com/products/shoes">

<!-- Paginated content (each page canonical to itself) -->
<!-- On page: https://example.com/blog?page=2 -->
<link rel="canonical" href="https://example.com/blog?page=2">
<link rel="prev" href="https://example.com/blog?page=1">
<link rel="next" href="https://example.com/blog?page=3">

<!-- Mobile version points to desktop -->
<!-- On page: https://m.example.com/article -->
<link rel="canonical" href="https://example.com/article">

<!-- Cross-domain canonical (syndicated content) -->
<!-- On syndicated site -->
<link rel="canonical" href="https://originalpublisher.com/article">

<!-- AMP version points to canonical -->
<!-- On AMP page: https://example.com/article/amp -->
<link rel="canonical" href="https://example.com/article">

Example: Complete duplicate content strategy

<head>
  <!-- Always use absolute URLs -->
  <link rel="canonical" href="https://example.com/seo-guide">
  
  <!-- Consistent protocol (HTTPS) -->
  <!-- NOT: http://example.com/seo-guide -->
  
  <!-- Consistent domain -->
  <!-- NOT: https://www.example.com/seo-guide -->
  
  <!-- Consistent trailing slash policy -->
  <!-- Choose /seo-guide OR /seo-guide/ (be consistent) -->
  
  <!-- Consistent case -->
  <!-- NOT: https://example.com/SEO-Guide -->
  
  <!-- Remove unnecessary parameters -->
  <!-- NOT: https://example.com/seo-guide?ref=twitter -->
</head>
Important Rules: Canonical URL must be absolute (include https://). Only one canonical tag per page. Canonical is a hint, not directive - Google may ignore it. Don't canonical to different content - only to same/similar content. Combine with 301 redirects for moved content.

13.6 Sitemap and Robot Instructions

File Purpose Location Format
sitemap.xml List all pages for search engines /sitemap.xml (root or submitted via Search Console) XML format
robots.txt Crawler instructions (allow/disallow) /robots.txt (must be in root) Plain text
sitemap index Multiple sitemaps container /sitemap_index.xml XML format
Sitemap Element Required Description
<loc> ✅ Yes Page URL (absolute, max 2048 chars)
<lastmod> ❌ Optional Last modification date (YYYY-MM-DD or ISO 8601)
<changefreq> ❌ Optional Update frequency (always, hourly, daily, weekly, monthly, yearly, never)
<priority> ❌ Optional Relative importance (0.0 to 1.0, default 0.5)
robots.txt Directive Syntax Purpose
User-agent User-agent: * Target specific bots (* = all)
Disallow Disallow: /admin/ Block crawling of path
Allow Allow: /public/ Override disallow (for subdirectories)
Sitemap Sitemap: https://example.com/sitemap.xml Sitemap location
Crawl-delay Crawl-delay: 10 Seconds between requests (not supported by Google)

Example: XML Sitemap (sitemap.xml)

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
        xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"
        xmlns:news="http://www.google.com/schemas/sitemap-news/0.9">
  
  <!-- Homepage -->
  <url>
    <loc>https://example.com/</loc>
    <lastmod>2025-12-22</lastmod>
    <changefreq>daily</changefreq>
    <priority>1.0</priority>
  </url>
  
  <!-- Blog post -->
  <url>
    <loc>https://example.com/blog/html-seo-guide</loc>
    <lastmod>2025-12-23T10:30:00+00:00</lastmod>
    <changefreq>weekly</changefreq>
    <priority>0.8</priority>
    
    <!-- Image extension -->
    <image:image>
      <image:loc>https://example.com/images/seo-guide.jpg</image:loc>
      <image:title>HTML SEO Guide Infographic</image:title>
      <image:caption>Visual guide to HTML SEO</image:caption>
    </image:image>
  </url>
  
  <!-- Product page -->
  <url>
    <loc>https://example.com/products/headphones</loc>
    <lastmod>2025-12-20</lastmod>
    <changefreq>monthly</changefreq>
    <priority>0.7</priority>
  </url>
  
  <!-- Static page -->
  <url>
    <loc>https://example.com/about</loc>
    <lastmod>2025-11-15</lastmod>
    <changefreq>yearly</changefreq>
    <priority>0.5</priority>
  </url>
  
</urlset>

Example: Sitemap Index for multiple sitemaps

<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  
  <sitemap>
    <loc>https://example.com/sitemap-posts.xml</loc>
    <lastmod>2025-12-23</lastmod>
  </sitemap>
  
  <sitemap>
    <loc>https://example.com/sitemap-products.xml</loc>
    <lastmod>2025-12-22</lastmod>
  </sitemap>
  
  <sitemap>
    <loc>https://example.com/sitemap-pages.xml</loc>
    <lastmod>2025-12-20</lastmod>
  </sitemap>
  
</sitemapindex>

Example: robots.txt file

# Allow all bots to crawl everything (default)
User-agent: *
Allow: /

# Block admin area
Disallow: /admin/
Disallow: /private/
Disallow: /temp/

# Block specific file types
Disallow: /*.json$
Disallow: /*.xml$
Disallow: /cgi-bin/

# Allow public files in otherwise blocked directory
Allow: /admin/public/

# Sitemap location
Sitemap: https://example.com/sitemap.xml
Sitemap: https://example.com/sitemap-images.xml

# Google-specific bot
User-agent: Googlebot
Crawl-delay: 0
Disallow: /search
Allow: /search/about

# Block bad bots
User-agent: BadBot
Disallow: /

# Block all bots from staging
User-agent: *
Disallow: /staging/
<head>
  <!-- Optional: Link to sitemap in HTML -->
  <link rel="sitemap" type="application/xml" 
        title="Sitemap" href="/sitemap.xml">
</head>
Best Practices: Max 50,000 URLs per sitemap file (50MB uncompressed). Use sitemap index for larger sites. Update sitemap when content changes. Submit sitemap to Google Search Console and Bing Webmaster Tools. robots.txt doesn't prevent indexing (use noindex meta tag for that). Sitemap helps discovery but doesn't guarantee indexing.

Section 13 Key Takeaways

  • Title (50-60 chars) and meta description (150-160 chars) are most important for SEO; unique per page
  • Use UTF-8 charset and viewport meta for mobile; robots meta controls indexing (index/noindex, follow/nofollow)
  • Open Graph (og:) tags control social media sharing; required: title, type, url, image (1200x630px)
  • Twitter Cards use name attribute (not property); falls back to Open Graph if Twitter tags missing
  • JSON-LD is Google's preferred structured data format; use schema.org types (Article, Product, Organization)
  • Test structured data with Google Rich Results Test; keep data in sync with visible content
  • Canonical URLs prevent duplicate content issues; always use absolute URLs (https://)
  • Self-referencing canonical on all pages; use for URL parameters, pagination, mobile versions
  • sitemap.xml lists all pages (max 50k URLs); robots.txt controls crawler access (must be in root)
  • Submit sitemaps to Search Console; robots.txt blocks crawling, noindex meta prevents indexing

14. Accessibility Implementation

14.1 Semantic HTML for Screen Readers

Semantic Element Purpose Screen Reader Benefit Replaces
<header> Page/section header Announces "banner" or "header" landmark <div class="header">
<nav> Navigation section Announces "navigation" landmark, quick access <div class="nav">
<main> Primary content Announces "main" landmark, skip-to-content <div class="main">
<article> Self-contained content Announces article boundary, easier navigation <div class="article">
<section> Thematic content grouping Announces section, hierarchical structure <div class="section">
<aside> Sidebar/tangential content Announces "complementary" landmark <div class="sidebar">
<footer> Page/section footer Announces "contentinfo" or "footer" landmark <div class="footer">
Heading Level Purpose Rules
<h1> Page title/main heading One per page (ideally), highest level
<h2> Major section headings Direct children of h1 sections
<h3> - <h6> Subsection headings Don't skip levels (h2 → h4 ❌)
List Type When to Use Screen Reader Announcement
<ul> Unordered list (bullets) "List, X items"
<ol> Ordered list (numbered) "List, X items" + item numbers
<dl> Definition/description list Announces term-definition pairs

Example: Semantic page structure for screen readers

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Accessible Page Example</title>
</head>
<body>
  <!-- Skip to main content link (hidden but accessible) -->
  <a href="#main-content" class="skip-link">Skip to main content</a>
  
  <!-- Header landmark -->
  <header>
    <h1>Website Title</h1>
    <p>Tagline or description</p>
  </header>
  
  <!-- Navigation landmark -->
  <nav aria-label="Main navigation">
    <ul>
      <li><a href="/">Home</a></li>
      <li><a href="/about">About</a></li>
      <li><a href="/contact">Contact</a></li>
    </ul>
  </nav>
  
  <!-- Main landmark (only one per page) -->
  <main id="main-content">
    <!-- Article 1 -->
    <article>
      <h2>Article Title</h2>
      <p>Published on <time datetime="2025-12-22">December 22, 2025</time></p>
      
      <section>
        <h3>Introduction</h3>
        <p>Article introduction...</p>
      </section>
      
      <section>
        <h3>Main Content</h3>
        <p>Article content...</p>
        
        <section>
          <h4>Subsection</h4>
          <p>Subsection content...</p>
        </section>
      </section>
    </article>
    
    <!-- Article 2 -->
    <article>
      <h2>Another Article</h2>
      <p>Content...</p>
    </article>
  </main>
  
  <!-- Complementary landmark -->
  <aside aria-label="Sidebar">
    <section>
      <h2>Related Links</h2>
      <ul>
        <li><a href="/related1">Related Article 1</a></li>
        <li><a href="/related2">Related Article 2</a></li>
      </ul>
    </section>
  </aside>
  
  <!-- Contentinfo landmark -->
  <footer>
    <p>&copy; 2025 Company Name. All rights reserved.</p>
    <nav aria-label="Footer navigation">
      <ul>
        <li><a href="/privacy">Privacy Policy</a></li>
        <li><a href="/terms">Terms of Service</a></li>
      </ul>
    </nav>
  </footer>
</body>
</html>
Best Practices: Use semantic elements instead of divs where possible. Maintain logical heading hierarchy (don't skip levels). One <main> per page. Label multiple navs with aria-label. Use <time> for dates with datetime attribute. Provide skip-to-content links for keyboard users.

14.2 ARIA Roles, States, and Properties

ARIA Category Purpose Examples
Roles Define element type/purpose button, dialog, tablist, alert
States Current condition (can change) aria-checked, aria-expanded, aria-pressed
Properties Characteristics (rarely change) aria-label, aria-labelledby, aria-describedby
Common ARIA Role Use When Required ARIA Attributes
role="button" Clickable non-button element tabindex="0", keyboard handlers
role="dialog" Modal or dialog window aria-labelledby or aria-label
role="alert" Important, time-sensitive message None (auto-announced)
role="tablist" Tab navigation component With role="tab" and role="tabpanel"
role="navigation" Navigation links Use <nav> aria-label (if multiple navs)
role="search" Search form None
role="status" Status message (low priority) None (politely announced)
role="progressbar" Progress indicator aria-valuenow, aria-valuemin, aria-valuemax
ARIA Property Purpose Example
aria-label Provide accessible name aria-label="Close dialog"
aria-labelledby Reference element ID for label aria-labelledby="dialog-title"
aria-describedby Reference element ID for description aria-describedby="hint-text"
aria-hidden Hide from screen readers aria-hidden="true" (decorative icons)
aria-live Announce dynamic content aria-live="polite" or "assertive"
aria-expanded Collapsible element state aria-expanded="false"
aria-pressed Toggle button state aria-pressed="true"
aria-checked Checkbox/radio state aria-checked="true"
aria-selected Selection state (tabs, options) aria-selected="true"
aria-disabled Disabled state (still focusable) aria-disabled="true"
aria-current Current item in set aria-current="page" or "step"

Example: Custom button with ARIA

<!-- Native button (preferred) -->
<button type="button">Click Me</button>

<!-- Custom button with ARIA (if native not possible) -->
<div 
  role="button" 
  tabindex="0"
  aria-label="Close dialog"
  onclick="closeDialog()"
  onkeydown="handleKeyPress(event)">
  <span aria-hidden="true">×</span>
</div>

<script>
function handleKeyPress(e) {
  // Space or Enter activates button
  if (e.key === ' ' || e.key === 'Enter') {
    e.preventDefault();
    closeDialog();
  }
}
</script>

Example: Accessible tabs with ARIA

<div class="tabs">
  <!-- Tab list -->
  <div role="tablist" aria-label="Content tabs">
    <button 
      role="tab" 
      id="tab-1"
      aria-selected="true" 
      aria-controls="panel-1"
      tabindex="0">
      Tab 1
    </button>
    <button 
      role="tab" 
      id="tab-2"
      aria-selected="false" 
      aria-controls="panel-2"
      tabindex="-1">
      Tab 2
    </button>
    <button 
      role="tab" 
      id="tab-3"
      aria-selected="false" 
      aria-controls="panel-3"
      tabindex="-1">
      Tab 3
    </button>
  </div>
  
  <!-- Tab panels -->
  <div 
    role="tabpanel" 
    id="panel-1" 
    aria-labelledby="tab-1"
    tabindex="0">
    Content for tab 1
  </div>
  
  <div 
    role="tabpanel" 
    id="panel-2" 
    aria-labelledby="tab-2"
    hidden
    tabindex="0">
    Content for tab 2
  </div>
  
  <div 
    role="tabpanel" 
    id="panel-3" 
    aria-labelledby="tab-3"
    hidden
    tabindex="0">
    Content for tab 3
  </div>
</div>

Example: Live regions for dynamic updates

<!-- Polite announcement (waits for pause) -->
<div role="status" aria-live="polite" aria-atomic="true">
  <!-- Content updated via JavaScript -->
  <p>5 new messages</p>
</div>

<!-- Assertive announcement (interrupts) -->
<div role="alert" aria-live="assertive">
  <!-- Urgent notifications -->
  <p>Error: Connection lost</p>
</div>

<!-- Loading state -->
<div 
  role="status" 
  aria-live="polite" 
  aria-busy="true"
  aria-label="Loading content">
  <span class="spinner"></span>
  Loading...
</div>

<script>
// Update live region
function updateStatus(message) {
  const status = document.querySelector('[role="status"]');
  status.textContent = message;
  // Screen reader announces: "5 new messages"
}

// Show error
function showError(error) {
  const alert = document.querySelector('[role="alert"]');
  alert.textContent = error;
  // Screen reader immediately announces error
}
</script>
ARIA Rules: First rule: Don't use ARIA if native HTML element exists (use <button> not <div role="button">). Second rule: Don't change native semantics (don't add role to <h1>). ARIA doesn't provide behavior - you must add keyboard handlers and focus management. Test with actual screen readers.

14.3 Keyboard Navigation and Focus Management

Keyboard Action Expected Behavior Elements
Tab Move focus to next focusable element All interactive elements
Shift + Tab Move focus to previous focusable element All interactive elements
Enter Activate element (click) Links, buttons, form submissions
Space Activate button, check checkbox Buttons, checkboxes
Arrow Keys Navigate within component Radio groups, tabs, menus, sliders
Escape Close/cancel Dialogs, menus, dropdowns
Home / End Jump to first/last item Lists, sliders, combo boxes
tabindex Value Behavior Use Case
tabindex="0" Natural tab order, focusable Make div/span focusable (custom controls)
tabindex="-1" Not in tab order, programmatically focusable Headings for skip links, modal content
tabindex="1+" Explicit order AVOID ❌ Breaks natural order, causes confusion
No tabindex Native focusable elements only Default for button, input, a, etc.
Focus Pattern Implementation Use Case
Focus Trap Cycle Tab within container Modal dialogs, dropdowns
Focus Restoration Return focus to trigger element Close modal, dropdown, menu
Skip Links Jump to main content Bypass navigation, headers
Roving Tabindex One focusable item in group, arrows move Radio groups, toolbars, tabs

Example: Skip to main content link

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

<!-- Visible on focus only -->
<a href="#main-content" class="skip-link">
  Skip to main content
</a>

<header>...navigation...</header>

<main id="main-content" tabindex="-1">
  <h1>Page Title</h1>
  <!-- Content -->
</main>

Example: Focus trap in modal dialog

<div 
  role="dialog" 
  aria-labelledby="dialog-title"
  aria-modal="true"
  class="modal">
  
  <h2 id="dialog-title">Confirm Action</h2>
  <p>Are you sure you want to continue?</p>
  
  <button id="confirm-btn">Confirm</button>
  <button id="cancel-btn">Cancel</button>
</div>

<script>
class ModalDialog {
  constructor(element) {
    this.modal = element;
    this.focusableElements = this.modal.querySelectorAll(
      'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
    );
    this.firstFocusable = this.focusableElements[0];
    this.lastFocusable = this.focusableElements[this.focusableElements.length - 1];
    this.previousFocus = null;
  }
  
  open() {
    // Store current focus
    this.previousFocus = document.activeElement;
    
    // Show modal
    this.modal.hidden = false;
    
    // Move focus to first element
    this.firstFocusable.focus();
    
    // Add event listeners
    this.modal.addEventListener('keydown', this.handleKeyDown.bind(this));
  }
  
  close() {
    // Hide modal
    this.modal.hidden = true;
    
    // Restore focus
    if (this.previousFocus) {
      this.previousFocus.focus();
    }
    
    // Remove listeners
    this.modal.removeEventListener('keydown', this.handleKeyDown);
  }
  
  handleKeyDown(e) {
    // Close on Escape
    if (e.key === 'Escape') {
      this.close();
      return;
    }
    
    // Focus trap on Tab
    if (e.key === 'Tab') {
      if (e.shiftKey) {
        // Shift + Tab
        if (document.activeElement === this.firstFocusable) {
          e.preventDefault();
          this.lastFocusable.focus();
        }
      } else {
        // Tab
        if (document.activeElement === this.lastFocusable) {
          e.preventDefault();
          this.firstFocusable.focus();
        }
      }
    }
  }
}

// Usage
const modal = new ModalDialog(document.querySelector('.modal'));
document.getElementById('open-modal-btn').addEventListener('click', () => {
  modal.open();
});
document.getElementById('cancel-btn').addEventListener('click', () => {
  modal.close();
});
</script>

Example: Roving tabindex for radio group

<div role="radiogroup" aria-labelledby="group-label">
  <p id="group-label">Choose your favorite color:</p>
  
  <div role="radio" aria-checked="true" tabindex="0">
    Red
  </div>
  <div role="radio" aria-checked="false" tabindex="-1">
    Green
  </div>
  <div role="radio" aria-checked="false" tabindex="-1">
    Blue
  </div>
</div>

<script>
const radioGroup = document.querySelector('[role="radiogroup"]');
const radios = Array.from(radioGroup.querySelectorAll('[role="radio"]'));

radioGroup.addEventListener('keydown', (e) => {
  const current = document.activeElement;
  const currentIndex = radios.indexOf(current);
  let nextIndex;
  
  switch(e.key) {
    case 'ArrowDown':
    case 'ArrowRight':
      e.preventDefault();
      nextIndex = (currentIndex + 1) % radios.length;
      break;
    case 'ArrowUp':
    case 'ArrowLeft':
      e.preventDefault();
      nextIndex = (currentIndex - 1 + radios.length) % radios.length;
      break;
    case ' ':
    case 'Enter':
      e.preventDefault();
      selectRadio(current);
      return;
    default:
      return;
  }
  
  // Update tabindex and focus
  radios[currentIndex].tabIndex = -1;
  radios[nextIndex].tabIndex = 0;
  radios[nextIndex].focus();
});

function selectRadio(radio) {
  radios.forEach(r => r.setAttribute('aria-checked', 'false'));
  radio.setAttribute('aria-checked', 'true');
}

// Click handler
radios.forEach(radio => {
  radio.addEventListener('click', () => selectRadio(radio));
});
</script>
Focus Best Practices: Always show visible focus indicator (:focus styles). Never use outline: none without alternative. Maintain logical tab order (matches visual flow). Use tabindex="0" for custom controls, never positive values. Trap focus in modals. Restore focus when closing overlays. Test with keyboard only (no mouse).

14.4 Alternative Text and Media Descriptions

Element Attribute Purpose Example
<img> alt Describes image content alt="Woman using laptop"
<img> (decorative) alt="" Empty for decorative images alt=""
<video> Text tracks Captions and descriptions <track kind="captions">
<audio> Fallback content Transcript link <a href="transcript.txt">
<figure> <figcaption> Extended description <figcaption>Chart showing...</figcaption>
SVG <title>, <desc> SVG accessibility <title>Icon name</title>
Icon fonts aria-label Describe icon purpose aria-label="Search"
Complex images aria-describedby Link to detailed description aria-describedby="chart-desc"

Example: Image Alt Text Guidelines

<!-- Informative image -->
<img src="graph.png" alt="Sales increased 25% in Q4 2023" />

<!-- Functional image (link/button) -->
<a href="home.html">
  <img src="logo.png" alt="Company Name Home" />
</a>

<!-- Decorative image -->
<img src="divider.png" alt="" role="presentation" />

<!-- Complex image with extended description -->
<figure>
  <img src="chart.png" alt="Revenue chart 2020-2023" 
       aria-describedby="chart-details" />
  <figcaption id="chart-details">
    Bar chart showing revenue growth from $2M in 2020 
    to $8M in 2023, with steady increase each year.
  </figcaption>
</figure>

<!-- Image in text flow -->
<p>
  Our CEO <img src="ceo.jpg" alt="Jane Smith" /> announced 
  the new initiative.
</p>

Example: Video with Captions and Audio Description

<video controls>
  <source src="video.mp4" type="video/mp4" />
  <source src="video.webm" type="video/webm" />
  
  <!-- Captions (subtitles) -->
  <track kind="captions" src="captions-en.vtt" 
         srclang="en" label="English" default />
  <track kind="captions" src="captions-es.vtt" 
         srclang="es" label="Spanish" />
  
  <!-- Audio descriptions for visually impaired -->
  <track kind="descriptions" src="descriptions.vtt" 
         srclang="en" label="Audio Description" />
  
  <!-- Fallback content -->
  <p>Your browser doesn't support video. 
     <a href="transcript.html">Read transcript</a>
  </p>
</video>

<!-- Provide transcript link -->
<p><a href="transcript.html">Full video transcript</a></p>

Example: Accessible SVG and Icon Fonts

<!-- SVG with title and description -->
<svg role="img" aria-labelledby="icon-title icon-desc">
  <title id="icon-title">Success</title>
  <desc id="icon-desc">Green checkmark indicating success</desc>
  <path d="M10 15 L5 10 L7 8 L10 11 L17 4 L19 6 Z" />
</svg>

<!-- Icon font with aria-label -->
<button>
  <i class="icon-trash" aria-hidden="true"></i>
  <span class="sr-only">Delete item</span>
</button>

<!-- Or using aria-label directly -->
<button aria-label="Close dialog">
  <span class="icon-close" aria-hidden="true">&times;</span>
</button>

<!-- Decorative SVG -->
<svg aria-hidden="true" focusable="false">
  <path d="..." />
</svg>
Alt Text Writing Guidelines: Be specific and concise (under 150 characters). Describe the information, not the image itself ("graph showing sales trends" not "graph image"). Include text visible in the image. Don't start with "image of" or "picture of". For functional images (links/buttons), describe the action/destination. Use alt="" for purely decorative images. Provide extended descriptions for complex images (charts, diagrams) using figcaption or aria-describedby.

14.5 Form Accessibility and Error Handling

Technique Implementation Purpose
Label association <label for="id"> Connect labels to inputs
Required fields required + aria-required="true" Indicate mandatory fields
Error messages aria-invalid + aria-describedby Associate errors with fields
Field instructions aria-describedby Provide helpful hints
Fieldset grouping <fieldset> + <legend> Group related inputs
Error summary role="alert" Announce validation errors
Input patterns pattern + title Define expected format
Autocomplete autocomplete attribute Help users fill forms faster

Example: Accessible Form with Validation

<form novalidate>
  <!-- Text input with label and hint -->
  <div class="form-group">
    <label for="username">
      Username <span class="required">*</span>
    </label>
    <input type="text" id="username" name="username" 
           required aria-required="true"
           aria-describedby="username-hint"
           autocomplete="username" />
    <small id="username-hint">
      Must be 3-20 characters, letters and numbers only
    </small>
  </div>

  <!-- Email with error message -->
  <div class="form-group">
    <label for="email">
      Email <span class="required">*</span>
    </label>
    <input type="email" id="email" name="email" 
           required aria-required="true"
           aria-invalid="true"
           aria-describedby="email-error"
           autocomplete="email" />
    <span id="email-error" class="error" role="alert">
      Please enter a valid email address
    </span>
  </div>

  <!-- Radio group -->
  <fieldset>
    <legend>Account type <span class="required">*</span></legend>
    <div>
      <input type="radio" id="personal" name="account" 
             value="personal" required />
      <label for="personal">Personal</label>
    </div>
    <div>
      <input type="radio" id="business" name="account" 
             value="business" />
      <label for="business">Business</label>
    </div>
  </fieldset>

  <!-- Checkbox -->
  <div class="form-group">
    <input type="checkbox" id="terms" name="terms" 
           required aria-required="true"
           aria-describedby="terms-error" />
    <label for="terms">
      I agree to the <a href="terms.html">terms</a>
    </label>
    <span id="terms-error" class="error" role="alert" 
          hidden>
      You must agree to the terms
    </span>
  </div>

  <button type="submit">Create Account</button>
</form>

Example: Error Summary and Focus Management

// Error summary at top of form
<div id="error-summary" class="error-summary" role="alert" 
     tabindex="-1" hidden>
  <h2>Please correct the following errors:</h2>
  <ul>
    <li><a href="#username">Username is required</a></li>
    <li><a href="#email">Email is invalid</a></li>
    <li><a href="#password">Password is too short</a></li>
  </ul>
</div>

<form id="signup-form">
  <!-- Form fields... -->
</form>

<script>
// Validation and error handling
const form = document.getElementById('signup-form');
const errorSummary = document.getElementById('error-summary');

form.addEventListener('submit', function(e) {
  e.preventDefault();
  
  const errors = validateForm();
  
  if (errors.length > 0) {
    // Show error summary
    errorSummary.hidden = false;
    
    // Populate error list
    const errorList = errorSummary.querySelector('ul');
    errorList.innerHTML = errors.map(error => 
      `<li><a href="#${error.field}">${error.message}</a></li>`
    ).join('');
    
    // Focus on error summary
    errorSummary.focus();
    
    // Mark invalid fields
    errors.forEach(error => {
      const field = document.getElementById(error.field);
      field.setAttribute('aria-invalid', 'true');
      
      // Show field-level error
      const errorMsg = document.getElementById(`${error.field}-error`);
      if (errorMsg) {
        errorMsg.hidden = false;
      }
    });
  } else {
    // Submit form
    this.submit();
  }
});

// Clear error on input
form.addEventListener('input', function(e) {
  if (e.target.matches('input, textarea, select')) {
    e.target.setAttribute('aria-invalid', 'false');
    const errorMsg = document.getElementById(`${e.target.id}-error`);
    if (errorMsg) {
      errorMsg.hidden = true;
    }
  }
});
</script>

Example: Autocomplete Attributes

<!-- Personal information -->
<input type="text" name="name" autocomplete="name" />
<input type="text" name="fname" autocomplete="given-name" />
<input type="text" name="lname" autocomplete="family-name" />
<input type="email" name="email" autocomplete="email" />
<input type="tel" name="phone" autocomplete="tel" />

<!-- Address fields -->
<input type="text" autocomplete="street-address" />
<input type="text" autocomplete="address-line1" />
<input type="text" autocomplete="address-line2" />
<input type="text" autocomplete="address-level2" /> <!-- City -->
<input type="text" autocomplete="address-level1" /> <!-- State -->
<input type="text" autocomplete="postal-code" />
<input type="text" autocomplete="country-name" />

<!-- Authentication -->
<input type="text" autocomplete="username" />
<input type="password" autocomplete="current-password" />
<input type="password" autocomplete="new-password" />

<!-- Payment -->
<input type="text" autocomplete="cc-name" />
<input type="text" autocomplete="cc-number" />
<input type="text" autocomplete="cc-exp" />
<input type="text" autocomplete="cc-csc" />
Form Accessibility Best Practices: Always use explicit <label> elements with for attribute. Mark required fields with required attribute AND visual indicator (*). Use aria-required="true" for custom controls. Provide clear, specific error messages associated with fields using aria-describedby. Mark invalid fields with aria-invalid="true". Show errors both at field level and in summary. Focus on error summary or first invalid field on submit. Use fieldset/legend for radio/checkbox groups. Provide instructions before the field, not just in placeholder. Use autocomplete attributes to help users. Test with screen reader and keyboard only.

14.6 Color Contrast and Visual Accessibility

Standard Contrast Ratio Requirement
WCAG 2.1 Level AA 4.5:1 Normal text (under 18pt or 14pt bold)
WCAG 2.1 Level AA 3:1 Large text (18pt+ or 14pt+ bold)
WCAG 2.1 Level AAA 7:1 Normal text (enhanced)
WCAG 2.1 Level AAA 4.5:1 Large text (enhanced)
UI Components 3:1 Graphical objects, form controls
Focus indicators 3:1 Against adjacent colors
Technique Purpose Implementation
Color + pattern Don't rely on color alone Use icons, shapes, patterns, labels
Visible focus Show keyboard focus :focus and :focus-visible styles
Link identification Distinguish from text Underline, bold, or 3:1 contrast + indicator
Text over images Ensure readability Overlay, shadows, sufficient contrast
Resize text Support 200% zoom Relative units (rem, em), no text in images
Reflow 320px width support Responsive design, avoid horizontal scroll
Animation control Reduce motion prefers-reduced-motion media query

Example: Color Contrast Examples

<!-- Good contrast (AA compliant) -->
<p style="color: #333; background: #fff;">
  Dark gray on white (11:1 ratio) ✓
</p>

<button style="color: #fff; background: #0066cc;">
  White on blue (6.5:1 ratio) ✓
</button>

<!-- Insufficient contrast (fails AA) -->
<p style="color: #999; background: #fff;">
  Light gray on white (2.8:1 ratio) ✗
</p>

<a href="#" style="color: #87ceeb; background: #fff;">
  Sky blue on white (2.4:1 ratio) ✗
</a>

<!-- Large text can use lower contrast -->
<h1 style="font-size: 24pt; color: #777; background: #fff;">
  Large heading (3.4:1 ratio) ✓ (AA for large text)
</h1>

Example: Not Relying on Color Alone

<!-- Bad: Color only -->
<p>
  Required fields are in <span style="color: red;">red</span>.
</p>

<!-- Good: Color + symbol -->
<label for="name">
  Name <span class="required" aria-label="required">*</span>
</label>

<!-- Bad: Color-coded status -->
<div class="status-green">Active</div>
<div class="status-red">Inactive</div>

<!-- Good: Color + icon + text -->
<div class="status status-active">
  <svg aria-hidden="true"><!-- checkmark icon --></svg>
  <span>Active</span>
</div>
<div class="status status-inactive">
  <svg aria-hidden="true"><!-- x icon --></svg>
  <span>Inactive</span>
</div>

<!-- Charts: use patterns in addition to colors -->
<svg role="img" aria-label="Sales by region">
  <!-- Use different fill patterns for each segment -->
  <rect fill="url(#pattern-stripes)" />
  <rect fill="url(#pattern-dots)" />
  <defs>
    <pattern id="pattern-stripes">...</pattern>
    <pattern id="pattern-dots">...</pattern>
  </defs>
</svg>

Example: Focus Indicators and Reduced Motion

<style>
/* Visible focus indicator */
a:focus,
button:focus,
input:focus {
  outline: 3px solid #0066cc;
  outline-offset: 2px;
}

/* Enhanced focus for keyboard navigation */
:focus-visible {
  outline: 3px solid #0066cc;
  outline-offset: 2px;
  box-shadow: 0 0 0 4px rgba(0, 102, 204, 0.25);
}

/* Remove outline for mouse clicks (but keep for keyboard) */
:focus:not(:focus-visible) {
  outline: none;
}

/* High contrast links */
a {
  color: #0066cc;
  text-decoration: underline;
  text-underline-offset: 2px;
}

a:hover {
  color: #004499;
  text-decoration-thickness: 2px;
}

/* Respect reduced motion preference */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* Default smooth animations */
@media (prefers-reduced-motion: no-preference) {
  .card {
    transition: transform 0.3s ease, box-shadow 0.3s ease;
  }
  
  .card:hover {
    transform: translateY(-4px);
    box-shadow: 0 8px 16px rgba(0,0,0,0.2);
  }
}

/* Text resize support */
html {
  font-size: 16px; /* Base size */
}

body {
  font-size: 1rem; /* Relative to root */
  line-height: 1.5;
}

h1 { font-size: 2rem; }
h2 { font-size: 1.5rem; }
p { font-size: 1rem; }

/* Ensure text can scale to 200% */
.container {
  max-width: 1200px;
  padding: 0 1rem;
}

@media (max-width: 480px) {
  /* Reflow content, no horizontal scroll */
  .grid {
    grid-template-columns: 1fr;
  }
}
</style>

Example: Text Over Images

<!-- Technique 1: Dark overlay -->
<div class="hero" style="
  background-image: url('photo.jpg');
  background-size: cover;
  position: relative;
">
  <div style="
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.6);
  "></div>
  <h1 style="
    position: relative;
    color: white;
    z-index: 1;
  ">
    Hero Title (Sufficient Contrast)
  </h1>
</div>

<!-- Technique 2: Text shadow -->
<div class="banner" style="background-image: url('bg.jpg');">
  <h2 style="
    color: white;
    text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8),
                 0 0 8px rgba(0, 0, 0, 0.6);
  ">
    Heading with Shadow
  </h2>
</div>

<!-- Technique 3: Background box -->
<div class="card" style="background-image: url('pattern.jpg');">
  <p style="
    background: rgba(255, 255, 255, 0.95);
    padding: 1rem;
    color: #333;
  ">
    Text in semi-transparent box
  </p>
</div>
Visual Accessibility Checklist: Ensure 4.5:1 contrast for normal text, 3:1 for large text and UI components. Test with contrast checker tools. Never use color alone to convey information—add icons, patterns, or text labels. Provide visible focus indicators (minimum 3:1 contrast). Make links distinguishable from regular text (underline or sufficient contrast difference). Support text resize up to 200% without loss of content or functionality. Ensure content reflows at 320px width without horizontal scrolling. Respect prefers-reduced-motion for animations. Avoid text in images; use real text with CSS styling. Test with different zoom levels, color blindness simulators, and high contrast mode.

Section 14: Key Takeaways

  • Semantic HTML: Use proper semantic elements (header, nav, main, article, aside, footer) to create document structure that screen readers can navigate effectively
  • ARIA: Enhance accessibility with ARIA roles, states, and properties when native HTML semantics are insufficient; use aria-label, aria-labelledby, aria-describedby, aria-live
  • Keyboard Navigation: Ensure all interactive elements are keyboard accessible with Tab, Enter, Space, Arrow keys; manage focus properly with tabindex, skip links, and focus traps
  • Alternative Text: Provide meaningful alt text for images (descriptive, concise, under 150 chars); use alt="" for decorative images; provide captions and transcripts for multimedia
  • Form Accessibility: Associate labels with inputs using for attribute; mark required fields; show inline validation errors with aria-invalid and aria-describedby; use autocomplete attributes
  • Color Contrast: Maintain minimum 4.5:1 ratio for normal text, 3:1 for large text and UI components; never rely on color alone—add icons, patterns, or text labels
  • Focus Indicators: Always provide visible focus styles (:focus, :focus-visible) with minimum 3:1 contrast; never remove outlines without accessible alternatives
  • Motion Sensitivity: Respect prefers-reduced-motion media query to disable or reduce animations for users with vestibular disorders
  • Text Scaling: Use relative units (rem, em) to support text resize up to 200%; ensure content reflows at 320px width without horizontal scrolling
  • Testing: Test with keyboard only (no mouse), screen readers (NVDA, JAWS, VoiceOver), color contrast checkers, and browser accessibility tools to verify compliance with WCAG 2.1 Level AA

15. Performance Optimization Techniques

15.1 Critical Resource Loading Strategies

Strategy Implementation Description Use Case
Critical CSS Inline <style>/* critical */</style> Inline above-the-fold CSS directly in HTML head to eliminate render-blocking First meaningful paint optimization for landing pages
Non-Critical CSS Defer media="print" onload="this.media='all'" Load non-critical stylesheets asynchronously without blocking rendering Secondary styles, fonts, animations
Resource Prioritization fetchpriority="high|low|auto" Hint browser priority for resource loading (images, scripts, links) Hero images (high), tracking scripts (low)
Critical Path Optimization Minimize render-blocking resources Reduce number of resources required for initial render Inline critical resources, defer non-critical
Progressive Enhancement Load content incrementally Start with basic HTML/CSS, progressively add features with JavaScript Works on slow connections, graceful degradation

Example: Critical CSS inline with deferred stylesheet loading

<!DOCTYPE html>
<html lang="en">
<head>
  <!-- Inline critical CSS for above-the-fold content -->
  <style>
    body { margin: 0; font-family: system-ui; }
    .hero { min-height: 100vh; background: #007acc; }
  </style>
  
  <!-- Defer non-critical CSS -->
  <link rel="stylesheet" href="main.css" media="print" onload="this.media='all'">
  <noscript><link rel="stylesheet" href="main.css"></noscript>
  
  <!-- Preload key resources -->
  <link rel="preload" href="hero-image.webp" as="image" fetchpriority="high">
</head>
<body>
  <div class="hero">
    <img src="hero-image.webp" alt="Hero" fetchpriority="high">
  </div>
</body>
</html>

15.2 Lazy Loading Images and Media

Technique Syntax Description Browser Support
Native Lazy Loading NEW loading="lazy" Browser defers loading images/iframes until near viewport All modern browsers
Eager Loading loading="eager" Load resource immediately (default behavior) All browsers
Iframe Lazy Loading <iframe loading="lazy"> Defer loading of embedded content (videos, maps) until needed Chrome 76+, Firefox 121+
Intersection Observer API JavaScript-based lazy loading Custom lazy loading with fine-grained control using observer pattern All modern browsers
Placeholder Strategy background: blur|gradient|LQIP Show low-quality placeholder while loading full image Improves perceived performance

Example: Native lazy loading with responsive images

<!-- Basic lazy loading -->
<img src="image.jpg" alt="Description" loading="lazy" width="800" height="600">

<!-- Lazy load with responsive images -->
<img srcset="small.jpg 400w, medium.jpg 800w, large.jpg 1200w"
     sizes="(max-width: 600px) 400px, (max-width: 1000px) 800px, 1200px"
     src="medium.jpg"
     alt="Responsive image"
     loading="lazy"
     width="800"
     height="600">

<!-- Lazy load YouTube embed -->
<iframe src="https://www.youtube.com/embed/VIDEO_ID"
        loading="lazy"
        width="560"
        height="315"
        frameborder="0"
        allow="accelerometer; autoplay; encrypted-media">
</iframe>

<!-- Picture element with lazy loading -->
<picture>
  <source srcset="image.webp" type="image/webp">
  <source srcset="image.jpg" type="image/jpeg">
  <img src="image.jpg" alt="Fallback" loading="lazy" width="800" height="600">
</picture>
Note: Always specify width and height attributes to prevent layout shift (CLS). Use loading="eager" for above-the-fold images (first 2-3 images) and loading="lazy" for below-the-fold content.

15.3 Resource Hints (preload, prefetch, dns-prefetch)

Hint Type Syntax Description Use Case
preload <link rel="preload" href="file" as="type"> High-priority fetch for current page resources needed soon Critical fonts, hero images, essential scripts
prefetch <link rel="prefetch" href="file"> Low-priority fetch for future navigation resources Next page resources, likely user actions
dns-prefetch <link rel="dns-prefetch" href="//domain"> Resolve DNS early for external domains CDNs, analytics, third-party APIs
preconnect <link rel="preconnect" href="//domain"> Establish early connection (DNS + TCP + TLS) Critical third-party resources (fonts, APIs)
prerender <link rel="prerender" href="page.html"> Render entire page in background (deprecated, use Speculation Rules) DEPRECATED
modulepreload <link rel="modulepreload" href="module.js"> Preload ES modules and dependencies JavaScript modules with imports

Example: Comprehensive resource hints implementation

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  
  <!-- DNS prefetch for third-party domains -->
  <link rel="dns-prefetch" href="//fonts.googleapis.com">
  <link rel="dns-prefetch" href="//www.google-analytics.com">
  <link rel="dns-prefetch" href="//cdn.example.com">
  
  <!-- Preconnect to critical third-party origins -->
  <link rel="preconnect" href="//fonts.gstatic.com" crossorigin>
  <link rel="preconnect" href="//api.example.com">
  
  <!-- Preload critical resources -->
  <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="/images/hero.webp" as="image" fetchpriority="high">
  <link rel="preload" href="/js/app.js" as="script">
  
  <!-- Modulepreload for ES modules -->
  <link rel="modulepreload" href="/js/main.mjs">
  <link rel="modulepreload" href="/js/components.mjs">
  
  <!-- Prefetch resources for next navigation -->
  <link rel="prefetch" href="/pages/about.html">
  <link rel="prefetch" href="/css/secondary.css">
  
  <link rel="stylesheet" href="/css/critical.css">
</head>
<body>
  <script src="/js/app.js" defer></script>
</body>
</html>
Preload Best Practices:
  • Limit to 3-5 critical resources
  • Always specify as attribute
  • Use crossorigin for CORS resources
  • Match preload type with actual usage
Common Pitfalls:
  • Over-preloading increases bandwidth usage
  • Preload without usage triggers console warnings
  • Missing as causes double download
  • Preconnect limit: 6 connections maximum

15.4 Script Loading and Async/Defer

Loading Strategy Syntax Behavior Use Case
Default (Blocking) <script src="file.js"> Blocks HTML parsing; executes immediately in order Critical scripts needed before page render (rare)
Async <script src="file.js" async> Downloads parallel to parsing; executes when ready (order not guaranteed) Independent scripts: analytics, ads, tracking
Defer <script src="file.js" defer> Downloads parallel to parsing; executes after DOM ready in order Scripts needing DOM access or dependency order
Module <script type="module" src="file.mjs"> Deferred by default; supports imports/exports Modern ES6+ modules with dependencies
Module + Async <script type="module" async> Execute module as soon as loaded (no order guarantee) Independent modules without dependencies
Inline Scripts <script>/* code */</script> Blocks parsing; executes immediately at position Critical initialization, feature detection
Dynamic Import import('module.js').then() Load modules on-demand at runtime Code splitting, lazy loading features

Example: Script loading strategies comparison

<!DOCTYPE html>
<html lang="en">
<head>
  <!-- Critical inline script (executes immediately) -->
  <script>
    // Feature detection or critical config
    window.APP_CONFIG = { apiUrl: '/api' };
  </script>
  
  <!-- Async: Independent analytics (order doesn't matter) -->
  <script src="https://analytics.com/track.js" async></script>
  <script src="https://ads.com/banner.js" async></script>
</head>
<body>
  <!-- Page content -->
  
  <!-- Defer: DOM-dependent scripts (execute in order after DOM) -->
  <script src="/js/jquery.js" defer></script>
  <script src="/js/plugins.js" defer></script>
  <script src="/js/app.js" defer></script>
  
  <!-- ES6 Module (deferred by default) -->
  <script type="module" src="/js/main.mjs"></script>
  
  <!-- Fallback for older browsers -->
  <script nomodule src="/js/legacy-bundle.js" defer></script>
</body>
</html>
Scenario Recommended Strategy Rationale
Analytics/Tracking async Independent; shouldn't block page rendering; order doesn't matter
Third-party widgets async Self-contained; potential slow loading shouldn't affect main content
Framework bundles defer Needs DOM; maintains execution order for dependencies
UI manipulation scripts defer Requires complete DOM tree; benefits from ordered execution
Modern app code type="module" Native ES6 support; automatic defer; handles dependencies
Feature-specific code dynamic import() Load only when needed; reduces initial bundle size
Performance Impact: Default blocking scripts can delay First Contentful Paint (FCP) by 1-3 seconds. Always use async or defer unless script must execute before rendering. Position scripts at end of <body> as fallback.

15.5 DOM Optimization and Minimal Markup

Technique Description Example Impact
Reduce DOM Depth Keep nesting to minimum (ideally <15 levels) Flatten div soup; use CSS Grid/Flexbox Faster rendering, layout recalculation
Minimize DOM Nodes Target <1500 total nodes per page Remove unnecessary wrappers, divs Lower memory usage, faster querySelector
Semantic HTML Use appropriate elements instead of generic divs/spans <article> vs <div class="article"> Better parsing, accessibility, SEO
Avoid Inline Styles Use CSS classes instead of style attributes class="btn" vs style="..." Reduces HTML size, improves caching
Minify HTML Remove whitespace, comments in production Use build tools (HTMLMinifier, Terser) 10-30% size reduction
Lazy Render Initially render only visible content Virtual scrolling for long lists Faster initial load, lower memory
Fragment Assembly Batch DOM insertions using DocumentFragment Build in memory, insert once Prevents multiple reflows
Attribute Optimization Remove default attributes, use shorthand type="text" is default for input Smaller HTML payload

Bad: Excessive DOM depth and nodes

<div class="wrapper">
  <div class="container">
    <div class="row">
      <div class="col">
        <div class="card">
          <div class="card-header">
            <div class="title">
              <h2>Title</h2>
            </div>
          </div>
          <div class="card-body">
            <p>Content</p>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

Good: Minimal, semantic markup

<article class="card">
  <h2>Title</h2>
  <p>Content</p>
</article>

<!-- CSS handles layout -->
<style>
.card {
  display: grid;
  gap: 1rem;
  padding: 1rem;
}
</style>
Performance Metrics: Each additional 1000 DOM nodes increases memory by ~1MB and slows layout by 10-15ms. Deep nesting (>20 levels) exponentially impacts CSS selector matching and style calculation.

15.6 Content Delivery and Caching Headers

Header/Technique Syntax/Value Description Recommended Value
Cache-Control Cache-Control: max-age=31536000 Define caching behavior and expiration time Immutable assets: 1 year; Dynamic: no-cache
Immutable Cache-Control: immutable Resource will never change at this URL Versioned/fingerprinted assets
ETag ETag: "abc123" Validate cached resource with server hash Dynamic content, APIs
Last-Modified Last-Modified: date Timestamp of last resource modification Static files without versioning
Compression Content-Encoding: gzip|br Compress text resources (HTML, CSS, JS) Brotli (br) preferred; gzip fallback
CDN Content Delivery Network Distribute static assets globally Cloudflare, CloudFront, Fastly
HTTP/2 Server Push Link: </css/main.css>; rel=preload Push critical resources before request Critical CSS, fonts (use sparingly)
Service Worker Cache JavaScript caching layer Programmatic cache control, offline support Progressive Web Apps (PWAs)

Example: HTML meta tags for caching and compression

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  
  <!-- Prevent caching of HTML (always fetch fresh) -->
  <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
  <meta http-equiv="Pragma" content="no-cache">
  <meta http-equiv="Expires" content="0">
  
  <!-- Versioned assets can be cached long-term -->
  <link rel="stylesheet" href="/css/main.abc123.css">
  <script src="/js/app.def456.js" defer></script>
  
  <!-- CDN-hosted resources -->
  <link rel="stylesheet" href="https://cdn.example.com/lib/v1.0.0/styles.css">
</head>
<body>
  <!-- Register service worker for caching -->
  <script>
    if ('serviceWorker' in navigator) {
      navigator.serviceWorker.register('/sw.js');
    }
  </script>
</body>
</html>
Asset Type Cache Strategy Cache-Control Value Rationale
HTML Pages No cache or short TTL no-cache or max-age=300 Ensure users get latest content and metadata
Versioned CSS/JS Long-term cache max-age=31536000, immutable Filename changes on update; safe to cache indefinitely
Images Long cache with validation max-age=86400 + ETag Large files benefit from caching; validate occasionally
Fonts Long-term cache max-age=31536000 Rarely change; large files; cache indefinitely
API Responses Conditional caching no-cache + ETag Validate on each request but avoid full download

Performance Optimization Checklist

Core Web Vitals: Focus on Largest Contentful Paint (LCP <2.5s), First Input Delay (FID <100ms), and Cumulative Layout Shift (CLS <0.1). These metrics directly impact SEO rankings and user experience.

16. Security Best Practices

16.1 Content Security Policy Implementation

CSP Directive Syntax Description Example Value
default-src default-src 'self' Fallback for all fetch directives; restricts all resources to same origin 'self' https://cdn.example.com
script-src script-src 'self' 'nonce-xyz' Controls valid sources for JavaScript execution 'self' 'unsafe-inline' https://apis.google.com
style-src style-src 'self' 'sha256-hash' Defines valid sources for stylesheets 'self' 'unsafe-inline' https://fonts.googleapis.com
img-src img-src 'self' data: https: Restricts sources for images; supports data URIs and wildcards 'self' data: https://*.cdn.com
connect-src connect-src 'self' wss: Controls fetch, XMLHttpRequest, WebSocket connections 'self' https://api.example.com wss://socket.io
font-src font-src 'self' data: Specifies valid sources for fonts 'self' https://fonts.gstatic.com data:
frame-src frame-src 'none' Restricts iframe embedding sources 'self' https://www.youtube.com
base-uri base-uri 'self' Limits URLs that can be used in <base> element 'self' (prevents base tag injection)
form-action form-action 'self' Restricts form submission targets 'self' https://secure-payment.com
upgrade-insecure-requests upgrade-insecure-requests Automatically upgrade HTTP requests to HTTPS No value needed (directive only)

Example: CSP implementation in HTML meta tag

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  
  <!-- Content Security Policy -->
  <meta http-equiv="Content-Security-Policy" 
        content="default-src 'self'; 
                 script-src 'self' 'nonce-2726c7f26c' https://cdn.example.com; 
                 style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; 
                 img-src 'self' data: https:; 
                 font-src 'self' https://fonts.gstatic.com; 
                 connect-src 'self' https://api.example.com; 
                 frame-src 'none'; 
                 base-uri 'self'; 
                 form-action 'self'; 
                 upgrade-insecure-requests;">
  
  <!-- Script with matching nonce -->
  <script nonce="2726c7f26c">
    console.log('This script is allowed by CSP');
  </script>
  
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto">
</head>
<body>
  <img src="image.jpg" alt="Allowed">
  <img src="https://external.com/image.jpg" alt="Also allowed">
</body>
</html>
CSP Keywords:
  • 'self' - Same origin only
  • 'none' - Block all sources
  • 'unsafe-inline' - Allow inline scripts/styles (avoid)
  • 'unsafe-eval' - Allow eval() (avoid)
  • 'nonce-{random}' - Cryptographic nonce for specific resources
  • 'sha256-{hash}' - Whitelist by content hash
Security Warning: Avoid 'unsafe-inline' and 'unsafe-eval' in production. Use nonces or hashes for inline scripts. Implement CSP in report-only mode first to detect violations before enforcing.

16.2 XSS Prevention and Input Sanitization

Technique Implementation Description Protection Level
HTML Entity Encoding Escape < > & " ' Convert special characters to HTML entities before output Essential - Prevents most XSS
Attribute Encoding Encode attribute values Escape quotes and special chars in HTML attributes Required for attribute injection
JavaScript Encoding Escape for JS context Properly encode data inserted into JavaScript Critical for dynamic JS
URL Encoding encodeURIComponent() Encode user input before adding to URLs Prevents URL injection
Content-Type Header text/html; charset=UTF-8 Explicitly set content type to prevent MIME sniffing Defense in depth
X-Content-Type-Options nosniff Prevent browser from MIME-sniffing responses Prevents XSS via uploads
DOMPurify Library Sanitize untrusted HTML Remove dangerous elements/attributes from user HTML For rich text content
Trusted Types API NEW Enforce type checking for DOM sinks Browser-enforced XSS protection for innerHTML, eval, etc. Chrome 83+, Edge 83+

Example: XSS prevention techniques

<!-- VULNERABLE: Direct user input insertion -->
<div id="output"></div>
<script>
  // NEVER do this:
  const userInput = '<img src=x onerror=alert("XSS")>';
  document.getElementById('output').innerHTML = userInput; // XSS!
</script>

<!-- SAFE: Use textContent for plain text -->
<div id="safe-output"></div>
<script>
  const userInput = '<img src=x onerror=alert("XSS")>';
  document.getElementById('safe-output').textContent = userInput; // Safe!
  // Output: &lt;img src=x onerror=alert("XSS")&gt;
</script>

<!-- SAFE: HTML entity encoding (server-side) -->
<div>
  <!-- User input: <script>alert('XSS')</script> -->
  <!-- Rendered: &lt;script&gt;alert('XSS')&lt;/script&gt; -->
  &lt;script&gt;alert('XSS')&lt;/script&gt;
</div>

<!-- SAFE: DOMPurify for rich content -->
<script src="https://cdn.jsdelivr.net/npm/dompurify/dist/purify.min.js"></script>
<script>
  const dirtyHTML = '<p>Safe content</p><script>alert("XSS")</script>';
  const clean = DOMPurify.sanitize(dirtyHTML);
  document.body.innerHTML = clean; // Only <p> tag remains
</script>

<!-- SAFE: Attribute encoding -->
<input type="text" value="user-input-here" data-user="escaped-value">

<!-- SAFE: URL encoding -->
<script>
  const searchTerm = 'user&input';
  const url = '/search?q=' + encodeURIComponent(searchTerm);
  // Result: /search?q=user%26input
</script>
Common XSS Attack Vectors:
  • innerHTML, outerHTML, document.write() with unsanitized input
  • Event handlers in attributes: <img onerror="...">, <body onload="...">
  • JavaScript URLs: <a href="javascript:alert(1)">
  • Data URIs: <img src="data:text/html,<script>...">
  • SVG script injection: <svg><script>alert(1)</script></svg>
  • CSS expression injection (IE legacy): style="expression(alert(1))"

16.3 CSRF Protection in Forms

Protection Method Implementation Description Effectiveness
CSRF Token <input type="hidden" name="csrf_token" value="random"> Server-generated unique token per session/request Most effective
SameSite Cookie Attribute Set-Cookie: session=abc; SameSite=Strict Prevent cookie from being sent in cross-site requests Strong defense
Double Submit Cookie Send token in both cookie and request body Server validates both match; no server-side state needed Stateless alternative
Custom Request Header X-Requested-With: XMLHttpRequest AJAX requests include custom header; can't be set cross-origin For API endpoints
Origin/Referer Validation Check Origin/Referer headers match Verify request comes from same origin Supplementary check
Re-authentication Require password for sensitive actions User must confirm identity for critical operations High-value transactions

Example: CSRF protection implementation

<!-- Method 1: Synchronizer Token Pattern -->
<form action="/transfer" method="POST">
  <!-- Server generates unique token per session -->
  <input type="hidden" name="csrf_token" value="8f7d6c5b4a3e2d1c">
  
  <label>Amount: <input type="number" name="amount" required></label>
  <label>To: <input type="text" name="recipient" required></label>
  <button type="submit">Transfer</button>
</form>

<!-- Method 2: Double Submit Cookie Pattern -->
<!-- Server sets cookie: csrf_token=abc123 -->
<form action="/delete-account" method="POST">
  <!-- Same value as cookie -->
  <input type="hidden" name="csrf_token" value="abc123">
  <button type="submit">Delete Account</button>
</form>

<!-- Method 3: SameSite Cookie (server response header) -->
<!-- 
  Set-Cookie: sessionId=xyz789; SameSite=Strict; Secure; HttpOnly
  
  SameSite values:
  - Strict: Never sent in cross-site requests
  - Lax: Sent on top-level navigation (GET only)
  - None: Always sent (requires Secure flag)
-->

<!-- Method 4: Custom header for AJAX -->
<script>
  fetch('/api/delete-user', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content,
      'X-Requested-With': 'XMLHttpRequest'
    },
    body: JSON.stringify({ userId: 123 })
  });
</script>

<!-- Store CSRF token in meta tag for JavaScript access -->
<meta name="csrf-token" content="8f7d6c5b4a3e2d1c">
Best Practices: Use multiple layers: SameSite cookies (Lax or Strict) + CSRF tokens for state-changing operations. For sensitive actions (money transfer, password change), add re-authentication. Always use POST/PUT/DELETE for state changes, never GET.

16.4 Secure Embedding and Sandboxing

Attribute/Directive Syntax Description Security Impact
sandbox (iframe) <iframe sandbox> Enable all restrictions (most secure default) Blocks scripts, forms, popups, same-origin access
sandbox="allow-scripts" <iframe sandbox="allow-scripts"> Allow JavaScript execution only Scripts can run but can't submit forms or open popups
sandbox="allow-same-origin" <iframe sandbox="allow-same-origin"> Allow same-origin access Can access cookies/storage of parent if same origin
sandbox="allow-forms" <iframe sandbox="allow-forms"> Allow form submission Forms can be submitted
sandbox="allow-popups" <iframe sandbox="allow-popups"> Allow popup windows Can open new windows/tabs
sandbox="allow-top-navigation" <iframe sandbox="allow-top-navigation"> Allow navigating top-level window Can change parent page URL (risky)
X-Frame-Options X-Frame-Options: DENY Prevent your page from being embedded Protects against clickjacking attacks
frame-ancestors (CSP) frame-ancestors 'none' Modern alternative to X-Frame-Options More flexible; supports multiple origins

Example: Secure iframe embedding

<!-- Most restrictive: Completely sandboxed (no scripts, forms, or navigation) -->
<iframe src="untrusted.html" sandbox></iframe>

<!-- Allow scripts but nothing else -->
<iframe src="third-party-widget.html" 
        sandbox="allow-scripts">
</iframe>

<!-- Allow scripts and forms (common for embedded content) -->
<iframe src="https://external.com/embed" 
        sandbox="allow-scripts allow-forms allow-popups"
        loading="lazy">
</iframe>

<!-- DANGEROUS: allow-same-origin + allow-scripts = no sandbox protection! -->
<!-- Iframe can remove its own sandbox attribute via JavaScript -->
<iframe src="malicious.html" 
        sandbox="allow-same-origin allow-scripts"> <!-- AVOID THIS -->
</iframe>

<!-- Prevent your page from being embedded (clickjacking protection) -->
<meta http-equiv="X-Frame-Options" content="DENY">
<!-- Or allow only same origin: -->
<meta http-equiv="X-Frame-Options" content="SAMEORIGIN">

<!-- Modern CSP alternative (preferred) -->
<meta http-equiv="Content-Security-Policy" 
      content="frame-ancestors 'none';">
<!-- Or whitelist specific origins: -->
<meta http-equiv="Content-Security-Policy" 
      content="frame-ancestors 'self' https://trusted.com;">

<!-- Secure YouTube embed example -->
<iframe src="https://www.youtube-nocookie.com/embed/VIDEO_ID"
        sandbox="allow-scripts allow-same-origin allow-presentation"
        allow="accelerometer; encrypted-media; gyroscope; picture-in-picture"
        loading="lazy"
        width="560"
        height="315">
</iframe>
Critical Security Rule: NEVER combine allow-same-origin and allow-scripts when embedding untrusted content. This combination allows the iframe to remove its own sandbox restrictions, completely bypassing security.

16.5 HTTP Security Headers

Header Value/Directive Description Implementation
Strict-Transport-Security (HSTS) max-age=31536000; includeSubDomains; preload Force HTTPS for all future requests Essential for HTTPS sites
X-Content-Type-Options nosniff Prevent MIME-type sniffing Always enable
X-Frame-Options DENY | SAMEORIGIN Clickjacking protection (legacy, use CSP) Use with CSP frame-ancestors
X-XSS-Protection 0 Disable legacy XSS filter (can cause vulnerabilities) Set to 0
Referrer-Policy strict-origin-when-cross-origin Control Referer header information leakage Recommended
Permissions-Policy geolocation=(), camera=(), microphone=() Control browser features and APIs access Feature restrictions
Cross-Origin-Embedder-Policy (COEP) require-corp Require explicit opt-in for cross-origin resources Enables SharedArrayBuffer
Cross-Origin-Opener-Policy (COOP) same-origin Isolate browsing context from cross-origin windows Spectre mitigation
Cross-Origin-Resource-Policy (CORP) same-site | same-origin | cross-origin Control which sites can embed resource Resource isolation

Example: Security headers in HTML meta tags

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  
  <!-- Content Security Policy -->
  <meta http-equiv="Content-Security-Policy" 
        content="default-src 'self'; script-src 'self' 'nonce-xyz'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; frame-ancestors 'none';">
  
  <!-- Prevent MIME sniffing -->
  <meta http-equiv="X-Content-Type-Options" content="nosniff">
  
  <!-- Clickjacking protection -->
  <meta http-equiv="X-Frame-Options" content="DENY">
  
  <!-- Disable legacy XSS filter (prevents vulnerabilities) -->
  <meta http-equiv="X-XSS-Protection" content="0">
  
  <!-- Referrer policy -->
  <meta name="referrer" content="strict-origin-when-cross-origin">
  
  <!-- Permissions policy (control browser features) -->
  <meta http-equiv="Permissions-Policy" 
        content="geolocation=(), camera=(), microphone=(), payment=()">
</head>
<body>
  <!-- Content -->
</body>
</html>

<!-- Note: While meta tags work for CSP and some headers, 
     HTTP response headers are preferred and more reliable.
     Configure these in server settings:
     
     Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
     X-Content-Type-Options: nosniff
     X-Frame-Options: DENY
     Content-Security-Policy: default-src 'self'
     Referrer-Policy: strict-origin-when-cross-origin
     Permissions-Policy: geolocation=(), camera=()
     Cross-Origin-Opener-Policy: same-origin
     Cross-Origin-Embedder-Policy: require-corp
-->
Referrer-Policy Value Behavior Use Case
no-referrer Never send Referer header Maximum privacy, may break analytics
no-referrer-when-downgrade Send full URL except HTTPS→HTTP Default browser behavior
origin Send only origin (no path) Balance privacy and functionality
strict-origin-when-cross-origin Full URL for same-origin; origin only cross-origin Recommended default
same-origin Send Referer only to same origin High privacy, may affect external integrations

16.6 Form Security and Data Protection

Security Measure Implementation Description Protection Against
HTTPS Only <form action="https://..." method="POST"> Always submit forms over encrypted connection Man-in-the-middle, eavesdropping
autocomplete="off" <input autocomplete="off"> Disable browser autofill for sensitive fields Credential theft from shared computers
autocomplete="new-password" <input type="password" autocomplete="new-password"> Prevent autofill of existing passwords Password manager confusion
maxlength Attribute <input maxlength="50"> Limit input length (client-side) Buffer overflow, DoS (supplement server validation)
input type Validation <input type="email|url|tel"> Browser validates input format Malformed data (basic validation only)
pattern Attribute <input pattern="[A-Za-z]{3,}"> Regex validation on client side Invalid formats (must validate server-side too)
novalidate Attribute <form novalidate> Disable HTML5 validation (use custom JS) N/A - allows custom validation logic
rel="noopener noreferrer" <form target="_blank" rel="noopener"> Prevent window.opener access Reverse tabnabbing attacks
Rate Limiting Server-side throttling Limit submissions per IP/user Brute force, spam, DoS attacks
CAPTCHA reCAPTCHA, hCaptcha integration Distinguish humans from bots Automated spam, credential stuffing

Example: Secure form implementation

<!-- Secure login form -->
<form action="https://example.com/login" 
      method="POST" 
      autocomplete="off">
  
  <!-- CSRF token -->
  <input type="hidden" name="csrf_token" value="secure-random-token">
  
  <!-- Username/email field -->
  <label for="email">Email:</label>
  <input type="email" 
         id="email" 
         name="email" 
         required 
         autocomplete="username"
         maxlength="100"
         pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$">
  
  <!-- Password field -->
  <label for="password">Password:</label>
  <input type="password" 
         id="password" 
         name="password" 
         required 
         autocomplete="current-password"
         minlength="8"
         maxlength="128">
  
  <button type="submit">Login</button>
</form>

<!-- Secure registration form -->
<form action="https://example.com/register" 
      method="POST" 
      autocomplete="off">
  
  <input type="hidden" name="csrf_token" value="secure-random-token">
  
  <label for="new-email">Email:</label>
  <input type="email" 
         id="new-email" 
         name="email" 
         required 
         autocomplete="email"
         maxlength="100">
  
  <label for="new-password">Password:</label>
  <input type="password" 
         id="new-password" 
         name="password" 
         required 
         autocomplete="new-password"
         minlength="8"
         aria-describedby="password-requirements">
  
  <div id="password-requirements" class="note">
    Must be at least 8 characters with uppercase, lowercase, number, and special character
  </div>
  
  <!-- CAPTCHA -->
  <div class="g-recaptcha" data-sitekey="your-site-key"></div>
  
  <button type="submit">Register</button>
</form>

<!-- Payment form (extra security) -->
<form action="https://secure-payment.com/process" 
      method="POST" 
      autocomplete="off"
      target="_blank"
      rel="noopener noreferrer">
  
  <input type="hidden" name="csrf_token" value="secure-token">
  
  <label for="card">Card Number:</label>
  <input type="text" 
         id="card" 
         name="card_number" 
         required 
         autocomplete="cc-number"
         pattern="[0-9]{13,19}"
         maxlength="19"
         inputmode="numeric">
  
  <label for="cvv">CVV:</label>
  <input type="password" 
         id="cvv" 
         name="cvv" 
         required 
         autocomplete="cc-csc"
         pattern="[0-9]{3,4}"
         maxlength="4"
         inputmode="numeric">
  
  <button type="submit">Pay Securely</button>
</form>
Autocomplete Values for Security:
  • username - Email/username field
  • current-password - Login password
  • new-password - Registration/change password
  • one-time-code - 2FA/OTP codes
  • cc-number - Credit card number
  • cc-csc - Card security code
  • off - Disable completely (sensitive data)
Never Trust Client-Side Validation:
  • Always validate and sanitize on server
  • HTML5 validation can be bypassed
  • Use client validation for UX only
  • Implement server-side rate limiting
  • Log and monitor suspicious activity
  • Never store sensitive data in localStorage

Security Best Practices Checklist

Critical Reminders: Client-side security is defense in depth only - never trust the client. Always validate, sanitize, and authorize on the server. Security is a process, not a feature - regular audits and updates are essential.

17. Internationalization and Localization

17.1 Language Declaration and BCP 47 Tags

Attribute/Tag Syntax Description Example
lang (HTML element) <html lang="en"> Primary language of the entire document <html lang="en-US"> - English (United States)
lang (on any element) <span lang="fr">Bonjour</span> Override language for specific content section Multi-language pages, foreign quotes
hreflang (links) <link rel="alternate" hreflang="es"> Language of linked resource Alternate language versions for SEO
BCP 47 Language Tag language-Script-REGION-variant Standard format for language identification zh-Hans-CN - Chinese, Simplified, China
Primary Language 2-3 letter code (ISO 639) Base language identifier en, fr, de, ja, ar
Script Subtag 4-letter code (ISO 15924) Writing system specification Hans (Simplified), Hant (Traditional), Latn (Latin)
Region Subtag 2-letter or 3-digit code Country/region specification US, GB, CN, BR

Example: Language declaration with BCP 47 tags

<!DOCTYPE html>
<!-- Primary language: English (United States) -->
<html lang="en-US">
<head>
  <meta charset="UTF-8">
  <title>Multilingual Website</title>
  
  <!-- Alternate language versions for SEO -->
  <link rel="alternate" hreflang="en-US" href="https://example.com/en-us/">
  <link rel="alternate" hreflang="en-GB" href="https://example.com/en-gb/">
  <link rel="alternate" hreflang="fr-FR" href="https://example.com/fr/">
  <link rel="alternate" hreflang="de-DE" href="https://example.com/de/">
  <link rel="alternate" hreflang="es-ES" href="https://example.com/es/">
  <link rel="alternate" hreflang="ja-JP" href="https://example.com/ja/">
  <link rel="alternate" hreflang="zh-Hans-CN" href="https://example.com/zh-cn/">
  <link rel="alternate" hreflang="zh-Hant-TW" href="https://example.com/zh-tw/">
  <link rel="alternate" hreflang="ar-SA" href="https://example.com/ar/">
  <link rel="alternate" hreflang="x-default" href="https://example.com/">
</head>
<body>
  <p>Welcome to our website.</p>
  
  <!-- Foreign language quote -->
  <blockquote lang="fr-FR">
    <p>La vie est belle.</p>
  </blockquote>
  
  <!-- Mixed language content -->
  <p>The Japanese word <span lang="ja">こんにちは</span> means "hello".</p>
  
  <!-- Arabic text section -->
  <section lang="ar-SA" dir="rtl">
    <h2>مرحبا</h2>
    <p>هذا نص باللغة العربية</p>
  </section>
</body>
</html>
Common Language Codes BCP 47 Tag Description Script/Region Notes
English (US) en-US American English Default English variant
English (UK) en-GB British English Different spelling (colour vs color)
Spanish (Spain) es-ES Castilian Spanish Differs from Latin American Spanish
Spanish (Mexico) es-MX Mexican Spanish Different vocabulary and idioms
Chinese (Simplified) zh-Hans-CN Mainland China Hans = Simplified characters
Chinese (Traditional) zh-Hant-TW Taiwan Hant = Traditional characters
Arabic (Saudi Arabia) ar-SA Saudi Arabic RTL text direction required
Portuguese (Brazil) pt-BR Brazilian Portuguese Distinct from European Portuguese
French (Canada) fr-CA Canadian French Different from France French
SEO Tip: Use hreflang="x-default" to specify the default version when no language matches. Always include bidirectional links (page A links to B, page B links to A) for proper SEO. Google requires consistency across all language versions.

17.2 Text Direction (LTR, RTL) and BiDi Support

Attribute Value Description Languages
dir="ltr" Left-to-Right Default text direction for most languages English, French, Spanish, German, Chinese, Japanese, Korean
dir="rtl" Right-to-Left Text flows from right to left Arabic, Hebrew, Persian, Urdu
dir="auto" Automatic direction Browser determines direction from first strong character User-generated content, mixed-direction text
BiDi Isolation <bdi> element Isolate text with different directionality Usernames, product names in RTL contexts
BiDi Override <bdo dir="rtl"> element Force specific text direction Override browser's direction algorithm
CSS direction direction: rtl; CSS property for text direction Styling RTL layouts
Logical Properties margin-inline-start Direction-agnostic CSS properties Adapts to LTR/RTL automatically

Example: RTL and BiDi text handling

<!-- RTL document (Arabic) -->
<html lang="ar" dir="rtl">
<head>
  <meta charset="UTF-8">
  <title>موقع عربي</title>
  <style>
    /* Direction-agnostic CSS using logical properties */
    .card {
      margin-inline-start: 1rem; /* Left in LTR, Right in RTL */
      margin-inline-end: 1rem;   /* Right in LTR, Left in RTL */
      padding-inline: 2rem;      /* Horizontal padding */
      border-inline-start: 2px solid blue; /* Left border in LTR */
    }
  </style>
</head>
<body>
  <h1>مرحبا بكم</h1>
  <p>هذا نص يُكتب من اليمين إلى اليسار</p>
  
  <!-- Embedding LTR text (English) in RTL context -->
  <p>
    المؤلف هو <bdi>John Smith</bdi> من أمريكا
  </p>
  
  <!-- User-generated content with auto direction -->
  <div class="comment" dir="auto">
    <!-- Browser detects direction from first strong character -->
    <p>This will be LTR</p>
  </div>
  
  <div class="comment" dir="auto">
    <p>هذا سيكون RTL</p>
  </div>
</body>
</html>

<!-- LTR document with embedded RTL content -->
<html lang="en" dir="ltr">
<body>
  <h1>Learning Arabic</h1>
  
  <!-- RTL section in LTR document -->
  <section lang="ar" dir="rtl">
    <h2>الأرقام العربية</h2>
    <p>١، ٢، ٣، ٤، ٥</p>
  </section>
  
  <!-- Bidirectional text with isolation -->
  <p>User <bdi>أحمد</bdi> has 15 messages.</p>
  
  <!-- Force direction override (rare use case) -->
  <p>Display in reverse: <bdo dir="rtl">ABCDEF</bdo> becomes FEDCBA</p>
</body>
</html>
CSS Logical Property Physical Equivalent (LTR) Physical Equivalent (RTL) Use Case
margin-inline-start margin-left margin-right Start margin (direction-aware)
margin-inline-end margin-right margin-left End margin (direction-aware)
padding-inline-start padding-left padding-right Start padding
border-inline-start border-left border-right Start border
inset-inline-start left right Positioning (absolute/fixed)
text-align: start text-align: left text-align: right Direction-aware alignment
text-align: end text-align: right text-align: left Opposite side alignment
RTL Common Pitfalls: Avoid using physical properties (left/right) in CSS for international sites. Use logical properties (inline-start/inline-end) instead. Icons and arrows may need to be flipped in RTL. Test with actual RTL content, not just reversing English text.

17.3 Character Encoding and Unicode Support

Encoding Syntax Description Coverage
UTF-8 <meta charset="UTF-8"> Universal character encoding (recommended) All languages, emojis, symbols
UTF-16 <meta charset="UTF-16"> 16-bit Unicode encoding Less efficient than UTF-8 for web
ISO-8859-1 (Latin-1) <meta charset="ISO-8859-1"> Western European languages only Legacy - use UTF-8
HTML Entities (Decimal) &#8364; Numeric character reference (€) Any Unicode character by code point
HTML Entities (Hex) &#x20AC; Hexadecimal character reference (€) Same as decimal, hex format
Named Entities &euro; &copy; Named character references Limited set of common characters
Unicode Escape (CSS/JS) \20AC (CSS), \u20AC (JS) Unicode escape sequences In stylesheets and scripts

Example: Character encoding and special characters

<!DOCTYPE html>
<html lang="en">
<head>
  <!-- ALWAYS declare UTF-8 encoding first -->
  <meta charset="UTF-8">
  <title>Unicode Characters</title>
</head>
<body>
  <!-- Direct UTF-8 characters (recommended with UTF-8) -->
  <p>Currency: € £ ¥ ₹ ₽</p>
  <p>Symbols: © ® ™ § ¶ † ‡</p>
  <p>Math: × ÷ ± ≠ ≈ ∞ √ ∑</p>
  <p>Arrows: ← → ↑ ↓ ↔ ⇒ ⇔</p>
  <p>Emojis: 😀 😂 ❤️ 👍 🌟 🎉</p>
  
  <!-- HTML entities (decimal) -->
  <p>Euro: &#8364; (€)</p>
  <p>Copyright: &#169; (©)</p>
  <p>Heart: &#10084; (❤)</p>
  
  <!-- HTML entities (hexadecimal) -->
  <p>Euro: &#x20AC; (€)</p>
  <p>Em dash: &#x2014; (—)</p>
  <p>Bullet: &#x2022; (•)</p>
  
  <!-- Named entities -->
  <p>&euro; &copy; &reg; &trade;</p>
  <p>&lt; &gt; &amp; &quot; &apos;</p>
  <p>&nbsp; &mdash; &ndash; &hellip;</p>
  
  <!-- Diacritics and accents -->
  <p>French: café, naïve, résumé</p>
  <p>German: Grüße, Äpfel, Größe</p>
  <p>Spanish: ñ, á, é, í, ó, ú</p>
  
  <!-- Non-Latin scripts -->
  <p lang="el">Greek: Ελληνικά</p>
  <p lang="ru">Russian: Русский</p>
  <p lang="ar" dir="rtl">Arabic: العربية</p>
  <p lang="he" dir="rtl">Hebrew: עברית</p>
  <p lang="hi">Hindi: हिन्दी</p>
  <p lang="th">Thai: ไทย</p>
  <p lang="ja">Japanese: 日本語</p>
  <p lang="ko">Korean: 한국어</p>
  <p lang="zh">Chinese: 中文</p>
</body>
</html>
Common HTML Entities Named Entity Decimal Hex Character
Non-breaking space &nbsp; &#160; &#xA0;
Em dash &mdash; &#8212; &#x2014;
En dash &ndash; &#8211; &#x2013;
Ellipsis &hellip; &#8230; &#x2026;
Left quote &ldquo; &#8220; &#x201C; "
Right quote &rdquo; &#8221; &#x201D; "
Apostrophe &rsquo; &#8217; &#x2019; '
Bullet &bull; &#8226; &#x2022;
Best Practice: Always use UTF-8 encoding. Direct Unicode characters are preferred over entities when using UTF-8. Use entities only for HTML-reserved characters (<, >, &, ") or when forced to use legacy encodings.

17.4 Date, Time, and Number Formatting

Element/Attribute Syntax Description Use Case
<time> element <time datetime="2025-12-22">Dec 22, 2025</time> Machine-readable date/time with human-readable display Events, publication dates, timestamps
datetime attribute datetime="2025-12-22T14:30:00Z" ISO 8601 format for dates and times Search engines, screen readers, scripts
Intl.DateTimeFormat (JS) new Intl.DateTimeFormat('en-US') JavaScript API for locale-specific formatting Dynamic date display based on user locale
Intl.NumberFormat (JS) new Intl.NumberFormat('de-DE') Format numbers according to locale Prices, quantities, percentages
Intl.RelativeTimeFormat (JS) new Intl.RelativeTimeFormat('en') Format relative time ("2 days ago") Social media timestamps, activity feeds
input type="date" <input type="date"> Native date picker with locale formatting Forms with date selection
input type="time" <input type="time"> Native time picker Appointment scheduling, time entry

Example: Date, time, and number formatting

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Localized Formatting</title>
</head>
<body>
  <!-- Time element with various formats -->
  <p>Published: <time datetime="2025-12-22T14:30:00Z">December 22, 2025 at 2:30 PM</time></p>
  <p>Event date: <time datetime="2025-12-31">New Year's Eve 2025</time></p>
  <p>Duration: <time datetime="PT2H30M">2 hours 30 minutes</time></p>
  
  <!-- Form with date/time inputs -->
  <form>
    <label>Appointment Date: <input type="date" name="date"></label>
    <label>Appointment Time: <input type="time" name="time"></label>
    <label>Birth Month: <input type="month" name="birth-month"></label>
    <label>Week: <input type="week" name="week"></label>
  </form>
  
  <!-- JavaScript locale-aware formatting -->
  <script>
    const date = new Date('2025-12-22T14:30:00Z');
    
    // Date formatting for different locales
    const usDate = new Intl.DateTimeFormat('en-US', {
      year: 'numeric', month: 'long', day: 'numeric'
    }).format(date); // December 22, 2025
    
    const ukDate = new Intl.DateTimeFormat('en-GB', {
      year: 'numeric', month: 'long', day: 'numeric'
    }).format(date); // 22 December 2025
    
    const frDate = new Intl.DateTimeFormat('fr-FR', {
      year: 'numeric', month: 'long', day: 'numeric'
    }).format(date); // 22 décembre 2025
    
    const jpDate = new Intl.DateTimeFormat('ja-JP', {
      year: 'numeric', month: 'long', day: 'numeric'
    }).format(date); // 2025年12月22日
    
    // Number formatting
    const price = 1234567.89;
    
    const usPrice = new Intl.NumberFormat('en-US', {
      style: 'currency', currency: 'USD'
    }).format(price); // $1,234,567.89
    
    const euPrice = new Intl.NumberFormat('de-DE', {
      style: 'currency', currency: 'EUR'
    }).format(price); // 1.234.567,89 €
    
    const jpPrice = new Intl.NumberFormat('ja-JP', {
      style: 'currency', currency: 'JPY'
    }).format(price); // ¥1,234,568 (no decimals)
    
    // Relative time formatting
    const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
    rtf.format(-1, 'day');    // "yesterday"
    rtf.format(2, 'day');     // "in 2 days"
    rtf.format(-3, 'month');  // "3 months ago"
    
    // Percentage formatting
    const percent = 0.456;
    const usPercent = new Intl.NumberFormat('en-US', {
      style: 'percent', minimumFractionDigits: 1
    }).format(percent); // 45.6%
    
    // Unit formatting
    const distance = 1234;
    const usDistance = new Intl.NumberFormat('en-US', {
      style: 'unit', unit: 'kilometer'
    }).format(distance); // 1,234 km
  </script>
  
  <!-- Display formatted values -->
  <div id="date-display"></div>
  <div id="number-display"></div>
</body>
</html>
Locale Date Format Time Format Number Format Currency Symbol
en-US MM/DD/YYYY 12-hour (AM/PM) 1,234.56 $ (before)
en-GB DD/MM/YYYY 24-hour 1,234.56 £ (before)
de-DE DD.MM.YYYY 24-hour 1.234,56 € (after)
fr-FR DD/MM/YYYY 24-hour 1 234,56 € (after, space)
ja-JP YYYY/MM/DD 24-hour 1,234.56 ¥ (before, no decimals)
zh-CN YYYY-MM-DD 24-hour 1,234.56 ¥ (before)
ar-SA DD/MM/YYYY 12-hour (AM/PM) 1,234.56 ر.س (after)

17.5 Font Selection for Multiple Languages

Technique Implementation Description Coverage
System Font Stack font-family: system-ui, -apple-system Use system default fonts for best language coverage All platforms, all scripts
Unicode Range (@font-face) unicode-range: U+0600-06FF; Load specific font for character range (Arabic) Optimize loading for specific scripts
Google Fonts (Noto Family) Noto Sans, Noto Serif Comprehensive multi-script font family 800+ languages, all scripts
Fallback Fonts sans-serif, serif, monospace Generic font families as final fallback Guaranteed rendering (browser default)
Language-Specific Fonts :lang() CSS selector Apply different fonts based on element language Optimized typography per language
Variable Fonts font-variation-settings Single font file with multiple styles Reduces HTTP requests, bandwidth

Example: Multi-language font configuration

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Multi-Language Fonts</title>
  <style>
    /* System font stack for broad language support */
    body {
      font-family: 
        system-ui,           /* Modern system default */
        -apple-system,       /* macOS/iOS */
        BlinkMacSystemFont,  /* Chrome on macOS */
        'Segoe UI',          /* Windows */
        Roboto,              /* Android, Chrome OS */
        'Noto Sans',         /* Cross-platform Unicode */
        'Helvetica Neue',    /* Legacy macOS */
        Arial,               /* Fallback */
        sans-serif,          /* Generic fallback */
        'Apple Color Emoji', /* Emoji support */
        'Segoe UI Emoji',
        'Noto Color Emoji';
    }
    
    /* Arabic-specific font */
    :lang(ar) {
      font-family: 'Noto Naskh Arabic', 'Arabic Typesetting', 'Simplified Arabic', serif;
    }
    
    /* Chinese fonts (Simplified) */
    :lang(zh-Hans),
    :lang(zh-CN) {
      font-family: 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei', sans-serif;
    }
    
    /* Chinese fonts (Traditional) */
    :lang(zh-Hant),
    :lang(zh-TW) {
      font-family: 'Noto Sans TC', 'PingFang TC', 'Microsoft JhengHei', sans-serif;
    }
    
    /* Japanese fonts */
    :lang(ja) {
      font-family: 'Noto Sans JP', 'Hiragino Sans', 'Yu Gothic', 'Meiryo', sans-serif;
    }
    
    /* Korean fonts */
    :lang(ko) {
      font-family: 'Noto Sans KR', 'Apple SD Gothic Neo', 'Malgun Gothic', sans-serif;
    }
    
    /* Hebrew fonts */
    :lang(he) {
      font-family: 'Noto Sans Hebrew', 'Arial Hebrew', 'David', serif;
    }
    
    /* Devanagari (Hindi, Sanskrit) */
    :lang(hi),
    :lang(sa) {
      font-family: 'Noto Sans Devanagari', 'Mangal', 'Nirmala UI', sans-serif;
    }
    
    /* Thai fonts */
    :lang(th) {
      font-family: 'Noto Sans Thai', 'Leelawadee UI', 'Thonburi', sans-serif;
    }
    
    /* @font-face with unicode-range for optimized loading */
    @font-face {
      font-family: 'WebFont';
      src: url('latin.woff2') format('woff2');
      unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F;
    }
    
    @font-face {
      font-family: 'WebFont';
      src: url('arabic.woff2') format('woff2');
      unicode-range: U+0600-06FF, U+FE70-FEFF; /* Arabic block */
    }
    
    @font-face {
      font-family: 'WebFont';
      src: url('chinese.woff2') format('woff2');
      unicode-range: U+4E00-9FFF; /* CJK Unified Ideographs */
    }
  </style>
  
  <!-- Google Fonts with multiple scripts -->
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link href="https://fonts.googleapis.com/css2?family=Noto+Sans:wght@400;700&family=Noto+Sans+Arabic:wght@400;700&family=Noto+Sans+JP:wght@400;700&family=Noto+Sans+SC:wght@400;700&display=swap" rel="stylesheet">
</head>
<body>
  <h1>Multi-Language Typography</h1>
  
  <p lang="en">English: The quick brown fox jumps over the lazy dog.</p>
  <p lang="ar" dir="rtl">Arabic: الثعلب البني السريع يقفز فوق الكلب الكسول</p>
  <p lang="zh-Hans">Chinese (Simplified): 快速的棕色狐狸跳过懒狗</p>
  <p lang="zh-Hant">Chinese (Traditional): 快速的棕色狐狸跳過懶狗</p>
  <p lang="ja">Japanese: 速い茶色のキツネが怠惰な犬を飛び越える</p>
  <p lang="ko">Korean: 빠른 갈색 여우가 게으른 개를 뛰어넘습니다</p>
  <p lang="hi">Hindi: तेज भूरी लोमड़ी आलसी कुत्ते के ऊपर कूदती है</p>
  <p lang="th">Thai: สุนัขจิ้งจอกสีน้ำตาลที่รวดเร็วกระโดดข้ามสุนัขที่ขี้เกียจ</p>
  <p lang="he" dir="rtl">Hebrew: השועל החום המהיר קופץ מעל הכלב העצלן</p>
</body>
</html>
Script/Language Recommended Fonts Unicode Range Special Considerations
Latin Roboto, Open Sans, Inter, system-ui U+0000-00FF Most web fonts support Latin
Arabic Noto Naskh Arabic, Amiri, Cairo U+0600-06FF Requires contextual shaping, RTL support
Chinese (Simplified) Noto Sans SC, Source Han Sans SC U+4E00-9FFF Large font files (~5-10MB), use subsetting
Japanese Noto Sans JP, Source Han Sans JP U+3040-309F (Hiragana), U+30A0-30FF (Katakana) Includes Kanji (U+4E00-9FFF)
Korean Noto Sans KR, Spoqa Han Sans U+AC00-D7AF (Hangul) 11,172 syllable blocks
Devanagari (Hindi) Noto Sans Devanagari, Poppins U+0900-097F Complex ligatures, combining marks
Thai Noto Sans Thai, Sukhumvit U+0E00-0E7F Tone marks, complex rendering
Emoji Noto Color Emoji, Apple Color Emoji U+1F300-1F9FF Color fonts (SVG, CBDT, COLR)
Performance Tip: Use font-display: swap to prevent invisible text during font loading. For CJK fonts (Chinese, Japanese, Korean), subset fonts to reduce file size from 10MB+ to under 1MB by including only commonly used characters.

17.6 Content Localization Strategies

Strategy Implementation Description Best For
Separate HTML Files /en/page.html, /fr/page.html Different HTML file per language Static sites, complete translations
URL Path Strategy example.com/en/, example.com/fr/ Language in URL path SEO-friendly, clear separation
Subdomain Strategy en.example.com, fr.example.com Language as subdomain Large sites, regional content
Query Parameter example.com?lang=fr Language via query string Poor SEO - avoid
Cookie/LocalStorage Store user language preference Remember choice, load appropriate content User preference persistence
Accept-Language Header Server detects browser language Auto-redirect based on browser settings Initial language detection
JavaScript i18n Libraries i18next, FormatJS, Globalize Client-side translation and formatting SPAs, dynamic content
Server-Side Rendering Template engines with translation Generate localized HTML on server SEO, initial load performance

Example: Complete localization implementation

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Localized Website</title>
  
  <!-- hreflang for all language versions -->
  <link rel="alternate" hreflang="en" href="https://example.com/en/">
  <link rel="alternate" hreflang="fr" href="https://example.com/fr/">
  <link rel="alternate" hreflang="de" href="https://example.com/de/">
  <link rel="alternate" hreflang="ja" href="https://example.com/ja/">
  <link rel="alternate" hreflang="x-default" href="https://example.com/">
</head>
<body>
  <!-- Language selector -->
  <nav aria-label="Language selection">
    <select id="language-selector" onchange="changeLanguage(this.value)">
      <option value="en" selected>English</option>
      <option value="fr">Français</option>
      <option value="de">Deutsch</option>
      <option value="es">Español</option>
      <option value="ja">日本語</option>
      <option value="zh">中文</option>
      <option value="ar">العربية</option>
    </select>
  </nav>
  
  <!-- Content with data attributes for translation -->
  <h1 data-i18n="welcome">Welcome</h1>
  <p data-i18n="description">This is a localized website.</p>
  
  <!-- Dynamic content with placeholders -->
  <p data-i18n="greeting" data-i18n-options='{"name":"John"}'>
    Hello, John!
  </p>
  
  <!-- Date/time with locale formatting -->
  <p>
    <time datetime="2025-12-22T14:30:00Z" id="event-time"></time>
  </p>
  
  <!-- Price with currency formatting -->
  <p class="price" data-amount="1234.56" data-currency="USD"></p>
  
  <script>
    // Translation data (normally loaded from JSON files)
    const translations = {
      en: {
        welcome: 'Welcome',
        description: 'This is a localized website.',
        greeting: 'Hello, {{name}}!'
      },
      fr: {
        welcome: 'Bienvenue',
        description: 'Ceci est un site web localisé.',
        greeting: 'Bonjour, {{name}}!'
      },
      de: {
        welcome: 'Willkommen',
        description: 'Dies ist eine lokalisierte Website.',
        greeting: 'Hallo, {{name}}!'
      },
      ja: {
        welcome: 'ようこそ',
        description: 'これはローカライズされたウェブサイトです。',
        greeting: 'こんにちは、{{name}}さん!'
      }
    };
    
    // Detect user's language
    function detectLanguage() {
      // 1. Check localStorage
      const stored = localStorage.getItem('preferredLanguage');
      if (stored) return stored;
      
      // 2. Check browser language
      const browserLang = navigator.language.split('-')[0];
      if (translations[browserLang]) return browserLang;
      
      // 3. Default to English
      return 'en';
    }
    
    // Change language
    function changeLanguage(lang) {
      // Save preference
      localStorage.setItem('preferredLanguage', lang);
      
      // Update HTML lang attribute
      document.documentElement.lang = lang;
      
      // Update text direction for RTL languages
      if (lang === 'ar' || lang === 'he') {
        document.documentElement.dir = 'rtl';
      } else {
        document.documentElement.dir = 'ltr';
      }
      
      // Translate all elements
      document.querySelectorAll('[data-i18n]').forEach(el => {
        const key = el.getAttribute('data-i18n');
        let text = translations[lang][key];
        
        // Handle placeholders
        const options = el.getAttribute('data-i18n-options');
        if (options) {
          const vars = JSON.parse(options);
          Object.keys(vars).forEach(k => {
            text = text.replace(`{{${k}}}`, vars[k]);
          });
        }
        
        el.textContent = text;
      });
      
      // Format date/time
      const eventTime = document.getElementById('event-time');
      const date = new Date(eventTime.getAttribute('datetime'));
      eventTime.textContent = new Intl.DateTimeFormat(lang, {
        year: 'numeric', month: 'long', day: 'numeric',
        hour: 'numeric', minute: 'numeric'
      }).format(date);
      
      // Format price
      document.querySelectorAll('.price').forEach(el => {
        const amount = parseFloat(el.getAttribute('data-amount'));
        const currency = el.getAttribute('data-currency');
        el.textContent = new Intl.NumberFormat(lang, {
          style: 'currency', currency: currency
        }).format(amount);
      });
    }
    
    // Initialize on page load
    const currentLang = detectLanguage();
    document.getElementById('language-selector').value = currentLang;
    changeLanguage(currentLang);
  </script>
</body>
</html>
Localization Best Practices:
  • Store translations in separate JSON files, not in code
  • Use ICU MessageFormat for complex plurals and gender
  • Never concatenate translated strings
  • Leave space for text expansion (German +35%, French +20%)
  • Translate alt text, title attributes, error messages
  • Consider cultural differences (colors, images, gestures)
Common Pitfalls:
  • Hard-coding text in HTML/JS instead of using translation keys
  • Forgetting to translate meta tags, Open Graph data
  • Using flags to represent languages (flag ≠ language)
  • Not accounting for text direction (RTL/LTR)
  • Ignoring date/number/currency formatting differences
  • Assuming all users speak English as fallback

Internationalization Checklist

Resources: Use Google Translate API, DeepL, or professional translators for quality. Test with tools like BrowserStack for international browsers. Consider Crowdin, Phrase, or Lokalise for translation management. Follow W3C Internationalization guidelines (w3.org/International).

18. Modern HTML Features and Experimental APIs

18.1 HTML5.1 and HTML5.2 New Features

Feature Version Description Browser Support
Multiple <main> elements HTML5.1 Allow multiple <main> if only one visible (using hidden) All modern browsers
Menu and menuitem DEPRECATED HTML5.1 Context menus (removed in HTML5.2) Deprecated, use custom
details and summary HTML5.1 Native disclosure widget (accordion/collapse) All modern browsers
dialog element HTML5.2 Native modal and non-modal dialogs Chrome 37+, Firefox 98+, Safari 15.4+
iframe allowpaymentrequest HTML5.2 Allow Payment Request API in iframe Chrome, Edge
Multiple values for rel HTML5.1 <link rel="icon stylesheet"> space-separated All browsers
Subresource Integrity (SRI) HTML5.1 integrity attribute for CDN security All modern browsers
picture element HTML5.1 Art direction and responsive images All modern browsers
srcset attribute HTML5.1 Responsive image sources All modern browsers
download attribute HTML5.1 Force download instead of navigation All browsers (cross-origin limited)

Example: HTML5.1 and HTML5.2 features

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Modern HTML Features</title>
  
  <!-- Subresource Integrity (HTML5.1) -->
  <link rel="stylesheet" 
        href="https://cdn.example.com/styles.css"
        integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/ux..."
        crossorigin="anonymous">
  
  <script src="https://cdn.example.com/script.js"
          integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpb..."
          crossorigin="anonymous"></script>
</head>
<body>
  <!-- Multiple main elements (only one visible) - HTML5.1 -->
  <main id="page1">
    <h1>Page 1</h1>
    <p>Content for page 1</p>
  </main>
  
  <main id="page2" hidden>
    <h1>Page 2</h1>
    <p>Content for page 2</p>
  </main>
  
  <!-- details/summary disclosure widget - HTML5.1 -->
  <details>
    <summary>Click to expand</summary>
    <p>Hidden content that can be toggled</p>
    <ul>
      <li>Item 1</li>
      <li>Item 2</li>
    </ul>
  </details>
  
  <!-- dialog element - HTML5.2 -->
  <dialog id="myDialog">
    <h2>Dialog Title</h2>
    <p>This is a native HTML dialog</p>
    <button onclick="document.getElementById('myDialog').close()">Close</button>
  </dialog>
  
  <button onclick="document.getElementById('myDialog').showModal()">Open Dialog</button>
  
  <!-- picture element with art direction - HTML5.1 -->
  <picture>
    <source media="(min-width: 1200px)" srcset="large.webp" type="image/webp">
    <source media="(min-width: 768px)" srcset="medium.webp" type="image/webp">
    <source srcset="small.webp" type="image/webp">
    <img src="fallback.jpg" alt="Responsive image">
  </picture>
  
  <!-- Download attribute - HTML5.1 -->
  <a href="/files/document.pdf" download="my-document.pdf">Download PDF</a>
  
  <!-- iframe with allowpaymentrequest - HTML5.2 -->
  <iframe src="https://payment.example.com" 
          allowpaymentrequest
          sandbox="allow-scripts allow-same-origin">
  </iframe>
</body>
</html>
HTML Evolution: HTML5.1 (Nov 2016) focused on accessibility and developer experience. HTML5.2 (Dec 2017) added dialog, removed obsolete features. HTML Living Standard (WHATWG) now guides development with continuous updates rather than versioned releases.

18.2 Popover API and Native Modals

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

Example: Popover API usage

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

18.3 Declarative Shadow DOM

Feature Syntax Description Browser Support
shadowrootmode NEW <template shadowrootmode="open"> Declarative shadow DOM in HTML (no JavaScript needed) Chrome 90+, Edge 91+, Safari 16.4+
shadowrootmode="open" Open shadow root Shadow root accessible via element.shadowRoot JavaScript can access shadow DOM
shadowrootmode="closed" Closed shadow root Shadow root not accessible from outside Maximum encapsulation
shadowrootdelegatesfocus shadowrootdelegatesfocus Delegate focus to first focusable element Improves keyboard navigation
getHTML() NEW element.getHTML({serializableShadowRoots: true}) Serialize element including shadow DOM Chrome 125+

Example: Declarative Shadow DOM

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Declarative Shadow DOM</title>
</head>
<body>
  <!-- Declarative shadow DOM component (no JavaScript!) -->
  <my-card>
    <template shadowrootmode="open">
      <style>
        /* Encapsulated styles - won't leak out */
        :host {
          display: block;
          border: 1px solid #ccc;
          border-radius: 8px;
          padding: 1rem;
          background: #f9f9f9;
        }
        
        h2 {
          color: #007acc;
          margin: 0 0 0.5rem 0;
        }
        
        ::slotted(p) {
          color: #333;
          line-height: 1.6;
        }
      </style>
      
      <div class="card">
        <h2><slot name="title">Default Title</slot></h2>
        <div class="content">
          <slot>Default content</slot>
        </div>
      </div>
    </template>
    
    <!-- Light DOM content projected into slots -->
    <span slot="title">My Custom Title</span>
    <p>This is the card content.</p>
    <p>Multiple paragraphs work too.</p>
  </my-card>
  
  <!-- Another instance with different content -->
  <my-card>
    <template shadowrootmode="open">
      <style>
        :host {
          display: block;
          border: 2px solid #28a745;
          padding: 1rem;
        }
        h2 { color: #28a745; }
      </style>
      <h2><slot name="title"></slot></h2>
      <slot></slot>
    </template>
    
    <span slot="title">Success Card</span>
    <p>Operation completed successfully!</p>
  </my-card>
  
  <!-- Closed shadow root (maximum encapsulation) -->
  <secure-component>
    <template shadowrootmode="closed">
      <style>
        .private { background: #ff0000; }
      </style>
      <div class="private">This shadow DOM cannot be accessed from outside</div>
    </template>
  </secure-component>
  
  <!-- With focus delegation -->
  <custom-form>
    <template shadowrootmode="open" shadowrootdelegatesfocus>
      <style>
        :host(:focus-within) {
          outline: 2px solid #007acc;
          outline-offset: 2px;
        }
      </style>
      <form>
        <input type="text" placeholder="Focus delegated here">
        <button>Submit</button>
      </form>
    </template>
  </custom-form>
  
  <script>
    // Feature detection and polyfill
    if (!HTMLTemplateElement.prototype.hasOwnProperty('shadowRootMode')) {
      console.log('Declarative Shadow DOM not supported, using polyfill');
      
      // Polyfill: Convert declarative to imperative
      document.querySelectorAll('template[shadowrootmode]').forEach(template => {
        const mode = template.getAttribute('shadowrootmode');
        const shadowRoot = template.parentNode.attachShadow({ mode });
        shadowRoot.appendChild(template.content);
        template.remove();
      });
    }
    
    // Access open shadow root
    const card = document.querySelector('my-card');
    console.log('Shadow root:', card.shadowRoot); // accessible if "open"
    
    // Serialize including shadow DOM (Chrome 125+)
    if (card.getHTML) {
      const html = card.getHTML({ serializableShadowRoots: true });
      console.log('Serialized:', html);
    }
  </script>
</body>
</html>
Benefits of Declarative Shadow DOM:
  • SSR-friendly (works without JavaScript)
  • No Flash of Unstyled Content (FOUC)
  • Better for performance and SEO
  • Simpler than imperative attachShadow()
  • Progressive enhancement ready
When to Use:
  • Server-rendered Web Components
  • Style encapsulation without JS
  • Reusable UI patterns
  • Design system components
  • Email-safe components (limited)

18.4 Import Maps and ES Modules

Feature Syntax Description Browser Support
Import Maps NEW <script type="importmap"> Control module resolution without bundler Chrome 89+, Edge 89+, Safari 16.4+
imports mapping "imports": {"lib": "./lib.js"} Map bare module specifiers to URLs Simplifies import statements
scopes mapping "scopes": {"/admin/": {...}} Scope-specific module resolution Different versions per path
type="module" <script type="module" src="app.js"> ES6 module script All modern browsers
Dynamic import() import('./module.js').then(...) Lazy load modules at runtime All modern browsers
import.meta import.meta.url Module metadata (URL, resolve) All modern browsers
Top-level await const data = await fetch(...) Await at module top level Chrome 89+, Firefox 89+, Safari 15+

Example: Import Maps and ES Modules

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Import Maps Demo</title>
  
  <!-- Define import map BEFORE any module scripts -->
  <script type="importmap">
  {
    "imports": {
      "lodash": "https://cdn.jsdelivr.net/npm/lodash-es@4.17.21/lodash.js",
      "react": "https://esm.sh/react@18",
      "react-dom": "https://esm.sh/react-dom@18",
      "utils/": "./src/utils/",
      "components/": "./src/components/",
      "@app/config": "./config.js"
    },
    "scopes": {
      "/admin/": {
        "utils/": "./admin/utils/"
      }
    }
  }
  </script>
</head>
<body>
  <div id="app"></div>
  
  <!-- Module script using import map -->
  <script type="module">
    // Use mapped imports (no ./ or full URL needed)
    import { debounce } from 'lodash';
    import React from 'react';
    import { render } from 'react-dom';
    import config from '@app/config';
    
    // Use path mappings
    import { formatDate } from 'utils/formatters.js';
    import Button from 'components/Button.js';
    
    console.log('Config:', config);
    console.log('Debounce:', debounce);
    
    // Dynamic import (code splitting)
    const loadModule = async () => {
      const module = await import('utils/heavy-module.js');
      module.initialize();
    };
    
    document.querySelector('#load-btn')?.addEventListener('click', loadModule);
  </script>
  
  <!-- Top-level await example -->
  <script type="module">
    // Fetch data at module top level
    const response = await fetch('/api/config');
    const config = await response.json();
    
    console.log('Config loaded:', config);
    
    // Module waits for this to complete before executing dependent modules
  </script>
  
  <!-- import.meta usage -->
  <script type="module">
    console.log('Current module URL:', import.meta.url);
    
    // Resolve relative URLs
    const imageUrl = new URL('./images/logo.png', import.meta.url);
    console.log('Image URL:', imageUrl.href);
    
    // Feature detection
    if (import.meta.resolve) {
      const resolved = import.meta.resolve('lodash');
      console.log('Lodash resolves to:', resolved);
    }
  </script>
  
  <!-- Module with exports -->
  <script type="module">
    // Named exports
    export const API_URL = 'https://api.example.com';
    export function fetchData() { /* ... */ }
    
    // Default export
    export default class App { /* ... */ }
  </script>
  
  <!-- Import from inline module -->
  <script type="module">
    // Can't directly import from inline modules in same document
    // Use external files or dynamic import
    
    const module = await import('./app.js');
    module.default.init();
  </script>
  
  <!-- Polyfill for older browsers -->
  <script>
    if (!HTMLScriptElement.supports || !HTMLScriptElement.supports('importmap')) {
      // Load es-module-shims polyfill
      const script = document.createElement('script');
      script.src = 'https://ga.jspm.io/npm:es-module-shims@1.8.0/dist/es-module-shims.js';
      script.async = true;
      document.head.appendChild(script);
    }
  </script>
</body>
</html>
Import Type Before Import Maps With Import Maps Benefit
CDN Module import {...} from 'https://cdn.../lib.js' import {...} from 'lib' Cleaner, centralized URL management
Local Module import {...} from './src/utils/helper.js' import {...} from 'utils/helper.js' Path aliasing, easier refactoring
Version Control Update all import statements Update import map once Centralized dependency management
Environment Build-time replacement Runtime mapping No build step needed

18.5 Web Streams and Readable Streams

API Description Use Case Browser Support
ReadableStream Stream of data chunks that can be read progressively Large file processing, infinite scrolls All modern browsers
WritableStream Destination for streaming data File writes, network uploads All modern browsers
TransformStream Transform data while streaming Compression, encryption, parsing All modern browsers
Response.body (Fetch) ReadableStream from fetch response Progressive rendering, download progress All modern browsers
async iteration for await...of loop over streams Simplified stream consumption All modern browsers
TextDecoderStream Decode byte stream to text UTF-8 decoding, text processing Chrome 71+, Firefox 105+, Safari 14.1+
CompressionStream Compress data stream (gzip, deflate) Client-side compression Chrome 80+, Firefox 113+, Safari 16.4+

Example: Web Streams API usage

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Web Streams Demo</title>
</head>
<body>
  <div id="output"></div>
  <div id="progress"></div>
  
  <script>
    // Example 1: Fetch with streaming response
    async function fetchWithProgress(url) {
      const response = await fetch(url);
      const reader = response.body.getReader();
      const contentLength = +response.headers.get('Content-Length');
      
      let receivedLength = 0;
      const chunks = [];
      
      while (true) {
        const { done, value } = await reader.read();
        
        if (done) break;
        
        chunks.push(value);
        receivedLength += value.length;
        
        // Update progress
        const percent = (receivedLength / contentLength * 100).toFixed(2);
        document.getElementById('progress').textContent = `${percent}% loaded`;
      }
      
      // Concatenate chunks into single array
      const chunksAll = new Uint8Array(receivedLength);
      let position = 0;
      for (let chunk of chunks) {
        chunksAll.set(chunk, position);
        position += chunk.length;
      }
      
      return chunksAll;
    }
    
    // Example 2: Async iteration over stream
    async function streamText(url) {
      const response = await fetch(url);
      const reader = response.body
        .pipeThrough(new TextDecoderStream())
        .getReader();
      
      for await (const chunk of readStream(reader)) {
        document.getElementById('output').textContent += chunk;
      }
    }
    
    async function* readStream(reader) {
      while (true) {
        const { done, value } = await reader.read();
        if (done) return;
        yield value;
      }
    }
    
    // Example 3: Transform stream (uppercase)
    async function transformStream(text) {
      const stream = new ReadableStream({
        start(controller) {
          controller.enqueue(text);
          controller.close();
        }
      });
      
      const transformedStream = stream.pipeThrough(
        new TransformStream({
          transform(chunk, controller) {
            controller.enqueue(chunk.toUpperCase());
          }
        })
      );
      
      const reader = transformedStream.getReader();
      const { value } = await reader.read();
      return value;
    }
    
    // Example 4: Create custom ReadableStream
    const customStream = new ReadableStream({
      start(controller) {
        let i = 0;
        const interval = setInterval(() => {
          if (i < 10) {
            controller.enqueue(`Chunk ${i}\n`);
            i++;
          } else {
            controller.close();
            clearInterval(interval);
          }
        }, 100);
      }
    });
    
    // Example 5: Compression stream
    async function compressData(text) {
      const blob = new Blob([text]);
      const stream = blob.stream();
      
      const compressedStream = stream.pipeThrough(
        new CompressionStream('gzip')
      );
      
      const compressedBlob = await new Response(compressedStream).blob();
      console.log('Original:', blob.size, 'Compressed:', compressedBlob.size);
      return compressedBlob;
    }
    
    // Example 6: Pipe streams
    async function pipeExample() {
      const response = await fetch('/data.txt');
      
      // Chain multiple transforms
      const processedStream = response.body
        .pipeThrough(new TextDecoderStream())
        .pipeThrough(new TransformStream({
          transform(chunk, controller) {
            // Process each chunk
            const processed = chunk.trim().toUpperCase();
            controller.enqueue(processed);
          }
        }));
      
      // Write to destination
      const writableStream = new WritableStream({
        write(chunk) {
          console.log('Processed chunk:', chunk);
          document.getElementById('output').textContent += chunk;
        }
      });
      
      await processedStream.pipeTo(writableStream);
    }
    
    // Example 7: Server-Sent Events with streams
    async function streamSSE(url) {
      const response = await fetch(url);
      const reader = response.body
        .pipeThrough(new TextDecoderStream())
        .pipeThrough(new TransformStream({
          transform(chunk, controller) {
            // Parse SSE format
            const lines = chunk.split('\n');
            for (const line of lines) {
              if (line.startsWith('data: ')) {
                const data = line.slice(6);
                controller.enqueue(JSON.parse(data));
              }
            }
          }
        }))
        .getReader();
      
      for await (const event of readStream(reader)) {
        console.log('SSE event:', event);
      }
    }
  </script>
</body>
</html>
Stream Benefits: Memory efficient for large files (process chunk-by-chunk), enables progress indicators, supports backpressure (slow consumer signals producer), composable (chain transformations), works with Service Workers for offline-first apps.

18.6 Navigation API and SPA Routing

API Feature Method/Event Description Browser Support
Navigation API NEW navigation.navigate(url) Modern replacement for History API Chrome 102+, Edge 102+
navigation.navigate() navigation.navigate('/page', {state}) Navigate to URL with state Cleaner than pushState
navigation.back() navigation.back() Navigate backward in history Equivalent to history.back()
navigation.forward() navigation.forward() Navigate forward in history Equivalent to history.forward()
navigation.reload() navigation.reload() Reload current entry Programmatic reload
navigate event navigation.addEventListener('navigate') Intercept all navigations (links, forms, browser UI) Single event for all navigation types
navigatesuccess event navigation.addEventListener('navigatesuccess') Navigation completed successfully Update UI, analytics
navigateerror event navigation.addEventListener('navigateerror') Navigation failed Error handling
navigation.currentEntry navigation.currentEntry.url Current navigation entry Access URL, state, key, index
View Transitions NEW document.startViewTransition() Animated transitions between pages Chrome 111+, Edge 111+

Example: Navigation API for SPA routing

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Navigation API Demo</title>
  <style>
    /* View Transitions API */
    @view-transition {
      navigation: auto;
    }
    
    ::view-transition-old(root) {
      animation: fade-out 0.3s ease-out;
    }
    
    ::view-transition-new(root) {
      animation: fade-in 0.3s ease-in;
    }
    
    @keyframes fade-out {
      to { opacity: 0; }
    }
    
    @keyframes fade-in {
      from { opacity: 0; }
    }
  </style>
</head>
<body>
  <nav>
    <a href="/">Home</a>
    <a href="/about">About</a>
    <a href="/contact">Contact</a>
  </nav>
  
  <main id="content"></main>
  
  <script>
    // Check for Navigation API support
    if ('navigation' in window) {
      console.log('Navigation API supported!');
      
      // Intercept all navigations
      navigation.addEventListener('navigate', (event) => {
        // Only handle same-origin navigations
        if (!event.canIntercept || event.hashChange || event.downloadRequest) {
          return;
        }
        
        const url = new URL(event.destination.url);
        
        // Intercept and handle as SPA navigation
        event.intercept({
          async handler() {
            // Fetch new content
            const response = await fetch(url.pathname);
            const html = await response.text();
            
            // Use View Transitions API if available
            if (document.startViewTransition) {
              await document.startViewTransition(() => {
                document.getElementById('content').innerHTML = html;
              }).finished;
            } else {
              document.getElementById('content').innerHTML = html;
            }
            
            // Update page title
            document.title = `Page - ${url.pathname}`;
          },
          
          // Optional: scroll to top, focus management
          scroll: 'after-transition',
          focusReset: 'after-transition'
        });
      });
      
      // Listen to successful navigation
      navigation.addEventListener('navigatesuccess', (event) => {
        console.log('Navigation succeeded:', navigation.currentEntry.url);
        
        // Analytics
        gtag?.('event', 'page_view', {
          page_path: navigation.currentEntry.url
        });
      });
      
      // Handle navigation errors
      navigation.addEventListener('navigateerror', (event) => {
        console.error('Navigation failed:', event.error);
        document.getElementById('content').innerHTML = '<h1>Error loading page</h1>';
      });
      
      // Programmatic navigation
      function navigateTo(path, state = {}) {
        navigation.navigate(path, { state, history: 'push' });
      }
      
      // Navigation with state
      document.querySelector('#special-btn')?.addEventListener('click', () => {
        navigation.navigate('/dashboard', {
          state: { userId: 123, fromButton: true }
        });
      });
      
      // Access current navigation entry
      console.log('Current URL:', navigation.currentEntry.url);
      console.log('Current state:', navigation.currentEntry.getState());
      console.log('Current key:', navigation.currentEntry.key);
      console.log('Current index:', navigation.currentEntry.index);
      
      // Traversal (back/forward)
      function goBack() {
        if (navigation.canGoBack) {
          navigation.back();
        }
      }
      
      function goForward() {
        if (navigation.canGoForward) {
          navigation.forward();
        }
      }
      
      // Get all entries
      const entries = navigation.entries();
      console.log('Total entries:', entries.length);
      
      // Navigate to specific entry
      navigation.traverseTo(entries[0].key);
      
    } else {
      console.log('Navigation API not supported, using History API fallback');
      
      // Fallback to traditional History API
      window.addEventListener('popstate', (event) => {
        loadPage(location.pathname, event.state);
      });
      
      document.addEventListener('click', (e) => {
        if (e.target.tagName === 'A' && e.target.host === location.host) {
          e.preventDefault();
          const path = e.target.pathname;
          history.pushState({}, '', path);
          loadPage(path);
        }
      });
      
      async function loadPage(path, state = {}) {
        const response = await fetch(path);
        const html = await response.text();
        document.getElementById('content').innerHTML = html;
      }
    }
    
    // View Transitions API (standalone)
    async function updateContent(newContent) {
      if (!document.startViewTransition) {
        // Fallback: instant update
        document.getElementById('content').innerHTML = newContent;
        return;
      }
      
      // Animated transition
      const transition = document.startViewTransition(() => {
        document.getElementById('content').innerHTML = newContent;
      });
      
      // Wait for transition to complete
      await transition.finished;
      console.log('Transition complete');
    }
  </script>
</body>
</html>
Feature History API (Old) Navigation API (New) Improvement
Navigation events popstate only (browser back/forward) navigate (all navigations) Single event for links, forms, browser UI
State management pushState(), replaceState() navigation.navigate() Cleaner API, promises
Async handling Manual promise management event.intercept({ async handler }) Built-in async support
Scroll/focus Manual implementation scroll, focusReset options Automatic scroll restoration
Navigation entries Limited access navigation.entries(), currentEntry Full history access, metadata
Error handling Manual try/catch navigateerror event Centralized error handling

Modern HTML Features Checklist

Browser Support Warning: Many features in this section are experimental or newly standardized. Always check caniuse.com for current support. Use feature detection: if ('navigation' in window), if (HTMLTemplateElement.prototype.hasOwnProperty('shadowRootMode')). Provide polyfills or graceful degradation for production apps.

19. Development Tools and Debugging

19.1 HTML Validation and W3C Compliance

Tool/Service URL/Usage Description Best For
W3C Markup Validation validator.w3.org Official W3C HTML validator Standards compliance, catching syntax errors
Nu Html Checker (v.Nu) validator.w3.org/nu/ Modern HTML5 validator HTML5 features, living standard compliance
HTML Validator Extension Browser extension (Chrome, Firefox) Real-time validation in browser Development workflow, instant feedback
html-validate (npm) npm install -g html-validate CLI/API validator with custom rules CI/CD pipelines, build automation
htmlhint (npm) npm install -g htmlhint Fast HTML linter with configurable rules Static analysis, team style guides
VS Code Extensions HTMLHint, W3C Validation IDE-integrated validation Real-time editing, inline errors
AMP Validator validator.ampproject.org Validate AMP HTML pages AMP-specific compliance
Structured Data Testing search.google.com/test/rich-results Validate schema.org markup SEO, rich snippets validation

Example: HTML validation workflow

<!-- Common validation errors to avoid -->

<!-- ERROR: Missing DOCTYPE -->
<html>  <!-- ✗ Should start with <!DOCTYPE html> -->

<!-- ERROR: Missing required attributes -->
<img src="image.jpg">  <!-- ✗ Missing alt attribute -->
<img src="image.jpg" alt="Description">  <!-- ✓ Correct -->

<!-- ERROR: Duplicate IDs -->
<div id="content">First</div>
<div id="content">Second</div>  <!-- ✗ ID must be unique -->

<!-- ERROR: Improper nesting -->
<p>Paragraph <div>with div</div></p>  <!-- ✗ Block in inline -->
<p>Paragraph <span>with span</span></p>  <!-- ✓ Correct -->

<!-- ERROR: Unclosed tags -->
<div>
  <p>Paragraph
</div>  <!-- ✗ Missing </p> -->

<!-- ERROR: Invalid attribute values -->
<input type="text" required="true">  <!-- ✗ Boolean attribute with value -->
<input type="text" required>  <!-- ✓ Correct -->

<!-- ERROR: Obsolete elements -->
<center>Text</center>  <!-- ✗ Obsolete -->
<div style="text-align: center">Text</div>  <!-- ✓ Use CSS -->

<!-- ERROR: Missing charset -->
<head>
  <title>Page</title>  <!-- ✗ Missing charset declaration -->
</head>

<head>
  <meta charset="UTF-8">  <!-- ✓ Should be first in head -->
  <title>Page</title>
</head>

Example: Using html-validate in project

<!-- Install html-validate -->
npm install --save-dev html-validate

<!-- Create .htmlvalidate.json config -->
{
  "extends": ["html-validate:recommended"],
  "rules": {
    "close-order": "error",
    "void-style": ["error", "selfclose"],
    "require-sri": "warn",
    "no-inline-style": "warn",
    "attribute-boolean-style": "error",
    "attr-case": ["error", "lowercase"],
    "id-pattern": ["error", "kebab-case"],
    "no-deprecated-attr": "error",
    "require-heading": "warn"
  }
}

<!-- Run validation -->
npx html-validate "src/**/*.html"

<!-- Add to package.json scripts -->
{
  "scripts": {
    "validate": "html-validate 'src/**/*.html'",
    "validate:fix": "html-validate --fix 'src/**/*.html'"
  }
}

<!-- Use in CI/CD (GitHub Actions) -->
name: Validate HTML
on: [push, pull_request]
jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
      - run: npm ci
      - run: npm run validate
Validation Best Practices: Validate early and often during development. Fix errors in order (DOCTYPE, charset, structure, then attributes). Use automated validation in CI/CD. Remember validation ensures correct syntax, not good design or accessibility.

19.2 Browser DevTools for HTML Debugging

DevTools Feature Keyboard Shortcut Description Use Case
Inspect Element Ctrl+Shift+C / Cmd+Shift+C Select element on page to inspect Quick navigation to element in DOM tree
Elements Panel F12 then Elements tab View and edit DOM structure and CSS Live HTML/CSS editing, debugging layout
Edit as HTML Right-click → Edit as HTML Edit entire element structure Test HTML changes, fix nesting issues
Break on Attribute Changes Right-click → Break on → attribute modifications Pause when element attributes change Debug dynamic attribute changes
Break on Subtree Modifications Right-click → Break on → subtree modifications Pause when child elements added/removed Debug dynamic content injection
Break on Node Removal Right-click → Break on → node removal Pause when element is removed from DOM Find what's removing elements
Computed Styles Elements → Computed tab See final computed CSS values Debug CSS specificity, inheritance
Event Listeners Elements → Event Listeners tab View all event handlers on element Debug event handling, remove listeners
Accessibility Tree Elements → Accessibility tab View accessibility properties and ARIA Debug screen reader issues
DOM Breakpoints Elements → DOM Breakpoints sidebar List all DOM modification breakpoints Manage multiple breakpoints
Search in DOM Ctrl+F / Cmd+F in Elements Search by text, selector, or XPath Find elements quickly
Copy as HTML/Selector Right-click → Copy Copy element HTML, selector, XPath, etc. Share code, create selectors

Example: DevTools debugging techniques

<!-- Console commands for HTML debugging -->

// Select element by CSS selector
$('div.container')           // First match
$('div.container')          // All matches (array)
$x('//div[@class="container"]') // XPath selector

// Inspect selected element
$0  // Currently selected element in Elements panel
$1  // Previously selected element
$2  // Element before that

// Get element properties
$0.innerHTML
$0.outerHTML
$0.attributes
$0.classList
$0.dataset  // data-* attributes

// Monitor events
monitorEvents($0)                    // All events
monitorEvents($0, 'click')          // Specific event
monitorEvents($0, ['click', 'keydown'])  // Multiple events
unmonitorEvents($0)                 // Stop monitoring

// Inspect element
inspect($('button'))  // Select element in Elements panel

// Get event listeners
getEventListeners($0)  // All listeners on element

// Copy to clipboard
copy($0.outerHTML)  // Copy element HTML
copy($('a').map(a => a.href))  // Copy all link URLs

// Debug DOM modifications
const observer = new MutationObserver((mutations) => {
  mutations.forEach((mutation) => {
    console.log('Type:', mutation.type);
    console.log('Added:', mutation.addedNodes);
    console.log('Removed:', mutation.removedNodes);
  });
});

observer.observe(document.body, {
  childList: true,
  subtree: true,
  attributes: true,
  characterData: true
});

// Stop observing
observer.disconnect();

// Find elements without required attributes
$('img:not([alt])').length  // Images missing alt
$('a:not([href])').length   // Links without href
$('button:not([type])').length  // Buttons without type

// Find accessibility issues
$('[role]:not([aria-label]):not([aria-labelledby])')
$('div[onclick]')  // Divs with click handlers (bad for a11y)

// Performance: Count DOM elements
document.querySelectorAll('*').length
$('*').length  // Same

// Find duplicate IDs
const ids = [...$('[id]')].map(el => el.id);
ids.filter((id, i) => ids.indexOf(id) !== i);  // Duplicates
DevTool Chrome DevTools Firefox DevTools Safari Web Inspector
Open DevTools F12 / Cmd+Opt+I F12 / Cmd+Opt+I Cmd+Opt+I
Inspect Element Ctrl+Shift+C Ctrl+Shift+C Cmd+Shift+C
Console Ctrl+Shift+J Ctrl+Shift+K Cmd+Opt+C
3D View ✓ Layers tab ✓ 3D View ✓ Layers sidebar
Grid Inspector ✓ Layout pane ✓ Grid badge ✓ Layout sidebar
Flexbox Inspector ✓ Layout pane ✓ Flexbox badge ✓ Layout sidebar

19.3 Accessibility Testing Tools

Tool Type Description Key Features
axe DevTools Browser Extension Automated accessibility testing in browser WCAG 2.1/2.2 compliance, guided fixes
WAVE Browser Extension / Web Service Visual feedback on accessibility issues Color-coded annotations, contrast checker
Lighthouse Accessibility Built into Chrome DevTools Automated accessibility audit Score 0-100, actionable recommendations
NVDA / JAWS Screen Reader Software Test with actual screen reader Real user experience, keyboard navigation
VoiceOver (macOS/iOS) Built-in Screen Reader Apple's screen reader for testing Cmd+F5 to enable, test on Mac/iPhone
pa11y CLI / npm package Automated testing in CI/CD Command-line testing, integration ready
axe-core JavaScript Library Programmatic accessibility testing Integrate in tests, custom rules
Color Contrast Analyzer Desktop App / Extension Check color contrast ratios WCAG AA/AAA compliance checking
Accessibility Insights Browser Extension (Microsoft) Comprehensive accessibility testing Fast Pass, Assessment, Tab stops visualization
HeadingsMap Browser Extension Visualize heading structure Document outline, heading hierarchy

Example: Accessibility testing workflow

<!-- Install and use axe-core for automated testing -->
npm install --save-dev axe-core

<!-- HTML page to test -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Accessibility Test</title>
</head>
<body>
  <!-- Include axe-core -->
  <script src="node_modules/axe-core/axe.min.js"></script>
  
  <script>
    // Run accessibility audit
    axe.run().then(results => {
      if (results.violations.length) {
        console.error('Accessibility Violations:', results.violations);
        
        results.violations.forEach(violation => {
          console.group(`${violation.impact}: ${violation.help}`);
          console.log('Description:', violation.description);
          console.log('Help URL:', violation.helpUrl);
          
          violation.nodes.forEach(node => {
            console.log('Element:', node.html);
            console.log('Selector:', node.target);
            console.log('Fix:', node.failureSummary);
          });
          
          console.groupEnd();
        });
      } else {
        console.log('✓ No accessibility violations found!');
      }
      
      // Log passes
      console.log('Passed checks:', results.passes.length);
      
      // Log incomplete (needs manual review)
      console.log('Manual review needed:', results.incomplete.length);
    });
  </script>
</body>
</html>

<!-- Use pa11y for CI/CD -->
npm install -g pa11y

# Test single page
pa11y https://example.com

# Test with specific standard
pa11y --standard WCAG2AA https://example.com

# Generate report
pa11y --reporter json https://example.com > report.json

# Test multiple pages
pa11y-ci --sitemap https://example.com/sitemap.xml

<!-- package.json script -->
{
  "scripts": {
    "test:a11y": "pa11y-ci --config .pa11yci.json"
  }
}

<!-- .pa11yci.json config -->
{
  "defaults": {
    "standard": "WCAG2AA",
    "runners": ["axe", "htmlcs"]
  },
  "urls": [
    "http://localhost:3000/",
    "http://localhost:3000/about",
    "http://localhost:3000/contact"
  ]
}
Manual Testing Checklist:
  • Keyboard navigation (Tab, Shift+Tab, Enter, Space, arrows)
  • Screen reader testing (NVDA, JAWS, VoiceOver)
  • Zoom to 200% (text should reflow, no horizontal scroll)
  • Color contrast (4.5:1 normal text, 3:1 large text)
  • Forms with screen reader (labels, errors, instructions)
  • Images (alt text descriptive, decorative marked alt="")
Common A11y Issues:
  • Missing alt attributes on images
  • Form inputs without labels
  • Low color contrast ratios
  • Keyboard focus not visible
  • Non-semantic elements for interactive content
  • Missing ARIA labels on icon buttons

19.4 Performance Profiling and Lighthouse

Tool Access Description Key Metrics
Lighthouse Chrome DevTools → Lighthouse Automated performance, accessibility, SEO audit Performance score, FCP, LCP, CLS, TBT
PageSpeed Insights pagespeed.web.dev Online Lighthouse with field data Lab + Real-user Core Web Vitals
WebPageTest webpagetest.org Advanced performance testing Waterfall, filmstrip, video playback
Chrome Performance Panel DevTools → Performance Record runtime performance FPS, CPU usage, network activity
Chrome Coverage DevTools → Coverage Find unused CSS/JS Percentage of unused code
Network Panel DevTools → Network Analyze resource loading Request timing, waterfall, size
Rendering Panel DevTools → More tools → Rendering Visualize layout shifts, paint flashing CLS debugging, paint areas
Lighthouse CI npm package for CI/CD Automated Lighthouse in build pipeline Performance budgets, regression detection

Example: Lighthouse and performance testing

<!-- Run Lighthouse programmatically -->
npm install -g lighthouse

# Test URL
lighthouse https://example.com --output html --output-path report.html

# Test with specific categories
lighthouse https://example.com --only-categories=performance,accessibility

# Test mobile
lighthouse https://example.com --preset=desktop
lighthouse https://example.com --preset=mobile  # default

# Custom Chrome flags
lighthouse https://example.com --chrome-flags="--headless --no-sandbox"

# Budget.json for performance budgets
lighthouse https://example.com --budget-path=budget.json

<!-- budget.json example -->
[
  {
    "path": "/*",
    "timings": [
      {"metric": "first-contentful-paint", "budget": 2000},
      {"metric": "largest-contentful-paint", "budget": 2500},
      {"metric": "cumulative-layout-shift", "budget": 0.1},
      {"metric": "total-blocking-time", "budget": 300}
    ],
    "resourceSizes": [
      {"resourceType": "script", "budget": 300},
      {"resourceType": "stylesheet", "budget": 100},
      {"resourceType": "image", "budget": 500},
      {"resourceType": "document", "budget": 50},
      {"resourceType": "font", "budget": 100},
      {"resourceType": "total", "budget": 1000}
    ],
    "resourceCounts": [
      {"resourceType": "script", "budget": 10},
      {"resourceType": "stylesheet", "budget": 5},
      {"resourceType": "image", "budget": 20},
      {"resourceType": "third-party", "budget": 5}
    ]
  }
]

<!-- Lighthouse CI in GitHub Actions -->
name: Lighthouse CI
on: [push]
jobs:
  lhci:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
      - run: npm ci
      - run: npm run build
      - run: npm install -g @lhci/cli
      - run: lhci autorun

<!-- lighthouserc.json config -->
{
  "ci": {
    "collect": {
      "url": ["http://localhost:3000/"],
      "numberOfRuns": 3
    },
    "assert": {
      "preset": "lighthouse:recommended",
      "assertions": {
        "categories:performance": ["error", {"minScore": 0.9}],
        "categories:accessibility": ["error", {"minScore": 0.9}],
        "categories:best-practices": ["error", {"minScore": 0.9}],
        "categories:seo": ["error", {"minScore": 0.9}]
      }
    },
    "upload": {
      "target": "temporary-public-storage"
    }
  }
}
Core Web Vital Metric Good Needs Improvement Poor
LCP (Largest Contentful Paint) Loading performance <2.5s 2.5s - 4s >4s
FID (First Input Delay) Interactivity <100ms 100ms - 300ms >300ms
CLS (Cumulative Layout Shift) Visual stability <0.1 0.1 - 0.25 >0.25
INP (Interaction to Next Paint) NEW Responsiveness (replacing FID) <200ms 200ms - 500ms >500ms
FCP (First Contentful Paint) Perceived load speed <1.8s 1.8s - 3s >3s
TTFB (Time to First Byte) Server response time <800ms 800ms - 1800ms >1800ms

19.5 HTML Linting and Code Quality

Tool Installation Description Configuration
HTMLHint npm install -g htmlhint Static code analysis for HTML .htmlhintrc file
html-validate npm install -g html-validate Offline HTML5 validator .htmlvalidate.json
Prettier npm install -g prettier Opinionated code formatter .prettierrc
EditorConfig IDE plugin Consistent coding styles across editors .editorconfig
Stylelint npm install -g stylelint Lint CSS in <style> tags .stylelintrc
ESLint (HTML plugin) npm install eslint-plugin-html Lint JavaScript in <script> tags .eslintrc with html plugin
linthtml npm install -g @linthtml/linthtml Fork of HTMLHint with active maintenance .linthtmlrc
SonarQube/SonarLint IDE extension / Server Code quality and security analysis sonar-project.properties

Example: Linting configuration

<!-- .htmlhintrc configuration -->
{
  "tagname-lowercase": true,
  "attr-lowercase": true,
  "attr-value-double-quotes": true,
  "attr-value-not-empty": false,
  "attr-no-duplication": true,
  "doctype-first": true,
  "tag-pair": true,
  "tag-self-close": false,
  "spec-char-escape": true,
  "id-unique": true,
  "src-not-empty": true,
  "title-require": true,
  "alt-require": true,
  "doctype-html5": true,
  "id-class-value": "dash",
  "style-disabled": false,
  "inline-style-disabled": false,
  "inline-script-disabled": false,
  "space-tab-mixed-disabled": "space",
  "id-class-ad-disabled": true,
  "href-abs-or-rel": false,
  "attr-unsafe-chars": true
}

<!-- .prettierrc for HTML formatting -->
{
  "printWidth": 100,
  "tabWidth": 2,
  "useTabs": false,
  "semi": true,
  "singleQuote": false,
  "trailingComma": "none",
  "bracketSpacing": true,
  "htmlWhitespaceSensitivity": "css",
  "endOfLine": "lf"
}

<!-- .editorconfig for consistent style -->
root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

[*.html]
indent_style = space
indent_size = 2

[*.md]
trim_trailing_whitespace = false

<!-- package.json scripts -->
{
  "scripts": {
    "lint:html": "htmlhint 'src/**/*.html'",
    "lint:validate": "html-validate 'src/**/*.html'",
    "format": "prettier --write 'src/**/*.html'",
    "format:check": "prettier --check 'src/**/*.html'",
    "lint": "npm run lint:html && npm run lint:validate",
    "precommit": "npm run lint && npm run format:check"
  }
}

<!-- VSCode settings.json for auto-formatting -->
{
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "[html]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "html.validate.scripts": true,
  "html.validate.styles": true,
  "htmlhint.enable": true,
  "files.eol": "\n",
  "files.insertFinalNewline": true,
  "files.trimTrailingWhitespace": true
}

<!-- Run linting -->
htmlhint src/**/*.html
html-validate src/**/*.html
prettier --check src/**/*.html

<!-- Auto-fix issues -->
prettier --write src/**/*.html
html-validate --fix src/**/*.html
Linting Best Practices: Configure linters early in project. Use pre-commit hooks (husky + lint-staged) to enforce quality. Combine multiple tools (HTMLHint for errors, Prettier for formatting). Share configs across team via .editorconfig. Run linting in CI/CD to catch issues before merge.

19.6 Cross-browser Testing Strategies

Strategy/Tool Type Description Best For
BrowserStack Cloud Testing Platform Real devices and browsers in the cloud Comprehensive testing, mobile devices
Sauce Labs Cloud Testing Platform Automated and manual cross-browser testing CI/CD integration, Selenium tests
LambdaTest Cloud Testing Platform Live interactive and automated testing Real-time debugging, screenshots
Can I Use Reference Website Browser feature support tables Feature detection, planning support
Modernizr JavaScript Library Feature detection library Polyfill loading, conditional features
Playwright Testing Framework Cross-browser automation (Chromium, Firefox, WebKit) E2E testing, screenshot comparison
Selenium Testing Framework Browser automation across platforms Legacy support, wide browser coverage
Cypress Testing Framework Modern E2E testing (Chrome, Firefox, Edge) Fast testing, developer experience
Percy Visual Testing Visual regression testing UI consistency, CSS changes
Polyfill.io CDN Service Automatic polyfills based on user agent Progressive enhancement, legacy browsers

Example: Cross-browser testing setup

<!-- Feature detection with Modernizr -->
<script src="https://cdn.jsdelivr.net/npm/modernizr@3.12.0/modernizr.min.js"></script>
<script>
  // Check feature support
  if (Modernizr.flexbox) {
    console.log('Flexbox supported');
  } else {
    console.log('Flexbox NOT supported - load polyfill');
    // Load flexbox polyfill
  }
  
  if (Modernizr.webp) {
    // Use WebP images
    document.documentElement.classList.add('webp');
  } else {
    // Fallback to JPEG/PNG
    document.documentElement.classList.add('no-webp');
  }
</script>

<!-- Polyfill.io for automatic polyfills -->
<script src="https://polyfill.io/v3/polyfill.min.js?features=default,es2015,es2016,es2017"></script>

<!-- Conditional loading based on feature detection -->
<script>
  // Load polyfill only if needed
  if (!window.Promise) {
    const script = document.createElement('script');
    script.src = 'https://cdn.jsdelivr.net/npm/promise-polyfill@8/dist/polyfill.min.js';
    document.head.appendChild(script);
  }
  
  // IntersectionObserver polyfill
  if (!('IntersectionObserver' in window)) {
    const script = document.createElement('script');
    script.src = 'https://polyfill.io/v3/polyfill.min.js?features=IntersectionObserver';
    document.head.appendChild(script);
  }
</script>

<!-- Playwright test example -->
// playwright.config.js
module.exports = {
  projects: [
    {
      name: 'chromium',
      use: { browserName: 'chromium' }
    },
    {
      name: 'firefox',
      use: { browserName: 'firefox' }
    },
    {
      name: 'webkit',
      use: { browserName: 'webkit' }
    }
  ]
};

// test.spec.js
const { test, expect } = require('@playwright/test');

test.describe('Cross-browser HTML tests', () => {
  test('page title is correct', async ({ page }) => {
    await page.goto('https://example.com');
    await expect(page).toHaveTitle('Example Domain');
  });
  
  test('all images have alt attributes', async ({ page }) => {
    await page.goto('https://example.com');
    const imagesWithoutAlt = await page.$('img:not([alt])');
    expect(imagesWithoutAlt.length).toBe(0);
  });
  
  test('form submits correctly', async ({ page }) => {
    await page.goto('https://example.com/form');
    await page.fill('input[name="email"]', 'test@example.com');
    await page.click('button[type="submit"]');
    await expect(page).toHaveURL(/.*success/);
  });
});

<!-- BrowserStack integration example -->
// wdio.conf.js (WebDriverIO with BrowserStack)
exports.config = {
  user: process.env.BROWSERSTACK_USERNAME,
  key: process.env.BROWSERSTACK_ACCESS_KEY,
  
  capabilities: [
    {
      browserName: 'Chrome',
      browserVersion: 'latest',
      'bstack:options': {
        os: 'Windows',
        osVersion: '11'
      }
    },
    {
      browserName: 'Safari',
      browserVersion: 'latest',
      'bstack:options': {
        os: 'OS X',
        osVersion: 'Monterey'
      }
    },
    {
      browserName: 'Edge',
      browserVersion: 'latest',
      'bstack:options': {
        os: 'Windows',
        osVersion: '10'
      }
    }
  ],
  
  services: ['browserstack']
};
Browser Market Share (2024) Testing Priority Common Issues
Chrome ~65% High - Primary Usually most standards-compliant
Safari ~20% High - iOS critical CSS Grid bugs, date input format, WebP support
Edge ~5% Medium - Chromium-based Similar to Chrome (Chromium engine)
Firefox ~3% Medium - Different engine Flexbox subtle differences, form styling
Samsung Internet ~3% Medium - Android default Older WebKit version, PWA support
Opera ~2% Low - Chromium-based Similar to Chrome
IE 11 EOL <1% Legacy only if required No modern JS, CSS Grid, Flexbox bugs

Development and Testing Workflow

Recommended Testing Stack: Local development: Chrome DevTools + Lighthouse. Validation: html-validate + Prettier. Accessibility: axe DevTools + manual screen reader testing. Performance: Lighthouse CI + WebPageTest. Cross-browser: Playwright for automation + BrowserStack for manual testing. Monitor production with Google Analytics + Core Web Vitals reporting.