<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
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
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-nameadded 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)
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.
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)
5. Hyperlinks and Navigation
5.1 Anchor Elements and Link Types
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"
Link Types
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
Example: Various link types and attributes
<!-- 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.
5.2 URL Schemes and Link Targets
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¶m2=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).
Note: Download attribute only works for same-origin URLs.
Cross-origin downloads require server CORS headers. Data URLs and Blob URLs work universally.
5.5 Email and Telephone Links
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
Example: Email and telephone links
<!-- 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.
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
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.
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.
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.
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
<!-- 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.
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.
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
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.
<!-- 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.
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:
<caption> (optional, first)
<colgroup> (optional)
<thead> (optional)
<tbody> (optional, can have multiple)
<tfoot> (optional, displays at bottom)
Note:<tfoot> can be placed before
<tbody> in HTML but will render at the bottom.
Note: Use <thead>, <tbody>, and
<tfoot> for accessibility and to enable features like fixed headers on scroll. Browser
implicitly creates <tbody> if omitted.
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.
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.
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).
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.
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 valuesconst 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 objectsconst storedUser = JSON.parse(localStorage.getItem('user'));console.log(storedUser.preferences.theme); // "dark"// Remove specific itemlocalStorage.removeItem('theme');// Clear all storagelocalStorage.clear();// sessionStorage (same API, different lifetime)sessionStorage.setItem('tempData', 'session-only');// Iterate through all itemsfor (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.
Example: Geolocation with error handling and options
// Check if geolocation is supportedif ('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.
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)
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 implementationconst routes = { '/': renderHome, '/about': renderAbout, '/contact': renderContact};// Navigate to new page without reloadfunction navigateTo(path) { // Add to history history.pushState({ path }, '', path); // Render new content renderPage(path);}// Render page based on pathfunction 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/forwardwindow.addEventListener('popstate', (e) => { const path = e.state?.path || '/'; renderPage(path);});// Intercept link clicksdocument.addEventListener('click', (e) => { if (e.target.matches('a[href^="/"]')) { e.preventDefault(); navigateTo(e.target.pathname); }});// Example: Update URL without navigationfunction 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 changeswindow.addEventListener('popstate', (e) => { console.log('State:', e.state); console.log('URL:', location.pathname); if (e.state?.filters) { applyFilters(e.state.filters); }});// Control scroll restorationhistory.scrollRestoration = 'manual'; // Manually handle scroll position// Save scroll position before navigationwindow.addEventListener('beforeunload', () => { const scrollPos = { x: window.scrollX, y: window.scrollY }; history.replaceState({ scrollPos }, '');});// Restore scroll positionwindow.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
<!-- Main page (main.html) --><script>// Create workerconst worker = new Worker('worker.js');// Send data to workerworker.postMessage({ operation: 'fibonacci', number: 40});// Receive results from workerworker.onmessage = (e) => { console.log('Result from worker:', e.data); // { result: 102334155, time: 1523 }};// Handle worker errorsworker.onerror = (e) => { console.error('Worker error:', e.message); console.error('File:', e.filename, 'Line:', e.lineno);};// Send multiple messagesworker.postMessage({ operation: 'sort', data: [5, 2, 8, 1, 9] });// Terminate worker when donesetTimeout(() => { worker.terminate(); console.log('Worker terminated');}, 5000);</script><!-- Worker file (worker.js) --><script>// Listen for messages from main threadself.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 scriptsimportScripts('math-utils.js', 'helpers.js');// Handle errorsself.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 workerconst sharedWorker = new SharedWorker('shared.js');// Get message portconst port = sharedWorker.port;// Start the portport.start();// Send messageport.postMessage({ type: 'subscribe', channel: 'chat' });// Receive messagesport.onmessage = (e) => { console.log('Message from shared worker:', e.data);};// Send broadcast to all connected tabsport.postMessage({ type: 'broadcast', message: 'Hello from tab ' + Date.now()});</script><!-- Shared Worker (shared.js) --><script>const connections = [];// Handle new connectionsself.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.
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)
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
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 objectconsole.log(el.shadowRoot.querySelector('.inner')); // <div class="inner">// From outside, can't access shadow DOM with regular queriesconsole.log(document.querySelector('.inner')); // null (not found)// With mode: 'closed', shadowRoot would be nullconst 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.
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 definitionclass 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 dynamicallyconst newItem = document.createElement('li');newItem.textContent = 'Item 4';list.appendChild(newItem); // Triggers slotchange event// Get items from componentconsole.log(list.getItems()); // [li, li, li, li]// Check which slot an element is inconst firstItem = list.querySelector('li');console.log(firstItem.assignedSlot); // <slot> element</script>
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
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
<!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.
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.
<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>
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>
<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><head></code> section of your HTML document:</p><pre><meta name="description" content="Page description"></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)
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.
<!-- 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">
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)
# Allow all bots to crawl everything (default)User-agent: *Allow: /# Block admin areaDisallow: /admin/Disallow: /private/Disallow: /temp/# Block specific file typesDisallow: /*.json$Disallow: /*.xml$Disallow: /cgi-bin/# Allow public files in otherwise blocked directoryAllow: /admin/public/# Sitemap locationSitemap: https://example.com/sitemap.xmlSitemap: https://example.com/sitemap-images.xml# Google-specific botUser-agent: GooglebotCrawl-delay: 0Disallow: /searchAllow: /search/about# Block bad botsUser-agent: BadBotDisallow: /# Block all bots from stagingUser-agent: *Disallow: /staging/
Example: Link to sitemap in HTML
<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
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>
<!-- 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 regionfunction updateStatus(message) { const status = document.querySelector('[role="status"]'); status.textContent = message; // Screen reader announces: "5 new messages"}// Show errorfunction 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.
<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(); } } } }}// Usageconst 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 handlerradios.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
<!-- 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">×</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 handlingconst 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 inputform.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>
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>
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
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.
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
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
'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: <img src=x onerror=alert("XSS")></script><!-- SAFE: HTML entity encoding (server-side) --><div> <!-- User input: <script>alert('XSS')</script> --> <!-- Rendered: <script>alert('XSS')</script> --> <script>alert('XSS')</script></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="...">
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
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.
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)
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.
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>
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 eventsmonitorEvents($0) // All eventsmonitorEvents($0, 'click') // Specific eventmonitorEvents($0, ['click', 'keydown']) // Multiple eventsunmonitorEvents($0) // Stop monitoring// Inspect elementinspect($('button')) // Select element in Elements panel// Get event listenersgetEventListeners($0) // All listeners on element// Copy to clipboardcopy($0.outerHTML) // Copy element HTMLcopy($('a').map(a => a.href)) // Copy all link URLs// Debug DOM modificationsconst 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 observingobserver.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 elementsdocument.querySelectorAll('*').length$('*').length // Same// Find duplicate IDsconst 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 pagepa11y https://example.com# Test with specific standardpa11y --standard WCAG2AA https://example.com# Generate reportpa11y --reporter json https://example.com > report.json# Test multiple pagespa11y-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" ]}
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.