# HTML

> HTML5 semantic rules - landmarks, forms structure, multimedia, interactive components, SEO (JSON-LD), performance and declarative components

- Skill: `14bryanespinoza/html` (Agent Skill)
- Install (CLI): `npx skillmds@latest add 14bryanespinoza/html`
- Raw SKILL.md: https://api.skillmd.com/api/skills/14bryanespinoza/html/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Marketing & Growth
- Author: 14BryanEspinoza (https://skillmd.com/u/14bryanespinoza)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/14bryanespinoza/html

---


# HTML5 - Rules and Conventions

---

## 1. Philosophy

1. **Semantics first** — Every tag has meaning. Prefer `<nav>`, `<article>`
   and `<aside>` over generic `<div>`s. HTML must stay readable without CSS.
2. **Accessibility by default** — WCAG AA is the minimum: `lang`, `alt`,
   labels, contrast, keyboard access, hierarchical headings.
3. **Progressive enhancement** — Content works without JavaScript; JS only
   adds enhancements, never required to read or operate.
4. **Structure over presentation** — HTML defines meaning; CSS owns looks.
5. **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>`.

```html
<!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>&copy; 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](../css/SKILL.md)).

### 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.

```html
<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.

```html
<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](../css/SKILL.md));
submission behavior and complex validation logic are JavaScript
([JavaScript](../javascript/SKILL.md)).

### Accessible Structure

```html
<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:

```html
<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](../javascript/SKILL.md)).

### 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

```html
<!-- 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

```html
<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.

```html
<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](../css/SKILL.md)); ARIA details for
dialogs live in [Accessibility](../accessibility/SKILL.md).

### 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.

```html
<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.

```html
<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.

```html
<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](../javascript/SKILL.md).

```html
<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).

```html
<!-- 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

```html
<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`.

```html
<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](../performance/SKILL.md).

### Resource Hints

```html
<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

```html
<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.

```html
<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

```html
<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:

```json
{
  "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](../performance/SKILL.md), Service Workers):

```html
<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.

```html
<a href="/page">Go to page</a>
<button type="button">Save</button>
<button type="submit">Submit form</button>
<a onclick="save()">Save</a
><!-- ❌ link acting as button -->
```

### Links

Descriptive text (never "click here"); `rel="noopener noreferrer"` on
`target="_blank"`; explicit protocols for phone, email and downloads.

```html
<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](../security/SKILL.md).

### Navigation Current Page

Mark the active item so users and AT know where they are:

```html
<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:

1. **MCP Context7 (priority)** — resolve the library and query its docs.
2. **MDN Web Docs** — confirm semantics, Baseline status, browser support.
3. **Can I Use** — verify against the project's browserslist targets.
4. **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 `alt` on informative images (decorative uses `alt=""`)
- 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 equal `alt`
- No placeholder-only labels, no passwords sent via GET
- No blind `crossorigin` on `<img>`/`<script>` (needs CORS origin)
- Never lazy-load the LCP / above-the-fold image

## 17. References

> Styling and layout: [CSS](../css/SKILL.md)
> Behavior, DOM and validation logic: [JavaScript](../javascript/SKILL.md)
> WCAG rules and testing: [Accessibility](../accessibility/SKILL.md)
> Core Web Vitals and loading: [Performance](../performance/SKILL.md)
> CSP, XSS, sandboxing and CORS: [Security](../security/SKILL.md)

Last updated: 2026-08

