HTML5 - Rules and Conventions
1. Philosophy
- Semantics first — Every tag has meaning. Prefer
<nav>,<article>and<aside>over generic<div>s. HTML must stay readable without CSS. - Accessibility by default — WCAG AA is the minimum:
lang,alt, labels, contrast, keyboard access, hierarchical headings. - Progressive enhancement — Content works without JavaScript; JS only adds enhancements, never required to read or operate.
- Structure over presentation — HTML defines meaning; CSS owns looks.
- Less is more — minimal semantic markup, no purposeless containers.
2. Minimum Versions
| Technology | Version |
|---|---|
| HTML | Living Standard (WHATWG HTML5) |
Baseline-first policy: prefer elements and attributes labeled Baseline on MDN; experimental features require a documented guard before use.
3. Base Structure
Start every page from this skeleton: skip link, primary landmarks, single
<main>.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="Page description" />
<title>Page title</title>
<link rel="stylesheet" href="css/styles.css" />
</head>
<body>
<a class="skip-link" href="#main">Skip to main content</a>
<header>
<nav aria-label="Primary">
<ul>
<li><a href="/">Home</a></li>
</ul>
</nav>
</header>
<main id="main">
<article>
<h1>Main title</h1>
<p>Article content</p>
</article>
</main>
<footer><p>© 2026</p></footer>
</body>
</html>
The skip link must be the first focusable element and target #main; its
show-on-focus styling is CSS work (see CSS).
Doctype, Lang and hreflang
Rule: always declare <!DOCTYPE html> and set lang on <html>;
multilingual sites link every alternative version, including themselves.
Why: missing lang breaks screen readers; missing hreflang splits
ranking across duplicate pages.
<link rel="alternate" hreflang="es" href="https://example.com/es/" />
<link rel="alternate" hreflang="en" href="https://example.com/en/" />
<link rel="alternate" hreflang="x-default" href="https://example.com/" />
4. Semantic Landmarks
| Tag | Purpose |
|---|---|
<header> |
Page or section header |
<nav> |
Navigation (label when multiple) |
<search> |
Search / find (Baseline 2023) |
<main> |
Main content (unique per page) |
<article> |
Independent content (post, card) |
<section> |
Thematic grouping (needs heading) |
<aside> |
Complementary content |
<footer> |
Page or section footer |
For anything beyond these landmarks (content models, text-level semantics
such as <time> or <abbr>), consult the MDN HTML elements index.
5. Tables
Data tables only; layout tables are prohibited. Give every header cell a
scope (col, row, colgroup, rowgroup), add a <caption>, and use
<colgroup> for column targeting.
<table>
<caption>
Sales by quarter 2026
</caption>
<colgroup>
<col span="2" class="col--figures" />
<col />
</colgroup>
<thead>
<tr>
<th scope="col">Quarter</th>
<th scope="col">Sales</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Q1</th>
<td>$10,000</td>
</tr>
</tbody>
</table>
scope values: col, row, colgroup, rowgroup.
6. Forms
HTML owns form structure: styling is CSS (CSS); submission behavior and complex validation logic are JavaScript (JavaScript).
Accessible Structure
<form action="/api/contact" method="POST">
<fieldset>
<legend>Contact information</legend>
<!-- Repeat this pattern for every control -->
<label for="name">Full name</label>
<input
type="text"
id="name"
name="name"
autocomplete="name"
required
minlength="3"
aria-describedby="name-hint name-error"
/>
<span id="name-hint">Minimum 3 characters</span>
<span id="name-error" role="alert"></span>
<button type="submit">Send</button>
</fieldset>
</form>
Non-negotiables: every control has a <label> (a placeholder is never a
label); related controls group under <fieldset> + <legend>; hints and
errors link via aria-describedby.
Rule: let native validation run. Add novalidate only when JavaScript
owns the entire validation flow, and then announce errors with
role="alert" plus aria-invalid="true" on invalid fields.
Why: disabling native validation without replacing its announcements
breaks error reporting for assistive technology.
Search Landmark
<search> wraps a form and exposes the landmark implicitly (Baseline
2023); an inner role="search" would duplicate it.
Input Types
Pick the most specific type: right mobile keyboard plus free validation.
| Type | Use | Native validation |
|---|---|---|
text |
Generic text | minlength, maxlength, pattern |
email |
Email address | Email format |
password |
Passwords | minlength |
tel |
Phone | pattern |
url |
URLs | URL format |
number |
Numbers | min, max, step |
date |
Dates | min, max |
file |
File upload | accept |
checkbox |
Multiple options | — |
radio |
Single option | — |
hidden |
Hidden data (non-sensitive) | — |
Native Validation
Declarative constraints first — they validate without a single line of JS:
<input type="text" required minlength="3" maxlength="50" pattern="[A-Za-z]+" />
<input type="email" required aria-describedby="email-error" />
<span id="email-error" role="alert"></span>
Custom error messages and real-time aria-invalid wiring are JavaScript
behavior (see JavaScript).
Autocomplete
Always set autocomplete on identity and payment fields: browsers and
password managers fill them correctly, and audits check for it. Common
tokens: name, email, tel, street-address, postal-code,
cc-number, new-password, current-password.
7. Multimedia
When: any image, video or audio embed.
Rule: declare intrinsic dimensions (width/height) so the browser
reserves space before load.
Why: unreserved space makes media cause layout shift (CLS).
Images
<!-- Responsive image -->
<img
src="img/photo-800.jpg"
srcset="
img/photo-400.jpg 400w,
img/photo-800.jpg 800w,
img/photo-1200.jpg 1200w
"
sizes="(max-width: 600px) 100vw, 50vw"
width="800"
height="600"
alt="Image description"
loading="lazy"
decoding="async"
/>
<!-- Art direction: different crops per breakpoint -->
<picture>
<source media="(min-width: 1024px)" srcset="img/hero-desktop.webp" />
<img
src="img/hero-mobile.jpg"
width="1200"
height="630"
alt="Hero banner"
loading="eager"
fetchpriority="high"
/>
</picture>
<!-- Image + caption: wrap in <figure> + <figcaption> -->
Use <img srcset/sizes> for resolution switching; <picture> only for art
direction or format negotiation. Icons and logos are inline SVG.
Video and Audio
<video controls poster="img/thumbnail.jpg" preload="metadata" playsinline>
<source src="video/intro.webm" type="video/webm" />
<source src="video/intro.mp4" type="video/mp4" />
<track
src="captions/intro-en.vtt"
kind="captions"
srclang="en"
label="English"
default
/>
</video>
<audio controls preload="metadata">
<source src="audio/podcast.mp3" type="audio/mpeg" />
</audio>
Captions (<track kind="captions">) are mandatory for content videos.
Autoplay background video must include muted (plus loop playsinline).
8. Interactive Components
Prefer the native component before any library: focus management and keyboard behavior ship for free.
Dialog (modal)
When: modal flows — confirmations, focused subtasks.
Rule: use <dialog> + showModal(); never hand-roll modals with divs.
Why: focus trapping, Esc-to-close and backdrop come free with
showModal(); hand-rolling them accessibly is error-prone.
<dialog id="modal">
<form method="dialog">
<h2>Confirm action</h2>
<menu>
<button value="cancel">Cancel</button>
<button value="confirm" autofocus>Confirm</button>
</menu>
</form>
</dialog>
<!-- Open: document.getElementById("modal").showModal() -->
Style ::backdrop via CSS (see CSS); ARIA details for
dialogs live in Accessibility.
Details / Summary
When: non-critical progressive disclosure (FAQ, accordions).
Rule: use native <details>; add JS only for exclusive-open behavior.
Why: the native toggle works without JS and is announced correctly.
<details>
<summary>More information</summary>
<p>Expandable content.</p>
</details>
Popover API
When: non-modal overlays — menus, tooltips, notifications.
Rule: use declarative popovers (Baseline 2024); reserve <dialog> for
modal flows.
Why: popovers get light-dismiss and Esc handling with zero JS.
<button popovertarget="menu" popovertargetaction="toggle">Open</button>
<div id="menu" popover role="menu">
<button popovertarget="menu" popovertargetaction="hide">Close</button>
</div>
JS control: .showPopover() / .hidePopover() / .togglePopover(); style
via [popover]::backdrop and :popover-open. Match the ARIA role to the
content — a bare popover div has no semantics.
inert
When: hidden-but-present UI — closed panels, off-screen slides.
Rule: apply the inert attribute (Baseline 2023) instead of tabindex
juggling or pointer-events hacks.
Why: removes the whole subtree from AT output and hit-testing at once.
<div id="sidebar" inert><!-- closed panel --></div>
<!-- Enable: sidebar.removeAttribute("inert") -->
9. Declarative Components
HTML-native reusable markup via <template> and <slot>; imperative
custom elements belong to the JavaScript skill.
<template id="card-template">
<article class="card">
<h2><slot name="title">Default title</slot></h2>
<div><slot></slot></div>
</article>
</template>
<!-- <my-card><span slot="title">T</span><p>Body</p></my-card> -->
<template> content stays inert until cloned; slots map slot
attributes to insertion points.
10. Graphics
Inline SVG for icons, logos and static graphics; Canvas only for dynamic raster drawing (per-frame charts, image manipulation).
<!-- Decorative icon: hidden from AT -->
<svg
width="24"
height="24"
viewBox="0 0 24 24"
aria-hidden="true"
focusable="false"
>
<path d="M12 2L2 7l10 5 10-5-10-5z" />
</svg>
<!-- Sprite: define once (<defs>), reuse via <use href="#id"> -->
<!-- Canvas needs an accessible name + fallback content -->
<canvas id="chart" width="400" height="300" role="img" aria-label="Sales chart"
>Canvas not supported.</canvas
>
11. SEO
Essential Meta
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="Unique 150-160 character description" />
<link rel="canonical" href="https://example.com/page" />
<meta property="og:title" content="Title" />
<meta property="og:image" content="https://example.com/image.jpg" />
<meta name="twitter:card" content="summary_large_image" />
index, follow is the default; add <meta name="robots"> only to restrict
crawling. One <h1> per page, strict hierarchy without skipped levels.
Structured Data — JSON-LD
Use JSON-LD (<script type="application/ld+json">): structured data stays
decoupled from markup. Common types: Article, BreadcrumbList,
Product, Organization, FAQPage.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "Article title",
"description": "Brief description",
"datePublished": "2026-07-15",
"author": { "@type": "Person", "name": "Author" }
}
</script>
Microdata (itemscope/itemprop) is a legacy alternative — do not use it
in new code unless the project already standardizes on it.
12. Performance
HTML-layer rules only. Measurement, budgets, caching and bundling live in the Performance skill.
Resource Hints
<link
rel="preload"
href="fonts/inter.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link rel="dns-prefetch" href="https://analytics.example.com" />
<link rel="prefetch" href="/products" as="document" />
Preload critical assets only (fonts require crossorigin). The Speculation
Rules API (type="speculationrules") is not Baseline — gate behind a
browser check.
Scripts
<script src="js/app.js" defer></script>
<!-- default: ordered, non-blocking -->
<script src="js/analytics.js" async></script>
<!-- independent scripts -->
<script type="module" src="js/main.js"></script>
<!-- deferred by default -->
Never ship parser-blocking <script src> in <head> without defer.
Lazy Loading
When: media below the fold.
Rule: loading="lazy" for off-viewport images and iframes; the LCP
image is always loading="eager" + fetchpriority="high" (Baseline 2024).
Why: lazy-loading the LCP image delays the metric it should optimize.
<img src="photo.jpg" width="800" height="600" loading="lazy" />
<iframe src="widget.html" loading="lazy" title="Widget description"></iframe>
<img
src="hero.jpg"
width="1200"
height="630"
loading="eager"
fetchpriority="high"
/><!-- LCP / hero: never lazy -->
13. Favicons and PWA
<link rel="icon" href="/favicon.ico" sizes="any" />
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<link rel="manifest" href="/manifest.json" />
<meta name="theme-color" content="#6366f1" />
Minimal installable manifest — skip advanced fields (display_override,
shortcuts) until the project needs them:
{
"name": "My App",
"short_name": "App",
"start_url": "/",
"display": "standalone",
"theme_color": "#6366f1",
"icons": [
{ "src": "/icon-192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/icon-512.png", "sizes": "512x512", "type": "image/png" }
]
}
Service worker registration (offline strategy lives in the Performance skill, Service Workers):
<script>
if ("serviceWorker" in navigator) navigator.serviceWorker.register("/sw.js");
</script>
14. Best Practices
Choosing the Right Container
| Context | Use | Why |
|---|---|---|
| Generic wrapper, no meaning needed | <div> |
No semantics required |
| Thematic group WITH heading | <section> |
Defines a section |
| Self-contained content (post, product) | <article> |
Independent, syndicatable |
| Complementary (sidebar, related) | <aside> |
Complementary landmark |
| Unique main content | <main> |
Main landmark |
| Navigation | <nav> |
Navigation landmark |
| Search form | <search> |
Search landmark |
| Image + caption | <figure> |
Groups media with caption |
Images: responsive static → <img srcset/sizes>; art direction or format
switching → <picture>; icons and logos → inline <svg>.
Links vs Buttons
Navigation goes to another location → <a href>; triggers an in-page
action → <button type="button">. Never swap roles with onclick hacks.
<a href="/page">Go to page</a>
<button type="button">Save</button>
<button type="submit">Submit form</button>
<a
><!-- ❌ link acting as button -->
Links
Descriptive text (never "click here"); rel="noopener noreferrer" on
target="_blank"; explicit protocols for phone, email and downloads.
<a href="/products">View all products</a>
<a href="https://external.com" target="_blank" rel="noopener noreferrer"
>External site</a
>
<a href="tel:+521234567890">(123) 456-7890</a>
<a href="/docs/manual.pdf" download>Download manual (PDF)</a>
Element-level security ends here: CSP, iframe sandboxing, XSS and CORS are owned by the Security skill.
Navigation Current Page
Mark the active item so users and AT know where they are:
<nav aria-label="Primary">
<ul>
<li><a href="/" aria-current="page">Home</a></li>
<li><a href="/about">About us</a></li>
</ul>
</nav>
15. Methodology
Before using any element, attribute or pattern not documented in this skill:
- MCP Context7 (priority) — resolve the library and query its docs.
- MDN Web Docs — confirm semantics, Baseline status, browser support.
- Can I Use — verify against the project's browserslist targets.
- Official spec — WHATWG HTML Living Standard for edge cases.
Hard rule: if it is neither in this skill nor verifiable against two authoritative sources, DO NOT USE IT. Document it as an assumption or risk to the orchestrator.
16. Prohibitions
- No
<div>-soup — use semantic tags; no tables for layout - No
<br>as paragraph separator — use<p> - No
<strong>/<em>purely for visual bold/italic - No inline styles or inline event handlers (
style=,onclick=) - No omitted
alton informative images (decorative usesalt="") - No undefined
lang, no skipped heading levels (h1→h3) - No
javascript:void(0)links; no obsolete tags (marquee,font) - No duplicate
<main>, no text-only-in-image without equalalt - No placeholder-only labels, no passwords sent via GET
- No blind
crossoriginon<img>/<script>(needs CORS origin) - Never lazy-load the LCP / above-the-fold image
17. References
Styling and layout: CSS Behavior, DOM and validation logic: JavaScript WCAG rules and testing: Accessibility Core Web Vitals and loading: Performance CSP, XSS, sandboxing and CORS: Security
Last updated: 2026-08