---
title: 'Web Fundamentals and HTML'
source: 'https://academia.sh/en/courses/web-fundamentals-and-html'
language: en
updated: '2026-08-17T04:41:05+00:00'
license: 'CC BY-SA 4.0'
lessons: 25
outcomes:
    - 'Describes the life cycle of a page from request to paint'
    - 'Structures a document with semantic tags'
    - 'Builds an accessible form and configures its validation behavior'
---

# Web Fundamentals and HTML

A document's transformation from bytes into a node tree, its structuring with semantic markup, and taking data from the user through an accessible form.

## How the Web Works

What a markup language is, the stages the browser goes through from bytes to paint, the node tree's origin, and how resource loading affects parsing.

### Markup Language Concept

Source: https://academia.sh/en/courses/web-fundamentals-and-html/markup-language-concept

Markup's definition as a structural declaration added to text; the structure/presentation/behavior split and a declarative language's limits.

The How the Internet Works course established how a name typed into the address bar reaches
a server, and that the server returns a response body. How that body is carried, which
header describes it, and how the connection is established were explained there. One
question was left open: what **is** the body itself?

This course answers that question. The response body is most often an HTML document, and
HTML is a **markup language**. This lesson defines what markup is, what it does and does not
define, and why it exists as a separate language.

#### Markup Is a Declaration Added to Text

Markup is extra information interspersed into plain text that states what its parts **are**.
The term comes from proofreading marks in print publishing: on a manuscript sent to the
typesetter, a word underlined would say "set this in emphasis," a note in the margin would
say "this is a heading." The mark itself was never printed; it described what would be
printed.

HTML does the same job. In the text below, there are two kinds of bytes: the ones meant to be
read, and the ones that describe what is meant to be read.

```js
// markup.mjs — separates markup from text
const document_ = `<h1>North Slope Station</h1>
<p>The station sits at an <abbr title="elevation above sea level">EASL</abbr> of 1840 meters
and measures <strong>four</strong> quantities.</p>`;

let content = "";
let structure = "";
let i = 0;
while (i < document_.length) {
  const k = document_.indexOf("<", i);
  if (k === -1) { content += document_.slice(i); break; }
  content += document_.slice(i, k);
  const end = document_.indexOf(">", k);
  structure += document_.slice(k, end + 1);
  i = end + 1;
}

console.log("--- content only ---");
console.log(content.trim());
console.log("--- structure only ---");
console.log(structure);
console.log("--- byte count ---");
console.log("total     :", Buffer.byteLength(document_));
console.log("content   :", Buffer.byteLength(content));
console.log("structure :", Buffer.byteLength(structure));
```

```
--- content only ---
North Slope Station
The station sits at an EASL of 1840 meters
and measures four quantities.
--- structure only ---
<h1></h1><p><abbr title="elevation above sea level"></abbr><strong></strong></p>
--- byte count ---
total     : 172
content   : 92
structure : 80
```

Neither part is complete on its own. The content part is readable but structureless: which
line is a heading, what the abbreviation `EASL` stands for, is lost. The structure part
carries no content at all. A document is the union of both, and in this example the two are
close in size — the structure declaration is not far behind the content itself. This ratio
differs from document to document; as information density rises, structure's share falls.

What matters here is that the structure declaration does not **disappear**. Even though the
explanation carried in the `abbr` element's `title` attribute is not shown on screen, it
stays in the document; a program or a person who cannot expand the abbreviation can still
consult it. Markup is a layer that is invisible but queryable.

#### Three Separate Responsibilities

A web page answers three separate questions, and these three questions are answered by three
separate languages.

| Question | Layer | Language |
|---|---|---|
| What **is** this content? | Structure | HTML |
| How will it **look**? | Presentation | CSS |
| What **happens** when the user interacts? | Behavior | JavaScript |

This split is not a matter of style, it is a decision about maintenance cost. The same
document can be shown with different presentations: on a narrow screen, on a printer, on a
screen reader, in a search engine's indexer. All of these read the same structure; only the
presentation layer changes. Had structure and presentation been defined in the same place,
the document itself would need to be rewritten for every new environment.

The split's violation is seen in a concrete example. Using an `h1` element to show text large
and bold is making a structural declaration for a presentational purpose: it tells the
document "this is a first-level heading," and every program that reads that declaration is
misled. The reverse holds too: writing a heading with a `p` element and only enlarging it
with styling destroys the structural information.

This course builds the structure layer. The presentation layer is covered in the next course,
the behavior layer in the JavaScript courses; throughout this course, styling and scripting
appear only to the extent that they relate to structure.

#### A Declarative Language

The Programming Fundamentals course defined statements, expressions, and control flow. HTML
is not a programming language in that sense: it cannot build a condition, compile a loop,
hold a variable, or perform a computation. What is written is not a sequence of commands, it
is a **declaration**.

Being declarative has two consequences.

First, the order in which the document is processed is not under the author's control. When
a document is written, it does not say "do this, then do that"; it says "this is a heading,
this is a list, this is a link." The program reading these declarations decides what to do
with them. The same document is rendered in a browser, parsed by an indexer, converted to
another form by a converter.

Second, behavior in the face of an error is different. The syntax-error concept from the
Programming Fundamentals course does not work the same way here: a flawed HTML document does
not refuse to be processed. The parser applies defined error-recovery rules and produces a
result. We will look at these rules in detail in a later lesson; what matters for now is that
there is no such output as "invalid document." Every document is turned into some result — the
question is whether the result it is turned into matches the author's intent.

A third consequence concerns the language's extensibility. When the parser meets an element
name it does not recognize, it does not reject the document; it builds an element carrying
that name and processes its content normally. It also keeps an unrecognized attribute. So
when a new element is added to the language, implementations that do not yet recognize it do
not lose the document entirely: the new element's extra meaning is lost, its content remains.
We saw the same principle for HTTP headers in the How the Internet Works course; an
unrecognized header is ignored, not treated as invalid. Extensibility, in both designs, is
bought with tolerance toward error.

#### Element, Tag, and Document

Three words are often confused; their distinctions will be kept throughout the course.

A **tag** is the writing between angle brackets in the source text: `<p>` is an opening tag,
`</p>` is a closing tag. A tag is a part of the source text.

An **element** is the whole formed by an opening tag, a closing tag, and the content between
them. An element is the conceptual unit that exists after parsing; it is found not in the
source text, but in the structure produced from it.

A **document** is the whole formed by elements.

This distinction may look like unnecessary precision, but it will be decisive in the next
lesson when parsing stages are examined: some elements have no tag at all in the source text,
and yet they are present in the document.

#### The Document Followed Throughout the Course

This course proceeds on a single example document: a **measurement station page**. A station
named North Slope is built at an elevation of 1840 meters above sea level and measures
temperature, relative humidity, wind speed, and precipitation. The page will carry the
station's identification, a measurement table, a location image, and finally a form where
observers can report a data correction.

The document grows throughout the course. In this lesson it is only a single sentence's
worth; by the course's end it will be a complete, multi-section page carrying media and a
validatable form. Every lesson adds a layer to this document and justifies why the added
layer takes the form it does.

#### Summary

- Markup is a declaration layer, mixed into the text, that states what the text's parts are;
  markup bytes are carried in the same document as content bytes but are not part of the text
  meant to be read.
- A page answers three separate questions: what the content is (structure), how it will look
  (presentation), what happens (behavior). This course builds only the structure layer.
- HTML is a declarative language: it carries no control flow, variables, or computation; the
  program reading the declaration decides what to do.
- A flawed document does not refuse to be processed; the parser always produces a result
  using defined error-recovery rules.
- A tag is part of the source text, an element is the unit formed after parsing; the two do
  not map onto each other one to one.

#### Next Step

This lesson defined what markup is, but not what the browser does with it. The What the
Browser Does lesson in the How the Internet Works course listed the stages under headings:
parsing, layout, painting. The next lesson takes up these stages from the document's own
point of view, and follows which transformations the incoming bytes pass through to become
something on screen, showing each stage's output.

### Browser Tasks

Source: https://academia.sh/en/courses/web-fundamentals-and-html/browser-tasks

Incoming bytes turning into characters, characters into tokens, tokens into a tree, and the tree into boxes on screen; each stage's input and output.

The previous lesson established that markup is a declaration layer and showed that a
document is made of text meant to be read plus the structure that describes it. What is left
is the question of what the browser does with that declaration.

The What the Browser Does lesson in the How the Internet Works course covered the chain from
resolving the address, to establishing a connection, to requesting subresources. This lesson
opens one link of that chain: what happens once the response body arrives. The stages are
linked by an input–output relationship, and each one's output is written in the next one's
language.

#### The Chain of Stages

Once the document arrives, six transformations are applied in sequence.

| Stage | Input | Output |
|---|---|---|
| Decoding | byte sequence | character sequence |
| Tokenization | character sequence | token stream |
| Tree building | token stream | node tree |
| Style computation | node tree + style rules | styled tree |
| Layout | styled tree | boxes' positions and sizes |
| Paint | boxes | pixels |

Every link in the chain carries a separate responsibility. The tokenizer does not know where
an element belongs; the tree builder does not know the character encoding; layout does not
know what the tokens were. This separation is what lets any one stage be changed
independently.

#### Decoding: From Byte to Character

What comes over the network is bytes. As established in the Character Encodings lesson of
the How Computers Work course, turning a byte sequence into a character sequence requires a
**rule**; the bytes themselves do not say which rule they were written with.

```js
// tokens.mjs — byte sequence to token stream
const bytes = Buffer.from('<p class="measurement">Temperature: <b>-4</b>°C</p>', "utf8");
console.log("byte count      :", bytes.length);

const text = bytes.toString("utf8"); // the charset declaration decides this step
console.log("char count      :", text.length);
console.log("--- tokens ---");

let i = 0;
while (i < text.length) {
  const k = text.indexOf("<", i);
  if (k === -1) { console.log("TEXT    ", JSON.stringify(text.slice(i))); break; }
  if (k > i) console.log("TEXT    ", JSON.stringify(text.slice(i, k)));
  const end = text.indexOf(">", k);
  const inner = text.slice(k + 1, end);
  if (inner.startsWith("/")) {
    console.log("CLOSE   " + inner.slice(1));
  } else {
    const space = inner.search(/[\s/]/);
    const name = space === -1 ? inner : inner.slice(0, space);
    const rest = space === -1 ? "" : inner.slice(space).trim();
    console.log("OPEN    " + name + (rest ? "  [" + rest + "]" : ""));
  }
  i = end + 1;
}
```

```
byte count      : 52
char count      : 51
--- tokens ---
OPEN    p  [class="measurement"]
TEXT     "Temperature: "
OPEN    b
TEXT     "-4"
CLOSE   b
TEXT     "°C"
CLOSE   p
```

The first two lines show why decoding is a separate stage at all: 52 bytes correspond to 51
characters. The difference of one comes from the `°` sign taking two bytes rather than one in
this encoding. This distinction between byte count and character count matters in every
computation built on a document's length.

Which rule decoding follows is usually declared inside the document itself; when the transport layer
declares an encoding, that one takes precedence. We will look at this declaration's form and what order it
is looked for in, in the Meta Tags lesson.

#### Tokenization: From Character to Token

The second block of the output above is this stage's product. The unit this stage produces
is called a **token**; the concept was defined under the same name in the Compilation Stages
lesson of the How Computers Work course. The tokenizer reads the character sequence from left
to right and produces three kinds of unit: an opening tag, a closing tag, and text. There are
separate token kinds for comments and the document type declaration too.

What the tokenizer does not do is worth noting. It does not say that the `b` element is
inside the `p` element; it only gives the tokens' sequence. Nesting information is a result
to be built from the sequence; it is not in the token itself. In the same way, the tokenizer
does not treat the `p` element being unclosed as a problem either: if the closing token never
comes, it is not produced at all.

The tokenizer is a **state machine**: how a given character is interpreted depends on the
state it is currently in. While in text state, the character `<` is taken as the start of a
tag; while in tag-name state, whitespace announces the name has ended; while in attribute-value
state, a `<` character inside quotes is an ordinary character. This is why "parsing HTML with
a regular expression" does not give a correct result in the general case: regular expressions
cannot build this state-to-state behavior. The script above only works correctly on an input
as narrow as this example.

#### Tree Building

A token stream is sequential and flat; a document is nested. The tree builder establishes
this nesting by pushing opening tokens onto a stack and popping the stack on closing tokens.
Its details are the next lesson's subject.

#### Style Computation and Box Generation

Once the tree is built, what each element is is known, but not what it will look like. The
style computation stage calculates the style values to apply to every element. Part of these
values comes from rules attached to the document from outside, part from the browser's own
default rules.

The existence of default styles matters: a document with no style file attached at all still
looks structured — headings are large and bold, lists are indented, links are distinguishable.
This look does not come from the elements' nature, it comes from the default rule set the
browser carries for every element. Calling elements "the bold one" or "the indented one" is
therefore misleading; these are the presentation layer's defaults, and they can be changed.

Style computation's output is a **box** for every visible element. The box's kind depends on the element's
style value: some boxes stretch across a line, some flow inside text. Some elements produce no box at all;
content in the `head` section and elements given `display: none` are like this.

#### Layout and Paint

**Layout** computes the boxes' position and size relative to one another. This computation
starts from the available width and proceeds down the tree: every box knows the space
allotted to it and distributes it to its children. The output is a position and a size for
every box.

**Paint** turns the computed boxes into colors. Backgrounds, borders, text, and images are
drawn in sequence. The paint order is separate from the elements' order of appearance in the
document: one element appearing on top of another is the style layer's decision.

Separating these two stages also separates the cost of redoing work. When an element's color
changes, only paint is repeated; when its width changes, layout is repeated too, and layout is
more expensive because the changed box also affects its neighbors and children. This
distinction is the foundation for the performance subject in later courses.

#### Boxes Do Not Map One to One onto Elements

The number of elements in the document and the number of boxes on screen are not equal, and
this is the reason layout is a separate stage at all. There are three kinds of divergence.

An element **may produce no box at all**. Content in the `head` section, comment nodes, and elements given
`display: none` do not enter layout; an element hidden with `visibility: hidden` still produces its box
and is merely not painted. These elements stay in the tree, can be queried, and can be changed by scripts;
they are just not painted.

An element **may produce more than one box**. The text of a paragraph that spans two lines is
split into two line boxes; if a link's text falls at a line break, it splits into two pieces
and each piece gets its own position and size. The element itself is single; its box is not.

A box **may correspond to no element at all**. The presentation layer can generate content
that is not in the document; list markers and generated content are of this kind. These boxes
correspond to no node in the tree.

These three divergences make concrete the difference between "changing the document" and
"changing the appearance." The tree carries the document's meaning; boxes are the appearance
that meaning takes on at a particular width, with a particular style.

#### The Stages Overlap

The chain does not work in a way where the second stage waits for the first to finish
entirely. Parsing begins before the whole document has arrived; once part of the tree is
built, an initial layout computation can be done and an initial paint can happen. That a user
sees the first screen of a long document before the whole document has downloaded is a result
of this overlap.

There are places where the overlap breaks: some resources halt parsing, some hold up paint.
This is the subject of this topic's fourth lesson.

#### Summary

- A document passes through six transformations in sequence: decoding, tokenization, tree
  building, style computation, layout, paint. Each stage's output is the next one's input.
- The decoding stage turns a byte sequence into a character sequence; byte count and character
  count are not equal, and the rule for the conversion is declared in the document.
- The tokenizer is a state machine and only produces a sequence; it carries no nesting
  information. This is why markup cannot be parsed with a regular expression in the general
  case.
- Elements' default appearance comes not from their nature, but from the browser's default
  style rules.
- Layout computes boxes' position and size, paint computes their appearance; which stage a
  change reruns decides its cost.
- The stages overlap: parsing can begin before the whole document has arrived, and paint can
  begin before the whole tree has been built.

#### Next Step

This lesson passed over the tree-building stage in one sentence: a token stream is turned into
a tree. Yet that tree is the foundation for everything after it — style is applied to it,
layout is computed over it, scripts change it. The next lesson shows step by step how the tree
is built: how the stack works, how elements that are not in the source text come into being,
and why two different ways of writing produce the same tree.

### Document Object Model Concept

Source: https://academia.sh/en/courses/web-fundamentals-and-html/document-object-model-concept

The token stream's conversion into a node tree, how the stack works, node types, and the disconnect between the source text and the tree.

The previous lesson passed over the tree-building stage in one sentence: a token stream is
turned into a tree. This lesson opens that sentence. The tree is not an intermediate product
in processing the document, it is the **actual product**: style is applied to it, layout is
computed over it, scripts read and change it. The source text is never used again once the
tree is built.

This tree's name is the **Document Object Model** — DOM for short. The name says two things
at once: it is a **model** of the document (not the source text itself, but a structured
representation built from it), and it is made of **objects** (every node is a unit that can
be queried and changed).

#### Why a Tree

The Tree Terminology lesson in the Data Structures course defined a tree as a structure where
every node has a single parent and ordered children. The structure markup produces is exactly
this, because markup itself is nested: an element is either entirely inside another element or
entirely outside it. Partial overlap — half of an element being inside, half outside — is not
defined.

This constraint is what makes tracking nesting with a stack possible. The Stacks lesson in the
Data Structures course defined last-in-first-out behavior; the tree builder uses exactly this:
an opened element is placed on the stack, a closed element is taken off the stack, a new node
always becomes the child of the element sitting at the top of the stack.

#### The Building Algorithm

The script below implements the core of the algorithm that builds a tree from a token stream.
Two rules have been added to the previous lesson's tokenizer: void elements are not put on the
stack, and some elements close on their own when another element is opened.

```js
// tree.mjs — builds a node tree from a token stream
const VOID_TAG = new Set(["br", "hr", "img", "input", "link", "meta"]);
const CLOSES = { li: ["li"], p: ["p"], h1: ["p"], div: ["p"], ul: ["p"] };

function tokenize(text) {
  const tokens = [];
  let i = 0;
  while (i < text.length) {
    const k = text.indexOf("<", i);
    if (k === -1) { tokens.push({ kind: "text", value: text.slice(i) }); break; }
    if (k > i) tokens.push({ kind: "text", value: text.slice(i, k) });
    const end = text.indexOf(">", k);
    const inner = text.slice(k + 1, end);
    if (inner.startsWith("!")) tokens.push({ kind: "doctype", value: inner.slice(1) });
    else if (inner.startsWith("/")) tokens.push({ kind: "closing", name: inner.slice(1).toLowerCase() });
    else {
      const space = inner.search(/[\s/]/);
      tokens.push({ kind: "opening", name: (space === -1 ? inner : inner.slice(0, space)).toLowerCase() });
    }
    i = end + 1;
  }
  return tokens;
}

function buildTree(text) {
  const root = { kind: "document", name: "#document", children: [] };
  const stack = [root];
  const top = () => stack[stack.length - 1];
  for (const token of tokenize(text)) {
    if (token.kind === "text") {
      if (token.value.trim() === "") continue;
      top().children.push({ kind: "text", value: token.value.trim(), children: [] });
    } else if (token.kind === "opening") {
      while (stack.length > 1 && (CLOSES[token.name] ?? []).includes(top().name)) stack.pop();
      const node = { kind: "element", name: token.name, children: [] };
      top().children.push(node);
      if (!VOID_TAG.has(token.name)) stack.push(node);
    } else if (token.kind === "closing") {
      const position = stack.map((d) => d.name).lastIndexOf(token.name);
      if (position > 0) stack.length = position;
    }
  }
  return root;
}

function write(node, depth = 0) {
  const indent = "  ".repeat(depth);
  if (node.kind === "text") console.log(`${indent}"${node.value}"`);
  else console.log(`${indent}${node.name}`);
  for (const child of node.children) write(child, depth + 1);
}

function count(node, counter = { element: 0, text: 0, depth: 0 }, depth = 0) {
  if (node.kind === "element") counter.element += 1;
  if (node.kind === "text") counter.text += 1;
  counter.depth = Math.max(counter.depth, depth);
  for (const child of node.children) count(child, counter, depth + 1);
  return counter;
}

const source = `<!doctype html>
<html>
<head><title>North Slope Station</title></head>
<body>
<h1>North Slope Station</h1>
<p>At an elevation of 1840 meters above sea level.
<ul>
<li>Temperature
<li>Relative humidity
</ul>
</body>
</html>`;

const tree = buildTree(source);
write(tree);
console.log("---");
console.log(count(tree));
```

```
#document
  html
    head
      title
        "North Slope Station"
    body
      h1
        "North Slope Station"
      p
        "At an elevation of 1840 meters above sea level."
      ul
        li
          "Temperature"
        li
          "Relative humidity"
---
{ element: 9, text: 5, depth: 5 }
```

The source text has no closing tag for the `p` element, and neither of the two `li`
elements do. In the tree, though, all three are closed and stand in the correct place. What
achieves this is the `CLOSES` table: when `ul` is opened, an open `p` closes; when `li` is
opened, an open `li` closes. These rules are not an optional convenience, they are part of
the language's definition; every parser applies the same ones.

#### Node Types

Three kinds of node appear in the output above — document, element, and text. The fourth kind, the comment
node, does not appear here at all; the source text carries no comment.

The **document node** is the tree's root. It corresponds to no element; it is the handle for
the whole tree.

**Element nodes** are produced from opening tags. They have a name, attributes, and ordered
children.

**Text nodes** are produced from the characters between tags. They have no children; they are
the tree's leaves. The five text nodes in the count above correspond to the document's five
pieces of text.

**Comment nodes** (the `<!-- ... -->` form) stay in the document and are not painted. Scripts
can read them; information embedded in markup should not be assumed hidden.

In the count, the `element: 9` value corresponds to the nine opening tags in the source text.
`depth: 5` is the number of edges crossed from the root to the farthest leaf: document → `html`
→ `body` → `ul` → `li` → text node.

#### Elements Not Present in the Source Text

In the example above, the `html`, `head`, and `body` elements were written in the source
text. Had they not been written, they would still be in the tree. This is the most concrete
consequence of the tag–element distinction established in the previous lesson: **not every
element in the tree has a tag in the source text.**

There is a reverse consequence too: not every tag in the source text corresponds to an
element in the tree. A closing tag with no matching opening is ignored; in the algorithm
above, this is what doing nothing when the `lastIndexOf` call returns `-1` corresponds to.

This disconnect running in both directions explains why validating a document cannot be done
by looking at the source text. The question to ask is not "what did I write," it is "what got
built."

#### Void Elements

Some elements cannot have content, and for this reason have no closing tag: `br`, `hr`,
`img`, `input`, `link`, `meta`, and a few more. These are called **void elements**. In the
algorithm, these elements are never placed on the stack at all; they are added to the tree as
a child and the top of the stack does not change.

For void elements, a closing form written as `<br/>` is accepted by the parser but has no
effect; the element is already empty. For a non-void element, the same form — `<div/>`, for
example — does not do what might be expected: the element does not close, content continues
to flow into it.

#### The Tree Is a Living Structure

The tree does not freeze once it is built. Scripts can add nodes, remove them, change their
attributes; every change reruns the relevant part of the style and layout stages. This is why
a page's tree at a given moment and the source text that came from the server can differ: the
source text describes the initial state, the tree describes the state at that moment.

No script will be written in this course; but knowing that written markup is directly turned
into a data structure is the foundation for later courses. Writing markup is not typesetting
text, it is declaring a tree.

#### Summary

- The Document Object Model is the node tree built from the token stream; style, layout, and
  scripts apply not to the source text but to this tree.
- The tree builder uses a stack: an opened element goes onto the stack, a closed element comes
  off, a new node becomes the child of the element sitting at the top of the stack.
- Some elements with no written closing tag close on their own when another element opens;
  these rules are part of the language's definition.
- There are four node types: document, element, text, and comment. Comment nodes are not
  painted but stay in the document.
- The source text and the tree do not map onto each other one to one: elements that are not
  written can appear in the tree, and some written tags may never enter the tree at all.
- Void elements cannot have content and are never placed on the stack; a closing form has no
  effect on them and is misleading on a non-void element.

#### Next Step

This lesson showed how the tree is built from the document's text and assumed parsing
proceeds without interruption. In reality, a document refers to other resources beyond
itself: style files, scripts, images. These references appear while parsing is under way, and
some of them halt parsing. The next lesson examines, through an event timeline, which
resource holds up which stage, and how this changes depending on where it is written in the
document.

### Resource Loading Order

Source: https://academia.sh/en/courses/web-fundamentals-and-html/resource-loading-order

How script and style files hold up parsing and paint; comparing defer and async loading modes through an event timeline.

The previous lesson assumed the tree is built from the token stream without interruption.
Real documents break this assumption: while parsing is under way, the document refers to
resources beyond itself, and some of these references halt parsing.

The What the Browser Does lesson in the How the Internet Works course showed that these
subresource requests exist: when an address was typed, the server saw more than one request.
This lesson takes up **when** those requests are made and how they affect the document being
processed. This is not a performance detail, it is a constraint that decides how the document
gets written.

#### A Reference Is Born During Parsing

The browser reads the document from start to finish. The moment it sees a `link` element it
sends the style file's request, the moment it sees an `img` element it sends the image's
request. It does not wait for the end of the document. This has a direct consequence: a
resource's **place** in the document decides the **moment** it is requested. A style file
written at the end of the document is requested later than one written at the start.

Resources do not have the same effect on parsing. There are three behaviors.

| Resource | Halts parsing | Holds up paint |
|---|---|---|
| Image | No | No |
| Style file (`link rel="stylesheet"`) | No | Yes |
| Script (no attribute written) | Yes | Indirectly |
| Script (`defer`) | No | No |
| Script (`async`) | While executing | Indirectly |

#### Why a Script Halts Parsing

When a script element with no attribute written is seen, parsing halts, the script is
downloaded, executed, and only then does parsing continue. This behavior is not a shortcoming,
it is a necessity: the script can read the tree built up to that moment and add nodes to it.
If the parser ran at the same time, the tree the script sees and the tree the parser is
building would collide.

The price of this necessity is that the rest of the document is not processed while the
script downloads. There are two ways to avoid paying that price, and both give a promise
about when the script will reach the tree.

**Defer (`defer`)**: the script downloads without halting parsing, and executes once parsing
has finished. Multiple deferred scripts execute in the order they are written in the
document. This is the only safe mode for scripts that depend on one another.

**Async (`async`)**: the script downloads without halting parsing, and executes the moment it
finishes downloading; parsing halts while it executes. Execution order depends on download
order, not the document's order. It is for scripts that run on their own, with no dependency
on other scripts.

This distinction is not a preference, it is a contract: writing `async` means "my order does
not matter." If this attribute is written on a script whose order does matter, the bug
surfaces only under certain conditions and is hard to reproduce.

#### Why a Style File Holds Up Paint

A style file does not halt parsing — tree building continues. But paint cannot happen until
style computation is complete; if it did, the page would first appear unstyled, then jump to
its styled appearance. The browser waits instead.

There is a second, less well-known rule too: **a script does not execute while a style file
is pending.** The reasoning is the same kind. A script can ask for an element's computed style
value; if there is a rule that has not been applied yet, the answer it gets would be wrong.
For this reason a script is held back until the style file arrives, even if the script itself
has already finished downloading.

#### Event Timeline

The script below runs these rules as an event model. It is not a real browser, it is a model
of the rules: every piece of the document holds a unit of parsing time, every resource's fetch
time is given.

```js
// loading.mjs — event model of the parsing/loading rules
const document_ = [
  { kind: "markup", name: "head start", duration: 1 },
  { kind: "resource", name: "style.css", format: "style", fetch: 6 },
  { kind: "markup", name: "body first half", duration: 2 },
  { kind: "resource", name: "measurement.js", format: "script", mode: "blocking", fetch: 3, exec: 1 },
  { kind: "markup", name: "body second half", duration: 2 },
  { kind: "resource", name: "chart.js", format: "script", mode: "deferred", fetch: 5, exec: 1 },
  { kind: "markup", name: "body end", duration: 1 },
];

const log = [];
let clock = 0;
const deferred = [];
let styleReady = Infinity;

for (const part of document_) {
  if (part.kind === "markup") {
    log.push([clock, clock + part.duration, "parsing: " + part.name]);
    clock += part.duration;
    continue;
  }
  if (part.format === "style") {
    log.push([clock, clock, "request: " + part.name + " (in background)"]);
    styleReady = clock + part.fetch;
    continue;
  }
  if (part.mode === "blocking") {
    const fetchEnd = clock + part.fetch;
    const execStart = Math.max(fetchEnd, styleReady);
    log.push([clock, fetchEnd, "HALTED: fetching " + part.name]);
    if (execStart > fetchEnd) {
      log.push([fetchEnd, execStart, "HALTED: pending style"]);
    }
    log.push([execStart, execStart + part.exec, "exec: " + part.name]);
    clock = execStart + part.exec;
    continue;
  }
  if (part.mode === "deferred") {
    log.push([clock, clock, "request: " + part.name + " (in background)"]);
    deferred.push({ ...part, ready: clock + part.fetch });
  }
}

const parsingEnd = clock;
log.push([clock, clock, "parsing complete"]);
for (const script of deferred) {
  const start = Math.max(clock, script.ready);
  log.push([start, start + script.exec, "exec (deferred): " + script.name]);
  clock = start + script.exec;
}

for (const [start, end, name] of log) {
  console.log(String(start).padStart(3) + " - " + String(end).padStart(3) + "  " + name);
}
console.log("parsing end:", parsingEnd, "| all scripts done:", clock);
```

```
  0 -   1  parsing: head start
  1 -   1  request: style.css (in background)
  1 -   3  parsing: body first half
  3 -   6  HALTED: fetching measurement.js
  6 -   7  HALTED: pending style
  7 -   8  exec: measurement.js
  8 -  10  parsing: body second half
 10 -  10  request: chart.js (in background)
 10 -  11  parsing: body end
 11 -  11  parsing complete
 15 -  16  exec (deferred): chart.js
parsing end: 11 | all scripts done: 16
```

The timeline makes three things visible. First, `measurement.js` halted parsing for three
units (the 3–6 span) and, even after it finished downloading, waited one more unit; what it
was waiting for was `style.css` being ready. Second, because `chart.js` was deferred, it
never halted parsing at all, its request went out at unit 10, and it executed only after
parsing finished. Third, when parsing finishes (11) and when the scripts finish (16) are
separate moments; when the document becomes readable, the whole behavior layer may not yet be
ready.

The numbers are this model's assumptions; real durations depend on network conditions and
file sizes. What does not change is the ordering.

#### Placement Decisions

The rules lead to a few direct consequences.

A style file is written in the document's head section. If it is requested late, paint starts
late; if it is written in the middle of the body, content that has been painted up to that
point gets restyled afterward.

A script mode that does not halt parsing is preferred over a script with no attribute
written. Writing the script at the end of the document also saves most of parsing, but it
does not start the download as early as defer does: a script written at the end only has its
request go out once the parser reaches that point.

The browser does not sit idle while the parser is halted. A mechanism that scans ahead
through the rest of the document, only to look for resource references, sends the requests it
finds early. This behavior relies on resources being explicitly declared in the document: a
resource whose address a script computes and produces is not visible to this scan.

#### Summary

- Resource requests are born during parsing; a resource's place in the document decides the
  moment it is requested.
- A script with no attribute written halts parsing, because the script can read and change the
  tree built up to that moment.
- Defer executes the script after parsing finishes and in the document's order; async mode
  executes it the moment it finishes downloading and gives no ordering guarantee.
- A style file does not halt parsing but holds up paint; a script also does not execute while
  a style file is pending.
- When parsing finishes and when the scripts finish are separate moments; the document being
  readable does not mean the whole behavior layer is ready.

#### Next Step

This topic established how the document is processed: bytes turning into a tree, the tree
turning into boxes, resources' effect on this chain. The next topic begins writing the
document itself. The first question is every document's outermost structure: what the
document type declaration is for, what separates the head and body sections, and what the
parser builds when none of these is written at all.

## Document Structure

The tags that build an HTML document's skeleton, from document type and root structure through syntax rules, heading order, and tables.

### Document Type and Root Structure

Source: https://academia.sh/en/courses/web-fundamentals-and-html/document-type-and-root-structure

The doctype declaration's function, the root element's language declaration, the head/body split, and unwritten elements being built by the parser.

The previous topic established how the document is processed. This topic establishes how the
document is written, and starts with the outermost structure: the declarations that sit in
every HTML document's first lines and are, most of the time, copied without a second thought.

Each of these declarations answers a specific problem. Knowing which problem each one answers
also decides which are mandatory and which are conditional.

#### The Doctype Declaration

A document's first line is the form `<!doctype html>`. This is not an element; it is a
declaration given to the parser, and it has a single job: choosing the parsing mode.

The reason this mode exists is backward compatibility. Some of the web's early documents were written
against rules that only became standard later; processing those documents with the standard rules would
break their appearance. The solution was to define two modes: **standards mode** for documents that follow
the standard rules, **quirks mode** for older documents. A narrow third mode — limited-quirks mode — was
later slotted between them, but the decisive distinction is still between these two. The doctype
declaration is the switch that chooses which mode a document is processed in.

The declaration's absence is not a flaw, it is a different request: the parser processes the
document in quirks mode, and that mode's rules are not the rules expected today. The
differences sit mostly in the presentation layer — how box width is computed, for example —
and are not covered in this course. The rule is clear: the declaration is written.

The declaration itself carries no other information. It has no version number, no schema
address, no language identity; it only chooses the mode.

#### The Root Element and Language Declaration

A single root element comes after the doctype declaration: `html`. It is the only child after
the document node in the tree, and the whole document sits inside it.

There is one attribute that must be written on the root element: `lang`. It declares the
document's natural language, and its effect is not visual.

```html
<!doctype html>
<html lang="en">
```

The language declaration affects at least four decisions. Screen readers learn from it which
language's pronunciation rules to read the text with; a wrongly declared language makes text
unreadable. Spell checking and hyphenation-based line-breaking rules depend on the language.
Search indexers classify the document by language. Some typographic conventions — the form
quotation marks take, for example — vary by language.

If part of the document is in a different language, that part is redeclared on the element
carrying it; the attribute is inherited and the nearest declaration applies.

```html
<p>The station's Spanish name is
  <span lang="es">Estación Ladera Norte</span>.</p>
```

#### Head and Body

The root element has two children, and their order never changes: `head`, then `body`.

The **head section** (`head`) carries information about the document: its title, character
encoding declaration, links to external resources, metadata. This content is not painted. Its
only visible effect is the `title` element being used in the browser tab and in bookmarks.

The **body section** (`body`) carries the document's readable content. Everything that gets
painted is here.

Even though the split looks sharp, there is one element that sits on the boundary: `script`.
A script element can be found in either section, and where it is found decides the loading
behavior seen in the previous lesson.

#### Unwritten Elements Are Built

Even when the `html`, `head`, and `body` elements' tags are not written, the parser builds
them. Which element goes into the head section and which goes into the body is decided by a
defined rule: the moment an element that does not belong to the head section is seen, the head
section closes and the body begins.

```js
// root.mjs — placement of unwritten html/head/body elements
const HEAD_CONTENT = new Set(["title", "base", "link", "meta", "style", "script", "noscript"]);

function place(text) {
  const tokens = [...text.matchAll(/<\/?([a-z0-9!]+)[^>]*>/g)]
    .map((e) => ({ name: e[1], closing: e[0][1] === "/" }));
  const head = [];
  const body = [];
  let doctype = null;
  let mode = "head";
  for (const token of tokens) {
    if (token.name === "!doctype") { doctype = "html"; continue; }
    if (token.closing) continue;
    if (["html", "head", "body"].includes(token.name)) continue;
    if (mode === "head" && HEAD_CONTENT.has(token.name)) head.push(token.name);
    else { mode = "body"; body.push(token.name); }
  }
  return { doctype, head, body };
}

const documents = {
  "fully written": `<!doctype html><html lang="en"><head><meta charset="utf-8">
<title>North Slope</title></head><body><h1>North Slope</h1><p>1840 m</p></body></html>`,
  "root elements not written": `<!doctype html><meta charset="utf-8">
<title>North Slope</title><h1>North Slope</h1><p>1840 m</p>`,
  "no doctype": `<title>North Slope</title><h1>North Slope</h1>`,
};

for (const [name, doc] of Object.entries(documents)) {
  const { doctype, head, body } = place(doc);
  console.log("[" + name + "]");
  console.log("  doctype    :", doctype ?? "none -> quirks mode");
  console.log("  head       :", head.join(", "));
  console.log("  body       :", body.join(", "));
}
```

```
[fully written]
  doctype    : html
  head       : meta, title
  body       : h1, p
[root elements not written]
  doctype    : html
  head       : meta, title
  body       : h1, p
[no doctype]
  doctype    : none -> quirks mode
  head       : title
  body       : h1
```

The first two documents were written differently and produced the same structure. The third is
split by the same rule but will be processed in a different mode.

One consequence of this rule is that an element that does not belong to the head section
cannot accidentally end up written into it: even if written there, it is carried over to the
body. The reverse holds too — a `title` element written in the middle of the body does not
travel back to the head section, it stays in the body and does not do the job expected of it.
The parser's tolerance is not about silently fixing writing mistakes, it is about producing a
defined result.

Writing the tags anyway still has a justification: readability and where attributes go. The
`lang` attribute has to be written on the `html` element, a class or id on the `body` element;
for that, the tag has to be present in the source text.

#### Uniqueness and Order Constraints

Four elements are unique in the root structure: a document has only one `html`, one `head`,
one `body`, and one `title`. Writing a second one does not build a new element; when the
parser sees a second `body` tag, it does not open a new body, it merges it with the existing
one's attributes. A document cannot have two bodies, because the tree has a single root and
that root has a single visible branch.

Order within the head section's content is generally free; two situations are the exception.
The character-set declaration is written as early as possible — the reason for this is the
next lesson's subject. The `base` element, which changes the base for relative addresses, is
also written early, because it affects addresses that come **after** it, and it is found only
once in the document.

The `title` element is the head section's mandatory content. If it is not written, the
document keeps being processed, but it cannot be named outside its own context: the address
shows in the tab, the bookmark is saved without a name, the text introducing the document in a
search result is generated from other sources. The requirement is not a parsing condition, it
is a condition for the document to be recognizable in the outside world.

#### The Station Document's Skeleton

Here is the first version of the document that will be developed throughout the course:

```html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>North Slope Measurement Station</title>
</head>
<body>
  <h1>North Slope Measurement Station</h1>
  <p>An automated measurement station built at an elevation of 1840 meters above sea level.</p>
</body>
</html>
```

Five lines of declaration, two lines of content. This ratio will fall as content grows in the
document; the declaration section stays fixed regardless of the document's size.

The `title` element is in the head section, the `h1` element is in the body, and their text is
the same here. The two do separate jobs: `title` names the document outside its context — in
the tab, in a bookmark, in a search result —; `h1` is the topmost heading inside the document.
Because `title` needs to be understandable outside its context, it is often written longer in
most documents and also carries the site's name.

#### Summary

- The doctype declaration is not an element; it chooses the parsing mode and carries no other
  information. Without it, the document is processed in quirks mode.
- The root element is `html`, the document's single root child; the `lang` attribute affects
  pronunciation, hyphenation, indexing, and typography decisions, and is redeclared where part
  of the document is in a different language.
- The head section carries information about the document, the body carries the readable
  content; head-section content is not painted.
- The parser builds the `html`, `head`, and `body` elements even when their tags are not
  written; the split happens at the first element that does not belong to the head section.
- `title` names the document outside its context, `h1` is the topmost heading inside the
  document; their text can be the same, but their functions are separate.

#### Next Step

This lesson built the document's outermost shell and used tags without going into their
syntactic detail. The next lesson takes up that detail: how a tag is written, which forms of
writing attributes accept, how the `<` and `&` characters that appear in text are written, and
which error-recovery rules the parser applies to writing that breaks the rules.

### Tags, Attributes, and Entities

Source: https://academia.sh/en/courses/web-fundamentals-and-html/tags-attributes-and-entities

Tag and attribute writing rules, character reference resolution, the difference between text and attribute context, and recovery behavior in flawed markup.

The previous lesson built the document's outermost shell and used tags without going into
their syntactic detail. This lesson defines that detail: how a tag is written, which forms
attributes accept, how the `<` and `&` characters that appear in text are written, and what
the parser does with writing that breaks the rules.

These rules are not a list to memorize. Each one corresponds to a specific transition in the
parser's state machine, and knowing that transition explains the reason behind unexpected
results.

#### Writing a Tag

An opening tag starts with `<`, continues with the element name, continues with optional
attributes, and ends with `>`. A closing tag starts with `</` and carries only the name.

Element names are case-insensitive: `<P>` opens the same element as `<p>`. The parser
lowercases the name and builds it into the tree in lowercase. Not being case-sensitive does not
mean two forms of writing can be used; a single form is used within a document, and lowercase
is the established form.

Every non-void element has a closing tag. As seen in the previous lesson, not writing part of
it gives a defined result, but every unwritten closing tag makes it harder for the reader to
parse the document in their own head.

#### Attributes

An attribute is a name–value pair written inside an opening tag that describes the element.

```html
<img src="slope.jpg" alt="Measurement pole" width="960" height="640">
```

Attribute names are case-insensitive too and get lowercased. Their order carries no meaning;
if the same name is written twice on the same element, the first applies, the second is
ignored.

There are three forms of writing a value, and all three are valid:

```html
<td colspan="2">    <!-- double quote -->
<td colspan='2'>    <!-- single quote -->
<td colspan=2>      <!-- unquoted -->
```

The unquoted form cannot contain a space, a quote character, `=`, `<`, `>`, or a backtick.
This constraint produces a trap: in the form `class=measurement table`, `table` is counted
as a separate attribute, it does not become part of the value. For this reason values are
always quoted; tracking where the constraint begins is more expensive than just always
applying the rule.

**Boolean attributes** are a separate class. For these, what carries the meaning is not the
value, it is the attribute's **presence**.

```html
<input type="checkbox" checked>
<input type="checkbox" checked="">
<input type="checkbox" checked="checked">
```

All three forms mean the same thing: checked. Counter to intuition, `checked="false"` means
the same thing too — the attribute is written, so it is checked. The only way to turn it off
is to not write the attribute at all.

#### Character References

Inside text, the character `<` is taken as the start of a tag, `&` as the start of a
reference. The way to write these characters as ordinary text is a **character reference**.
It has three forms: named (`&lt;`), decimal numeric (`&#60;`), and hexadecimal numeric
(`&#x3C;`).

The script below applies the resolution rule. Named references are, in reality, looked up
from a table of close to a couple thousand names; a small subset is used here.

```js
// entities.mjs — resolving character references
const NAMED = { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'", reg: "®" };
const PATTERN = /&(#x[0-9a-fA-F]+|#[0-9]+|[a-zA-Z][a-zA-Z0-9]*)(;?)/g;

function resolve(text, isAttribute) {
  return text.replace(PATTERN, (full, body, semi, pos) => {
    if (body.startsWith("#")) {
      const hex = body[1] === "x";
      return String.fromCodePoint(parseInt(body.slice(hex ? 2 : 1), hex ? 16 : 10));
    }
    let name = null;
    for (let n = body.length; n > 0; n -= 1) {
      if (body.slice(0, n) in NAMED) { name = body.slice(0, n); break; }
    }
    if (name === null) return full;
    const exactMatch = name.length === body.length && semi === ";";
    const rest = body.slice(name.length) + semi;
    if (exactMatch) return NAMED[name];
    const next = rest[0] ?? text[pos + full.length] ?? "";
    if (isAttribute && /[=a-zA-Z0-9]/.test(next)) return full;
    return NAMED[name] + rest;
  });
}

const examples = [
  "5 &lt; 8 &amp;&amp; 8 &gt; 5",
  "risen &#8722;2&#xb0;C to 6&#xb0;C",
  "code: &lt;p&gt;",
  "&weather; not in table",
];
for (const example of examples) {
  console.log(JSON.stringify(example), "->", JSON.stringify(resolve(example, false)));
}

console.log("--- same string, two contexts ---");
const address = "/measure?type=humidity&regcount=3";
console.log("as text content    :", resolve(address, false));
console.log("as attribute value :", resolve(address, true));
console.log("escaped writing    :", resolve("/measure?type=humidity&amp;regcount=3", true));
```

```
"5 &lt; 8 &amp;&amp; 8 &gt; 5" -> "5 < 8 && 8 > 5"
"risen &#8722;2&#xb0;C to 6&#xb0;C" -> "risen −2°C to 6°C"
"code: &lt;p&gt;" -> "code: <p>"
"&weather; not in table" -> "&weather; not in table"
--- same string, two contexts ---
as text content    : /measure?type=humidity®count=3
as attribute value : /measure?type=humidity&regcount=3
escaped writing    : /measure?type=humidity&regcount=3
```

The first line resolves three named references. The second mixes the decimal and hexadecimal
forms of numeric references; both refer to the same set of code points — the code points
defined in the Character Encodings lesson of the How Computers Work course. The fourth line
shows that a name not in the table is not resolved and is left as is.

#### The Same String, Two Contexts

The second block of the output makes a rule visible that is missed often. The string
`/measure?type=humidity&regcount=3` breaks, producing the character `®`, in text content; it
does not break in an attribute value.

The reason for the difference is a separate rule defined for named references that do not end
in a semicolon: in an attribute value, if the reference is followed by `=` or a letter/digit,
no resolution is done. This rule exists to prevent addresses inside quotes from silently
breaking — but it only applies in attribute context.

The third line shows the correct way to write it. When an address is written into an attribute
value, the parameter separator is escaped in the form `&amp;`. Even though the attribute-context
rule makes this escaping unnecessary in most cases, escaping gives uniform behavior instead of
relying on the rule working in "most cases."

The general rule covers three characters: in text content, `<` and `&` are escaped; in an
attribute value, the quote character surrounding the value is escaped as well. This has a
security dimension too, and not doing this escaping when text coming from a user is placed
into the document is the source of the most common web vulnerabilities; the subject is detailed
in later courses.

#### Recovery Rules

The first lesson said "there is no such output as an invalid document." The script below makes
that claim concrete: it prints the open element stack at every token and shows what is done on
writing that breaks the rules.

```js
// recovery.mjs — the open element stack's trace in flawed markup
const VOID = new Set(["br", "img", "input", "meta", "link", "hr"]);
const CLOSES = { li: ["li"], dt: ["dt", "dd"], dd: ["dt", "dd"], p: ["p"], ul: ["p"], h1: ["p"] };

function trace(text) {
  const stack = [];
  console.log("source:", text);
  for (const m of text.matchAll(/<(\/?)([a-z0-9]+)[^>]*>/g)) {
    const closing = m[1] === "/";
    const name = m[2];
    let note = "";
    if (!closing) {
      const implicit = CLOSES[name] ?? [];
      while (stack.length > 0 && implicit.includes(stack[stack.length - 1])) {
        note = "(open " + stack.pop() + " closed on its own)";
      }
      if (VOID.has(name)) note = "(void element, not pushed to stack)";
      else stack.push(name);
    } else if (VOID.has(name)) {
      note = "(void element's closing tag is ignored)";
    } else {
      const position = stack.lastIndexOf(name);
      if (position === -1) note = "(no matching opening, ignored)";
      else {
        if (position !== stack.length - 1) note = "(" + stack.slice(position + 1).join(", ") + " above it also closes)";
        stack.length = position;
      }
    }
    console.log(("  " + m[0].padEnd(10) + "stack: [" + stack.join(" > ") + "] " + note).trimEnd());
  }
  if (stack.length > 0) console.log("  end of document  left open: [" + stack.join(" > ") + "] -> closed");
  console.log();
}

trace("<p>Measurement <b>stopped</b></p>");
trace("<p>Measurement <b><i>stopped</b></i>");
trace("<ul><li>first<li>second</ul>");
trace("<p>text</span></p>");
```

```
source: <p>Measurement <b>stopped</b></p>
  <p>       stack: [p]
  <b>       stack: [p > b]
  </b>      stack: [p]
  </p>      stack: []

source: <p>Measurement <b><i>stopped</b></i>
  <p>       stack: [p]
  <b>       stack: [p > b]
  <i>       stack: [p > b > i]
  </b>      stack: [p] (i above it also closes)
  </i>      stack: [p] (no matching opening, ignored)
  end of document  left open: [p] -> closed

source: <ul><li>first<li>second</ul>
  <ul>      stack: [ul]
  <li>      stack: [ul > li]
  <li>      stack: [ul > li] (open li closed on its own)
  </ul>     stack: [] (li above it also closes)

source: <p>text</span></p>
  <p>       stack: [p]
  </span>   stack: [p] (no matching opening, ignored)
  </p>      stack: []

```

Four rules become visible. A closing tag with no matching opening is **ignored**. When
elements are closed out of order, the elements above the one being closed close too — in the
second example, the `i` element closes when `b` closes, and the `</i>` that comes after falls
flat. Elements left open at the end of the document are closed. Some elements close on their
own when a sibling opens.

What these rules can together produce may not match the author's intent. In the second
example, the author wanted the `b` and `i` elements to span the same text, but in the end the
`i` element never spans the word `stopped` at all. The parser reports no error; the document
is built. This is the reason markup-validating tools are necessary: the flaw is not visible at
parse time, it is visible in the result differing from what was expected.

#### Comments

A comment is a section starting with `<!--` and ending with `-->`, and it produces a comment
node in the tree. It is not painted, but it stays in the document and is readable. Nothing
written inside it is hidden; server addresses, notes about removed features, and temporary
explanations are sent along with the document.

A comment's text cannot contain the sequences `<!--`, `-->`, or `--!>`, and cannot start with `>` or `->`.
A bare `--` is not forbidden, but it sits close enough to those limits that it is not the preferred
separator.

#### Summary

- Element and attribute names are case-insensitive and get lowercased; attribute order carries
  no meaning, and on a repeated name the first applies.
- Attribute values can be written unquoted too, but the unquoted form ends at a space; for this
  reason values are always quoted.
- For boolean attributes, what carries the meaning is not the value, it is the presence; the
  form `checked="false"` turns the attribute on too, and the only way to turn it off is to not
  write it.
- There are three forms of character reference; a named reference not ending in a semicolon
  gives a different result in text content than in an attribute value.
- The parser does not stop on flawed markup: an unmatched closing tag is ignored, closing out
  of order also closes the elements above it, and elements left open are closed at the end of
  the document.
- Comments are sent along with the document and are readable; they carry no hidden information.

#### Next Step

While resolving character references, this lesson assumed one thing: which rule bytes are
turned into characters by was already known. That rule, though, is declared in the document
itself, and is guessed at when it is not declared. The next lesson takes up the metadata tags
in the head section, and starts with the most important of them: how the character-set
declaration decides the way bytes are interpreted.

### Meta Tags

Source: https://academia.sh/en/courses/web-fundamentals-and-html/meta-tags

How the charset declaration decides byte interpretation, the priority of sources that determine encoding, the viewport declaration, and named metadata.

The previous lesson made an assumption while resolving character references: which rule bytes
were turned into characters by was already known. This lesson lifts that assumption and takes
up the metadata tags in the head section, starting with the most decisive one.

The `meta` element is a void element and carries no meaning on its own; what carries meaning
is its attributes. It is used in four separate functions, and these are independent of one
another.

#### The Charset Declaration

The Character Encodings lesson in the How Computers Work course established a single point: a
byte sequence does not say, on its own, which rule it was written with. The same bytes resolve
to different characters under different rules.

```js
// charset.mjs — same bytes, two separate encoding assumptions
// The measured text is kept in Turkish on purpose: the lesson measures the exact
// byte pattern (c4 b1) of the letter "ı", which has no equivalent in English.
const text = "Sıcaklık: -4 °C";
const bytes = Buffer.from(text, "utf8");

const codePoints = (d) =>
  [...d].map((k) => "U+" + k.codePointAt(0).toString(16).toUpperCase().padStart(4, "0")).join(" ");

console.log("bytes           :", bytes.toString("hex").match(/../g).join(" "));
console.log("byte count      :", bytes.length);
console.log("char count      :", text.length);
console.log("utf-8 reading   :", bytes.toString("utf8"));
console.log("latin1 reading  :", bytes.toString("latin1"));
console.log("utf-8 first 3   :", codePoints(bytes.toString("utf8").slice(0, 3)));
console.log("latin1 first 5  :", codePoints(bytes.toString("latin1").slice(0, 5)));

console.log("--- the charset declaration is searched for in the document's first bytes ---");
const head = '<!doctype html>\n<html lang="en">\n<head>\n<meta charset="utf-8">\n';
console.log("byte declaration ends at:", Buffer.byteLength(head));
console.log("under 1024 bytes        :", Buffer.byteLength(head) < 1024);
```

```
bytes           : 53 c4 b1 63 61 6b 6c c4 b1 6b 3a 20 2d 34 20 c2 b0 43
byte count      : 18
char count      : 15
utf-8 reading   : Sıcaklık: -4 °C
latin1 reading  : SÄ±caklÄ±k: -4 Â°C
utf-8 first 3   : U+0053 U+0131 U+0063
latin1 first 5  : U+0053 U+00C4 U+00B1 U+0063 U+0061
--- the charset declaration is searched for in the document's first bytes ---
byte declaration ends at: 63
under 1024 bytes        : true
```

The byte pair `c4 b1` resolves to a single character under one rule (U+0131, `ı`); under
another rule, to two separate characters (U+00C4 and U+00B1). The bytes are the same; only
the interpretation differs. Eighteen bytes give fifteen characters under the first reading,
eighteen under the second.

The document's own declaration is a single line:

```html
<meta charset="utf-8">
```

This line's effect is not visual, it is structural. If the declaration is wrong, it is not
just a few characters that break; the document resolves into an entirely different character
sequence, and even tag names can be affected.

#### Why the Declaration's Place Matters

There is a circular problem here. The declaration itself is inside the document; reading it
requires decoding the document, and decoding requires reading the declaration.

The solution is a pre-scan. The parser scans the document's first section — the defined limit
is 1024 bytes — with a single-byte assumption and looks only for a `charset` declaration. If
it finds one, it starts parsing the document with that rule.

This has two consequences. First, the declaration has to be within the first 1024 bytes; this
is why it is written at the very start of the head section. Second, if the declaration falls
outside this limit or is not found during the pre-scan with the default rule, the parser has
already started with the wrong rule; once it sees the declaration later, it has to redo
parsing **from the start.** This restart is a measurable cost, and it is avoided by the
declaration's placement.

#### Sources That Determine Encoding

The `meta` declaration in the document is not the only source. There is an ordered priority
list.

| Priority | Source |
|---|---|
| 1 | Byte order mark (BOM) |
| 2 | The user's explicit choice |
| 3 | Transport layer: the `charset` in the HTTP `Content-Type` header |
| 4 | The `meta charset` declaration in the document |
| 5 | Sniffing and default |

The byte order mark concept was defined in the Byte Order lesson of the How Computers Work
course; a few bytes at the start of the document state the encoding and override everything
else.

The third rank deserves attention: **the HTTP header overrides the document's declaration.**
The HTTP Request and Response lesson in the How the Internet Works course showed that the
`Content-Type` header carries a `charset` parameter. That parameter applies even if the
document carries a different declaration. If the server configuration and the document
conflict, the server wins; this is a frequent reason why characters that look broken are
searched for in the document and not found.

The fifth rank is not a guarantee. Sniffing makes a guess by looking at the document's content,
and the guess can be wrong. Not writing the declaration means leaving the result to the
environment.

#### The Viewport Declaration

The second use of `meta` declares how the document will scale on narrow screens.

```html
<meta name="viewport" content="width=device-width, initial-scale=1">
```

This declaration's reason is historical. Narrow-screen devices assume a wide virtual viewport
to keep documents not written for a narrow screen readable, and shrink the page. The
declaration turns off this assumption: the viewport's width is taken as the device's own
width, and the initial scale is one.

The declaration itself is not a presentation decision; it decides which width the presentation
layer will compute against. For this reason it is present in every document that accounts for
narrow screens, and it is the precondition for the responsive layout covered in the next
course.

Values that turn off scaling, like `user-scalable=no`, can be written. They should not be:
zooming in is the only access path for users with limited vision, and turning it off is an
accessibility violation.

#### Named Metadata

The third use declares information about the document with a `name`/`content` pair. This
information is not painted; it is carried for programs that process the document.

```html
<meta name="description" content="Temperature, relative humidity, wind speed,
      and precipitation records from the North Slope automated measurement station.">
<meta name="robots" content="index, follow">
<meta name="author" content="Measurement Network Working Group">
```

The `description` value can be used as the document's summary shown in search results. It is
not guaranteed to be used — indexers can choose to produce a more suitable summary from the
document's content. Writing it is still justified: when it is not written, the choice is left
entirely to the indexer.

The `robots` value tells indexers whether the document should be indexed and whether its links
should be followed. This is not an access control; a client that does not follow the
declaration still reads the document. Content requiring privacy is protected with
authorization, not with metadata.

There are separate metadata contracts for systems that preview the document in other
environments; these use the same `meta` mechanism and declare fields like title, description,
and image address. Contract names vary from platform to platform; what is common is that the
preview is explicitly declared instead of being guessed from the document's content.

#### Equivalent Headers

The fourth use declares the equivalent of an HTTP response header with the `http-equiv`
attribute.

```html
<meta http-equiv="content-security-policy" content="default-src 'self'">
```

This mechanism is for situations where there is no way to change the server's headers. If
there is a way, the real header is preferred: the header applies before the document is
parsed, the `http-equiv` declaration only once the parser reaches it. This difference is
decisive for security declarations.

#### The Station Document's Head Section

The head section of the document developed throughout the course becomes this:

```html
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>North Slope Measurement Station — Measurement Network</title>
  <meta name="description" content="Temperature, relative humidity, wind speed,
        and precipitation records from the North Slope automated measurement station.">
</head>
```

The order is not incidental: the encoding declaration comes first, because it decides how
everything after it will be read. The `title` element, as noted in the previous lesson, names
the document outside its context, and here it also carries the site's name.

#### Summary

- The charset declaration says which rule bytes will be converted to characters by; a wrong
  declaration resolves the whole document into a different character sequence.
- Because of the parser's pre-scan, the declaration has to be within the first 1024 bytes;
  being found late means parsing has to be redone from the start.
- Sources that determine encoding have a priority, and the HTTP header overrides the
  document's declaration.
- The viewport declaration decides which width the presentation layer computes against; values
  that turn off scaling break accessibility.
- Named metadata is for programs that process the document and is not binding; indexing
  declarations provide no access control.
- `http-equiv` declarations stand in for real HTTP headers but apply later; the header is
  preferred where possible.

#### Next Step

The head section is complete; next comes the body. The body's first structural decision is
headings: the tags that declare which sections the document is divided into and these
sections' level relative to one another. The next lesson shows why heading level is a
structural declaration rather than a font size, and how skipping a level breaks the
document's outline.

### Heading Hierarchy

Source: https://academia.sh/en/courses/web-fundamentals-and-html/heading-hierarchy

Heading level's function as a structural declaration, extracting the document's outline, the consequence of skipping a level, and headings' use as a navigation tool.

The head section is complete; writing the body begins. The body's first structural decision is
headings: the tags that declare which sections the document is divided into and these
sections' level relative to one another.

There are six heading elements: `h1` through `h6`. The number gives the heading's **level** —
the first level is the topmost. This number is not a font size; it declares nesting depth.

#### A Heading Is a Structural Declaration

A heading element does two jobs. It names the content that follows it, and it declares that
content's depth in the document. The second job is carried by the level number.

It is enough to think of a book's table of contents: chapter titles, section titles, and
subsection titles are written at different indentation levels, and the indentation shows which
subsection belongs to which chapter. In HTML, the level number is indentation's counterpart.

This information is used. Screen readers offer navigation from heading to heading; instead of
listening to the document from the start, the user scans the outline and jumps to the section
they are interested in. Content-extraction tools divide the document by its headings.
Indexers give heading text a different weight than body text.

#### Extracting the Outline

Heading levels define a tree, and this tree can be computed from the document.

```js
// heading.mjs — extracts the outline from a heading-level sequence
const document_ = `
<h1>North Slope Station</h1>
<h2>Location</h2>
<h2>Measured Quantities</h2>
<h3>Temperature</h3>
<h3>Relative Humidity</h3>
<h2>Data Access</h2>
<h4>File Format</h4>
`;

const headings = [...document_.matchAll(/<h([1-6])>([^<]*)<\/h\1>/g)].map((e) => ({
  level: Number(e[1]),
  text: e[2],
}));

let previous = 0;
const warnings = [];
const number = [];
for (const heading of headings) {
  if (previous !== 0 && heading.level > previous + 1) {
    warnings.push(`h${previous} then h${heading.level}: h${previous + 1} skipped (${heading.text})`);
  }
  number.length = heading.level;
  number[heading.level - 1] = (number[heading.level - 1] ?? 0) + 1;
  const label = [...number].map((s) => s ?? 0).join(".");
  console.log("  ".repeat(heading.level - 1) + label + "  " + heading.text);
  previous = heading.level;
}

console.log("--- audit ---");
console.log("heading count :", headings.length);
console.log("h1 count      :", headings.filter((b) => b.level === 1).length);
for (const warning of warnings) console.log("warning       :", warning);
```

```
1  North Slope Station
  1.1  Location
  1.2  Measured Quantities
    1.2.1  Temperature
    1.2.2  Relative Humidity
  1.3  Data Access
      1.3.0.1  File Format
--- audit ---
heading count : 7
h1 count      : 1
warning       : h2 then h4: h3 skipped (File Format)
```

The last heading makes the flaw visible. While `Data Access` is at the second level, `File
Format` was written at the fourth level; the third level was skipped. In the numbering, this
shows up as an empty digit, in the form `1.3.0.1`: there is a level in the outline with no
counterpart.

#### The Consequence of Skipping a Level

A skipped level leaves a gap in the outline. A user navigating the document from heading to
heading expects a subsection that does not exist between `Data Access` and `File Format`, and
does not find it. The result is not a visual flaw, it is a wrong structural declaration.

The reason for the skip is usually presentational: the author found a fourth-level heading's
default appearance more suitable. This is the layer violation defined in the first lesson. The
correct fix is to choose the level by structure and correct the appearance in the presentation
layer.

There is no rule in the reverse direction: skipping while the level **decreases** is not a
flaw. Returning to the second level after a third-level section declares that the subsection
has ended and a new higher-level section has begun; this is an ordinary closing.

#### How Many First-Level Headings in a Document

There are two defensible approaches on this question, and both are in use.

**The single-`h1` approach**: the document has only one first-level heading, and it names the
document's subject. Everything else is beneath it. This approach makes the outline a
single-rooted tree and makes it easy to summarize the document outside its context.

**The per-section-`h1` approach**: every sectioning element restarts its own heading level,
and level is computed from nesting. The outline computation this approach relies on has not
been consistently supported across screen-reader implementations.

This course follows the first approach: a single `h1` in the document, with headings beneath
it that follow numeric order. The justification is not theoretical superiority, it is that
implementations interpret this arrangement consistently. A structural declaration's value
depends on the tools reading it drawing the same conclusion from it.

#### A Heading's Content

A heading element carries text and allows inline markup: a heading can contain an
abbreviation, a code fragment, or emphasis. What it should not contain is content that belongs
to a separate section — a heading names a section, it is not the section's content itself.

Heading text should also be understandable outside its context. A user navigating from heading
to heading hears only the headings, without seeing the body text. Headings like "Details,"
"More," or "Section 3" carry no information in this use.

If a document has text that is not written with a heading element but visually stands like a
heading, that section is not in the outline. The reverse holds too: text placed in a heading
element only to look large adds a section to the outline that does not exist.

#### The Subheading Problem

Writing a short introductory line beneath a heading, one that complements it, is a common
requirement. If this line is placed in a second-level heading element, a section that does not
exist is added to the outline: while navigating, the user expects a subsection, but there is
only an introductory sentence.

```html
<h1>North Slope Measurement Station</h1>
<h2>At 1840 meters, four quantities</h2>   <!-- adds a fake section to the outline -->
```

The correct way to write it leaves the introductory line outside the heading element and keeps
the two together:

```html
<header>
  <h1>North Slope Measurement Station</h1>
  <p>At 1840 meters, four quantities</p>
</header>
```

Here the outline has only one entry; the introductory line is a paragraph, and its visual
relationship with the heading is established in the presentation layer. The `header` element
used here is the subject of the next topic; its job here is to group the two elements as a
single introductory block.

The same reasoning applies to opening a second heading element just to show part of the
heading text smaller. If an entry is meant to be added to the outline, a heading element is
used; if not, it is not used. Appearance is not the criterion for this decision.

#### The Station Document's Outline

The body of the document developed throughout the course is divided into sections with
headings:

```html
<body>
  <h1>North Slope Measurement Station</h1>
  <p>An automated measurement station built at an elevation of 1840 meters above sea level.</p>

  <h2>Location and Setup</h2>
  <p>The station sits on the middle stretch of a north-facing slope.</p>

  <h2>Measured Quantities</h2>
  <h3>Temperature</h3>
  <p>The measurement interval is ten minutes.</p>
  <h3>Relative Humidity</h3>
  <p>The measurement interval is ten minutes.</p>

  <h2>Data Access</h2>
  <p>Records are published as daily files.</p>
</body>
```

This skeleton is the structure the rest of the document will be built on. In the following
lessons, each section's content will be filled in; the heading arrangement will not change.

#### Summary

- Heading level is not a font size, it is structural data that declares depth in the document.
- Headings define an outline; this outline is used by navigation, content-extraction, and
  indexing tools.
- Skipping while level increases leaves a gap in the outline with no counterpart; skipping
  while level decreases is not a flaw.
- The approach of keeping a single first-level heading in the document is followed in this
  course because of implementations' consistent interpretation.
- Heading text should be understandable outside its context too; a user navigating from
  heading to heading does not see the body.

#### Next Step

Headings named the sections; paragraphs will fill their content. Paragraph text itself is
structureless too: why a piece is emphasized, what an abbreviation stands for, where a
quotation comes from is lost unless declared. The next lesson takes up inline markup, and
along the way shows a transformation the parser applies to text: what happens to whitespace
and line breaks in the source text.

### Text Tags

Source: https://academia.sh/en/courses/web-fundamentals-and-html/text-tags

Marking emphasis, quotation, abbreviation, definition, and code; whitespace collapsing and line-break behavior's dependence on the source text.

Headings named the sections; this lesson marks up the text that fills them. Paragraph text
looks structureless at first glance, but there is information inside it waiting to be
declared: why a word stands out, what an abbreviation stands for, where a quotation comes
from.

This lesson also shows a transformation applied to text: whitespace and line breaks in the source text do
not reach the screen as they are.

#### Paragraph

The `p` element declares a paragraph. It cannot contain another paragraph, a list, or a
heading; as seen in the Document Object Model lesson, an open paragraph closes on its own when
such an element is opened.

The space between paragraphs is the presentation layer's decision. Writing empty paragraphs to
separate text adds meaningless nodes to the outline and can be announced as an empty paragraph
by a screen reader.

#### Emphasis and Importance

Four elements are often confused because their default appearances are the same, two by two.

| Element | What it declares |
|---|---|
| `em` | The sentence's emphasis is on this word |
| `strong` | The content is important, serious, or urgent |
| `i` | The text carries a quality set apart from the flow (a foreign word, a term, a thought) |
| `b` | The text should draw attention, without adding meaning |

The difference between `em` and `i` shows up in how it is read aloud. `em` is emphasis that
changes the sentence's meaning:

```html
<p><em>This</em> station is at 1840 meters.</p>
<p>This station is at <em>1840</em> meters.</p>
```

The two sentences carry the same words and say different things; the first foregrounds which
station is being talked about, the second foregrounds the elevation's value. Screen readers can
voice this distinction.

`strong`, by contrast, declares importance, not emphasis; it does not change how something is
read aloud, it declares the content's weight. Used nested, the degree of importance increases.

The `i` and `b` elements carry no meaning. Marking a Latin species name with `i` or a product
code with `b` is valid; these elements are not "write in italics" and "write in bold"
commands, they are "there is something different here" declarations. If only appearance is
wanted, the right place is the presentation layer.

#### Quotations

There are two elements, and the distinction is not length, it is whether the quote sits within
the flow.

`q` is a quotation appearing inside a sentence. `blockquote` is a quotation block that stands
on its own. Both can carry the source's address with the `cite` attribute.

```html
<p>The installation report states that <q cite="/report/2019">the slope's north face
   was selected</q>.</p>

<blockquote cite="/report/2019">
  <p>The slope's north face, receiving no direct light throughout the day, gives less
     deviation in temperature measurement.</p>
  <footer>Installation Report, <cite>Measurement Network Technical Notes</cite></footer>
</blockquote>
```

The `cite` **attribute** carries the source's address and is invisible in most browsers. The
`cite` **element**, by contrast, marks a work's name — a book, a report, an article. The two
are separate things, and the `cite` element is not used to mark a person's name; it marks a
work's name.

#### Abbreviation and Definition

`abbr` marks an abbreviation and carries its expansion with the `title` attribute. `dfn` marks
the place a term **is defined** — not every place the term appears.

```html
<p><dfn><abbr title="elevation above sea level">EASL</abbr></dfn> is a point's vertical
   distance relative to mean sea level. The North Slope station's <abbr title="elevation
   above sea level">EASL</abbr> value is 1840 meters.</p>
```

The `title` attribute's content is accessible only when the mouse pointer is hovered over it;
access by keyboard or touchscreen is not reliable. For this reason the expansion is also
written at least once in the document as open text — the definition sentence above does this
job.

#### Code, Input, and Variable

Four elements are for technical text and are shown by default in a monospace font: `code`
marks a code fragment, `kbd` the keys the user presses, `samp` a program's output, `var` a
variable's name.

```html
<p>The <code>download.sh</code> script writes the message <samp>interrupted: partial
   file deleted</samp> when stopped with <kbd>Ctrl</kbd> + <kbd>C</kbd>.</p>
```

The value of this distinction is in search and conversion: extracting every command example in
a document is done with a single query if `code` elements are marked.

#### Whitespace Is Collapsed

Whitespace sequences in the source text **do** carry over to the document as they are; the collapsing
happens not at parsing but at paint time, under the presentation layer's default `white-space` rule.
Space, tab, and line-break sequences collapse to a single space; whitespace at the start and end of a
section is dropped.

```js
// whitespace.mjs — model of the whitespace-collapsing rule
function collapse(text) {
  return text.replace(/[ \t\n\r]+/g, " ").replace(/^ | $/g, "");
}

const source = "Measurement       interval\n        is ten minutes.\n\n    Recording time is UTC.";
console.log("source (chars)   :", source.length);
console.log(JSON.stringify(source));
console.log("--- after collapsing ---");
const result = collapse(source);
console.log("result (chars)   :", result.length);
console.log(JSON.stringify(result));

console.log("--- non-breaking space does not collapse ---");
const regularSpaces = "18" + "   " + "degrees";                  // three regular spaces
const nonBreaking = "18" + "   " + "degrees"; // three times &nbsp;
console.log("regular :", collapse(regularSpaces).length, "chars");
console.log("nonbreak:", collapse(nonBreaking).length, "chars");

console.log("--- tag spacing across consecutive lines ---");
const textContent = (d) => collapse(d.replace(/<[^>]+>/g, ""));
console.log("line break :", JSON.stringify(textContent("<em>south</em>\n<em>north</em>")));
console.log("adjacent   :", JSON.stringify(textContent("<em>south</em><em>north</em>")));
```

```
source (chars)   : 78
"Measurement       interval\n        is ten minutes.\n\n    Recording time is UTC."
--- after collapsing ---
result (chars)   : 59
"Measurement interval is ten minutes. Recording time is UTC."
--- non-breaking space does not collapse ---
regular : 10 chars
nonbreak: 12 chars
--- tag spacing across consecutive lines ---
line break : "south north"
adjacent   : "southnorth"
```

The rule has three consequences. First, the source text can be freely indented and split
across lines for readability; the text that carries over to the document does not change.
Second, the **non-breaking space** written with the `&nbsp;` character reference does not
undergo this collapsing; it is used when two words must not be allowed to separate. Using it in
place of an ordinary space causes the text not to be found in search results. Third, as the
last two lines show, writing two elements on separate lines puts a space between them; writing
them side by side does not. The source text's formatting can change the visible result.

#### Line Break and Preformatted Text

The collapsing rule has two exceptions.

The `br` void element forces a line break. It is used only where the line break is part of the
content — an address, a poem, discrete short lines. It is not used to separate paragraphs.

Inside the `pre` element, whitespace and line breaks in the text are preserved. Code listings
and aligned output are placed in this element; in technical documents, `pre` is used together
with `code`.

```html
<pre><code>time,temperature,humidity
07:00,-4.2,72
07:10,-4.1,71</code></pre>
```

The line break right after the opening tag is discarded by the parser; the one before the
closing tag is not. This is why the closing tag is written flush against the last line in the
example above.

#### Summary

- `em` declares the sentence's emphasis, `strong` the content's importance; `i` and `b` show
  the text carries a separate quality, without adding meaning.
- `q` is a quotation within the flow, `blockquote` a quotation standing on its own; the `cite`
  attribute carries the source's address, the `cite` element a work's name.
- `abbr` carries an abbreviation's expansion with `title`; because access to this content is
  not reliable, the expansion is also written at least once in the document as open text.
- Whitespace sequences collapse to a single space and whitespace at the edges is dropped; the
  non-breaking space does not undergo this collapsing.
- Writing two elements on separate lines puts a space between them; the source text's
  formatting can affect the visible result.
- `br` is used only where the line break is part of the content; whitespace and line breaks
  are preserved inside `pre`.

#### Next Step

Inline markup structured the inside of the paragraph. Some content, though, is not a
paragraph: ordered steps, items of equal standing, term–description pairs. Writing these as
paragraphs destroys structural information. The next lesson takes up list elements and shows
ordered lists' numbering rule, along with the attributes that decide where the numbering
starts.

### Lists

Source: https://academia.sh/en/courses/web-fundamentals-and-html/lists

The distinction among unordered, ordered, and definition lists; the ordered-list numbering rule, nested writing, and the structural justification for choosing a list.

Inline markup structured the inside of the paragraph. Some content, though, is not a
paragraph: ordered steps, items of equal standing, term–description pairs. Writing these as
paragraphs destroys structural information — the reader cannot tell how many items there are,
whether their order is meaningful, or where they end.

There are three list kinds, and the choice is made according to the content's nature.

#### Three Kinds

**Unordered list** (`ul`) is a list whose items' order carries no meaning. If items swap
places, the document's meaning does not change.

**Ordered list** (`ol`) is a list where order carries meaning: steps, ranking, priority.
Swapping places breaks the meaning.

**Definition list** (`dl`) is made of name–value pairs: a term and its description, a field
and its content, a question and its answer.

The choice criterion is not appearance. Removing an unordered list's markers or hiding an
ordered list's numbers is the presentation layer's job; which element is chosen is decided by
whether order is meaningful.

#### The Item Element and Its Container

The `li` element is a list item and can only be the direct child of a list element. An `li`
element written directly into the body is invalid.

The `li` element's closing tag may go unwritten; as seen in the Document Object Model lesson,
the previous one closes on its own when a new `li` is opened. Writing it is still preferred.

Writing text as a direct child of a list element is invalid: the children of `ul` and `ol`
elements can only be `li`. Whitespace and line breaks between them are ignored, but real text
is moved by the parser to outside the list.

```html
<ul>
  <li>Temperature</li>
  <li>Relative humidity</li>
  <li>Wind speed</li>
  <li>Precipitation</li>
</ul>
```

#### The Numbering Rule

An ordered list's numbers start from one and increase by one by default. Three attributes
change this behavior: `start` decides the starting value, `reversed` a decreasing order,
`value` a single item's value. Once `value` is written, the following items continue from
there.

```js
// lists.mjs — ordered list numbering rule
function number(list) {
  const n = list.items.length;
  const start = list.start ?? (list.reversed ? n : 1);
  const result = [];
  let counter = start;
  for (const item of list.items) {
    const value = item.value ?? counter;
    result.push([value, item.text]);
    counter = value + (list.reversed ? -1 : 1);
  }
  return result;
}

const lists = [
  { name: "default", items: [{ text: "housing opens" }, { text: "sensor is read" }, { text: "record is written" }] },
  { name: "start=5", start: 5, items: [{ text: "housing opens" }, { text: "sensor is read" }] },
  { name: "reversed", reversed: true, items: [{ text: "housing opens" }, { text: "sensor is read" }, { text: "record is written" }] },
  { name: "skip with value", items: [{ text: "housing opens" }, { text: "sensor is read", value: 7 }, { text: "record is written" }] },
];

for (const list of lists) {
  console.log("[" + list.name + "]");
  for (const [num, text] of number(list)) console.log("  " + num + ". " + text);
}
```

```
[default]
  1. housing opens
  2. sensor is read
  3. record is written
[start=5]
  5. housing opens
  6. sensor is read
[reversed]
  3. housing opens
  2. sensor is read
  1. record is written
[skip with value]
  1. housing opens
  7. sensor is read
  8. record is written
```

In the `reversed` case, note that the starting value comes from the item count: if the list
has three items, it starts from three. If `start` is written, that value applies instead.

These values are not just appearance; they are data the document carries. Writing
`start="8"` on a procedure's second page is different from writing the numbers by hand in the
presentation layer: it is declared in the document that this is the eighth step.

The numbers' form — digit, letter, roman numeral — is usually a presentation decision and is decided in
the presentation layer.

#### Definition Lists

The `dl` element carries name–value pairs. `dt` marks the name, `dd` the value. The
relationship between the two comes from adjacency: `dd` elements following a `dt` element
belong to it.

```html
<dl>
  <dt>Installation elevation</dt>
  <dd>1840 m</dd>

  <dt>Measurement interval</dt>
  <dd>10 minutes</dd>

  <dt>Temperature</dt>
  <dt>Relative humidity</dt>
  <dd>Measured in the same sensor housing.</dd>
</dl>
```

The last group shows that more than one name can share a single description. The reverse
holds too: a name can be given more than one `dd` element.

If a pair needs to be grouped as a single unit, the `dt` and `dd` elements can be wrapped in a
`div`; this is the only extra element allowed as a direct child of `dl`.

A definition list is used beyond what its name suggests: an identification block, a metadata
table, a question–answer set are also name–value structures. The criterion is not being a
dictionary, it is that every item has a name and a value bound to it.

#### Nested Lists

A sublist is written **inside** the parent list's item — not between items.

```html
<ul>
  <li>Temperature
    <ul>
      <li>Housing temperature</li>
      <li>Soil temperature</li>
    </ul>
  </li>
  <li>Relative humidity</li>
</ul>
```

Writing the sublist between `li` elements, as a direct child of the `ul` element, breaks the
structure: the sublist does not declare which item it belongs to. The flaw is easy to miss
because the appearance stays similar in most cases; on a screen reader, though, the nesting is
announced incorrectly.

Writing closing tags is especially important in nested writing. If the outer item's closing
is not written, the implicit-closing rule fires not at the sublist's end but at the next `li`
opening; the result is usually what was expected, but the source text becomes misleading.

#### The Information a List Announces

A list element's value is not in its markers, it is in the count it carries. Screen readers
announce the item count and which item number is currently on when entering a list. Writing
the same content as consecutive paragraphs destroys this information: the user only learns how
many items there are once they reach the end.

This criterion also decides when a list should not be used. A single-item list carries no
count information and is unnecessary if it was only written for indentation. A structure where
every line is its own list is misleading the same way: the count drops to one every time.

Two-dimensional data is not a list either. If every item has more than one field — like each
measurement's time, value, and unit — the structure is a table and is written with the table
elements covered in this topic's last lesson. The criterion that turns a list into a table is
whether items are split into comparable fields.

#### In the Station Document

The measurement station page has two lists, and their kinds differ:

```html
<h2>Measured Quantities</h2>
<ul>
  <li>Temperature</li>
  <li>Relative humidity</li>
  <li>Wind speed</li>
  <li>Precipitation</li>
</ul>

<h2>Calibration Procedure</h2>
<ol>
  <li>The housing cover is opened.</li>
  <li>A reading is taken without disconnecting the sensor.</li>
  <li>It is compared against the reference value.</li>
  <li>The deviation is recorded and the housing is closed.</li>
</ol>
```

The order of the measured quantities carries no meaning; the calibration steps' order does.
The distinction is given not by the two elements' appearance, but by the nature of the
content.

#### Summary

- An unordered list is where order is meaningless, an ordered list where it is meaningful; a
  definition list carries name–value pairs.
- The `li` element can only be the direct child of a list element; text cannot be written as
  the direct child of a list element.
- In an ordered list, `start`, `reversed`, and `value` decide numbering; once `value` is
  written, the following items continue from there.
- In a definition list, more than one value can be bound to a name, more than one name to a
  value; the relationship comes from adjacency.
- A sublist is written inside the item it belongs to; writing it between items breaks the
  nesting information.

#### Next Step

Lists grouped content, but the document is still closed in on itself: there is no way to move
to another document. The next lesson takes up links and shows how a relative address is
resolved — against which base, with which rule — through real resolution output.

### Links

Source: https://academia.sh/en/courses/web-fundamentals-and-html/links

Resolving relative and absolute addresses, determining the base address, the information link text carries, and link relationship declarations.

Lists grouped content, but the document is still closed in on itself. A link is the element
that connects a document to another resource, and it is what turns the web from a pile of
documents into a network.

The link element `a` declares its target with the `href` attribute. This lesson takes up how
that attribute's value is interpreted, what the text needs to carry, and the attributes that
declare the link's relationship to its target.

#### Resolving the Address

The What the Browser Does lesson in the How the Internet Works course separated an address's
parts — scheme, host, port, path, query, fragment. The address written in a document most of
the time does not carry all of these parts; missing parts are completed from the **base
address**.

The base address is, by default, the document's own address. The resolution rule is defined
and gives the same result on every client.

```js
// links.mjs — resolving relative addresses against the base address
const base = "https://example.test/station/north-slope/measurements.html";

const candidates = [
  "log.html",
  "./log.html",
  "../summary.html",
  "../../index.html",
  "/about.html",
  "//other.test/data.json",
  "https://other.test/data.json",
  "?type=humidity",
  "#temperature",
  "data/2024.csv",
];

console.log("base:", base);
for (const candidate of candidates) {
  console.log(candidate.padEnd(28), "->", new URL(candidate, base).href);
}

console.log("--- the base element changes the base ---");
const newBase = new URL("/archive/", base).href;
console.log("new base:", newBase);
console.log("log.html".padEnd(28), "->", new URL("log.html", newBase).href);
```

```
base: https://example.test/station/north-slope/measurements.html
log.html                     -> https://example.test/station/north-slope/log.html
./log.html                   -> https://example.test/station/north-slope/log.html
../summary.html              -> https://example.test/station/summary.html
../../index.html             -> https://example.test/index.html
/about.html                  -> https://example.test/about.html
//other.test/data.json       -> https://other.test/data.json
https://other.test/data.json -> https://other.test/data.json
?type=humidity               -> https://example.test/station/north-slope/measurements.html?type=humidity
#temperature                 -> https://example.test/station/north-slope/measurements.html#temperature
data/2024.csv                -> https://example.test/station/north-slope/data/2024.csv
--- the base element changes the base ---
new base: https://example.test/archive/
log.html                     -> https://example.test/archive/log.html
```

The output distinguishes five separate forms.

**Same-directory relative**: `log.html` and `./log.html` give the same result; the directory
the document sits in is the base. Note that the document's own name is dropped in resolution —
`measurements.html` is not part of the result.

**Parent-directory relative**: `../` goes up one directory on every repetition.

**Root-relative**: an address starting with `/` writes the path from the root; the host and
scheme come from the base. When the document moves within the directory tree, these addresses
do not change; relative ones do.

**Scheme-relative**: an address starting with `//` takes only the scheme from the base. Tying
the scheme to the document is not preferred, because it allows moving from an insecure context
to an insecure target; the scheme is written explicitly.

**Same-document relative**: addresses that carry only a query or a fragment keep the
document's own path. Fragment addresses in the form `#temperature` never go out onto the
network at all; the target is the element in the document carrying that identifier.

#### Changing the Base Address

The `base` element, written in the head section, changes the base for every relative address
in the document.

```html
<head>
  <base href="/archive/">
</head>
```

The output's last section shows the effect: the same `log.html` value resolves to a different
address. This element is found at most once in a document and affects addresses written after
it; that is the reason it is written early.

Because its effect spreads across the whole document, this element is used carefully. It is
not a decision affecting one section, it is one affecting every relative address — including
fragment addresses in the document.

#### Characters in the Address

An address is written with a limited character set. Characters outside this set — Turkish
letters, spaces — are converted to bytes with **percent-encoding**.

```js
// encoding.mjs — non-ASCII characters and spaces in an address
const base = "https://example.test/station/";
for (const candidate of ["ölçüm raporu.pdf", "veri/2024 mart.csv", "arşiv/kuzey-yamaç"]) {
  console.log(JSON.stringify(candidate), "->", new URL(candidate, base).pathname);
}
console.log("--- the host name is encoded with a separate rule ---");
console.log(new URL("https://ölçüm.test/veri").href);
```

```
"ölçüm raporu.pdf" -> /station/%C3%B6l%C3%A7%C3%BCm%20raporu.pdf
"veri/2024 mart.csv" -> /station/veri/2024%20mart.csv
"arşiv/kuzey-yamaç" -> /station/ar%C5%9Fiv/kuzey-yama%C3%A7
--- the host name is encoded with a separate rule ---
https://xn--lm-4ia2d6a.test/veri
```

Every `%XX` pair is one byte, and the bytes are produced with the rule defined in the
Character Encodings lesson; because the letter `ö` takes two bytes, it converts to two pairs.
A space becomes `%20`.

The last line shows that the host name is handled with a separate rule: the path portion is
written with percent-encoding, the host name with the ASCII representation stated in the What
Is a Domain Name lesson. The same address's two parts go through two separate encodings.

Writing file names with ASCII characters makes this conversion unnecessary and keeps the
address readable; it is not mandatory, but it keeps the address short in the source text.

#### Link Text

A link's text should declare what the target is. The criterion for this is not the sentence
around it: screen readers present the links inside a document as a list, and the user scans
that list by looking at their text.

```html
<!-- carries no information once taken out of context -->
<p>For daily measurement files, <a href="/data/log">click here</a>.</p>

<!-- understandable on its own -->
<p>Measurements are published <a href="/data/log">as daily files</a>.</p>
```

In the second way of writing, the link text defines the target. Two links in the same document
carrying the same text but going to different targets is also a situation to avoid.

A link is for going to a resource, not for an action that does not leave the document — deleting
a record, submitting a form. The button element is used for actions; the distinction will be
covered in this topic's form lessons.

#### The Target's Type and Relationship

The `download` attribute requests that the target be downloaded instead of displayed, and optionally gives
a file name; it holds only for same-origin addresses and the `blob:` and `data:` schemes, and is ignored
cross-origin. The `type` attribute declares the target's content type; it is a hint, not binding.

The `rel` attribute declares the link's **relationship** to its target. Its values are
space-separated keywords, and each one is defined.

```html
<p>Raw data: <a href="/data/2024-03.csv" download type="text/csv">March records</a></p>

<p>The method is defined in the <a href="https://other.test/standard" rel="noopener external">
   measurement standard</a>.</p>
```

The `target="_blank"` attribute opens the target in a new context. Current browsers apply the `noopener`
behavior implicitly for this attribute: the opened document cannot reach, via script, the document that
opened it. `rel="noopener"` is still written — both for older implementations and to keep the intent
visible in the document. Opening in a new context is itself a decision that deserves review; it breaks the
user's expectation of going back, and whether the document expects this behavior cannot be told from it.

The `rel` attribute is not specific to the `a` element. The `link` element in the head section
uses the same attribute and declares resources related to the document — a style file, an
icon, the same document's version in another language, a paginated index's previous and next
pages. On both elements, `rel` is the declaration "this address is in this relationship with
me."

#### In the Station Document

```html
<h2>Data Access</h2>
<p>Records are published <a href="data/">as daily files</a>. The measurement method
   <a href="../method.html">is shared across the network</a>. Raw data files can be
   downloaded as <a href="data/2024-03.csv" download type="text/csv">March records</a>.</p>
```

All three links are written as relative and depend on the document's place in the directory
tree. If the document moves, the `../method.html` link breaks; root-relative writing prevents
this break but makes it harder to publish the document at another root. The choice between the
two forms of writing is a decision about where the document will be published.

#### Summary

- Relative addresses are resolved against a base address; the base is, by default, the
  document's own address, and the document's file name is dropped in resolution.
- There are five relative forms: same-directory, parent-directory, root-relative,
  scheme-relative, and same-document; fragment addresses never go out onto the network.
- The `base` element changes the base for every relative address in the document and is found
  at most once in a document.
- Link text should define its target even when taken out of context; screen readers present
  links as a list.
- The `rel` attribute declares the link's relationship to its target; on links that open in a
  new context, the `noopener` relationship is also declared.

#### Next Step

At this point the document can connect to other resources. What is left is this topic's last
structure: two-dimensional data. Measurement records are not a list, they are a table — every
row has more than one field, and every field has a heading. The next lesson takes up table
elements and shows how a data cell's associated headings are computed.

### Tables

Source: https://academia.sh/en/courses/web-fundamentals-and-html/tables

Table elements' structure, header cells' scope, building the grid with colspan and rowspan, and computing a data cell's headers.

The previous lesson connected the document to other resources. This lesson takes up the
topic's last structure: two-dimensional data.

Measurement records are not a list. Every row has more than one field, every field has a
heading, and a cell's meaning comes from both its row's and its column's heading. The table
element declares this two-dimensional relationship.

#### When a Table Is Used

The criterion is single: is the data two-dimensional? If every cell's meaning comes from the
intersection of a row heading and a column heading, the structure is a table.

Using table elements to build page layout is a separate subject and is not done: layout is the
presentation layer's job, and when a table element is used, a screen reader announces the
content as a row-and-column relationship — a data relationship that does not exist gets
declared.

#### Basic Structure

```html
<table>
  <caption>North Slope station's weekly measurement summary</caption>
  <thead>
    <tr><th scope="col">Day</th><th scope="col">Low (°C)</th></tr>
  </thead>
  <tbody>
    <tr><th scope="row">Monday</th><td>-4</td></tr>
    <tr><th scope="row">Tuesday</th><td>-6</td></tr>
  </tbody>
  <tfoot>
    <tr><th scope="row">Average</th><td>-5</td></tr>
  </tfoot>
</table>
```

`caption` is the table's name and is written as the table's first child. It declares what the
table shows; a screen reader announces this text when entering the table, so it names the
table outside its context. A heading written as a separate paragraph above the table does not
do this job: there is no declared bond between it and the table.

`thead`, `tbody`, and `tfoot` split the table into three sections. Even if `tbody` is not
written, the parser builds it. The reason for writing them is that in long tables the header
row is separated from the data: this separation gives information both to the presentation
layer and to tools that process the table by section.

`tr` is a row, `th` a header cell, `td` a data cell. The difference between `th` and `td` is
not appearance: a header cell names the data cells associated with it.

#### A Header's Scope

The `scope` attribute declares which direction of cells a header cell names: `col` its column,
`row` its row. In simple tables, this declaration does not leave the cell–header match to
guesswork.

Column headers are found inside `thead`, row headers in each row's first cell. Both can be
present at once — in the example above, `Monday` is a row header, `Low` a column header, and
the `-4` cell is associated with both.

#### The Grid and Spanning

A table is written row by row in the source text but is really a **grid**. The `colspan` and
`rowspan` attributes let a cell cover more than one grid slot, and in that case the cell count
in the source text and the slot count in the grid diverge.

The script below builds the grid and computes every data cell's headers.

```js
// tables.mjs — builds the grid with colspan/rowspan, finds each cell's headers
const rows = [
  [{ h: 1, t: "Day", rowspan: 2 }, { h: 1, t: "Temperature (°C)", colspan: 2 }, { h: 1, t: "Humidity (%)", rowspan: 2 }],
  [{ h: 1, t: "Low" }, { h: 1, t: "High" }],
  [{ h: 1, t: "Monday" }, { t: "-4" }, { t: "3" }, { t: "72" }],
  [{ h: 1, t: "Tuesday" }, { t: "-6" }, { t: "1" }, { t: "68" }],
];

const grid = [];
const place = (r, c, cell) => {
  grid[r] ??= [];
  grid[r][c] = cell;
};

let rowNo = 0;
for (const row of rows) {
  let colNo = 0;
  for (const cell of row) {
    while (grid[rowNo]?.[colNo]) colNo += 1; // skip occupied spot
    const width = cell.colspan ?? 1;
    const height = cell.rowspan ?? 1;
    for (let r = 0; r < height; r += 1) {
      for (let c = 0; c < width; c += 1) place(rowNo + r, colNo + c, cell);
    }
    colNo += width;
  }
  rowNo += 1;
}

console.log("--- grid ---");
for (const row of grid) {
  console.log(row.map((h) => (h.h ? "[" + h.t + "]" : h.t).padEnd(20)).join("").trimEnd());
}

function headers(r, c) {
  const rowHeaders = [];
  for (let i = c - 1; i >= 0; i -= 1) {
    const h = grid[r][i];
    if (h?.h && !rowHeaders.includes(h.t)) rowHeaders.unshift(h.t);
  }
  const colHeaders = [];
  for (let i = r - 1; i >= 0; i -= 1) {
    const h = grid[i][c];
    if (h?.h && !colHeaders.includes(h.t)) colHeaders.unshift(h.t);
  }
  return [...rowHeaders, ...colHeaders];
}

console.log("--- data cells' headers ---");
for (let r = 2; r < grid.length; r += 1) {
  for (let c = 0; c < grid[r].length; c += 1) {
    const cell = grid[r][c];
    if (cell.h) continue;
    console.log(cell.t.padStart(3), "<-", headers(r, c).join(" / "));
  }
}
```

```
--- grid ---
[Day]               [Temperature (°C)]  [Temperature (°C)]  [Humidity (%)]
[Day]               [Low]               [High]              [Humidity (%)]
[Monday]            -4                  3                   72
[Tuesday]           -6                  1                   68
--- data cells' headers ---
 -4 <- Monday / Temperature (°C) / Low
  3 <- Monday / Temperature (°C) / High
 72 <- Monday / Humidity (%)
 -6 <- Tuesday / Temperature (°C) / Low
  1 <- Tuesday / Temperature (°C) / High
 68 <- Tuesday / Humidity (%)
```

In the source structure, the first row carries three cells, the second row two; in the grid,
though, every row is four slots. The `Day` and `Humidity (%)` headers span two rows, the
`Temperature (°C)` header spans two columns, and the same cell appears in more than one slot
in the grid.

The output's second section shows the real information the markup carries. The value `-4` is
meaningless on its own; together with the three headers it is associated with, it means "the
low temperature value on Monday." Screen readers announce these headers when a cell is
entered; this is how the user tracks where they are in the row and column.

Here is the markup's counterpart:

```html
<tr>
  <th scope="col" rowspan="2">Day</th>
  <th scope="colgroup" colspan="2">Temperature (°C)</th>
  <th scope="col" rowspan="2">Humidity (%)</th>
</tr>
<tr>
  <th scope="col">Low</th>
  <th scope="col">High</th>
</tr>
```

The `colgroup` scope value declares that the header names not a single column but a group of
columns; its row-direction counterpart is the `rowgroup` value.

#### Explicit Association in Complex Tables

The computation above relies on adjacency and gives the correct result when headers are placed
regularly. In irregular tables — headers in the middle of the table or at more than one level —
this guess is not enough. In this case the association is declared explicitly: every header
cell is given an `id`, and every data cell lists the identifiers of the headers it is
associated with in the `headers` attribute.

```html
<table>
  <tr>
    <th id="day" rowspan="2">Day</th>
    <th id="temp" colspan="2">Temperature (°C)</th>
  </tr>
  <tr>
    <th id="temp-low">Low</th>
    <th id="temp-high">High</th>
  </tr>
  <tr>
    <th id="day-mon" headers="day">Monday</th>
    <td headers="day-mon temp temp-low">-4</td>
    <td headers="day-mon temp temp-high">3</td>
  </tr>
</table>
```

Explicit association is expensive to maintain: every cell lists its own headers, and
identifiers have to be unique within the document. This cost is paid only when the structure
cannot be resolved by adjacency. A better solution in most cases is splitting the table — a
complex table, once split into two simple tables, becomes easier both to write and to read.

#### Column Groups

The `colgroup` and `col` elements are for giving attributes to columns in bulk and are written
at the start of the table, after the `caption` element. They carry no content; they name the
columns in the grid. Their use relates to the presentation layer and does not establish a data
association; they do not stand in for cell headers.

#### In the Station Document

```html
<h2>Weekly Summary</h2>
<table>
  <caption>North Slope station, daily extremes over the last seven days</caption>
  <thead>
    <tr>
      <th scope="col" rowspan="2">Day</th>
      <th scope="colgroup" colspan="2">Temperature (°C)</th>
      <th scope="col" rowspan="2">Humidity (%)</th>
    </tr>
    <tr>
      <th scope="col">Low</th>
      <th scope="col">High</th>
    </tr>
  </thead>
  <tbody>
    <tr><th scope="row">Monday</th><td>-4</td><td>3</td><td>72</td></tr>
    <tr><th scope="row">Tuesday</th><td>-6</td><td>1</td><td>68</td></tr>
  </tbody>
</table>
```

#### Summary

- A table is for two-dimensional data where a cell's meaning comes from the intersection of
  row and column headers; it is not used for page layout.
- `caption` names the table and is bound to it; a separate paragraph written above it does not
  establish this bond.
- The difference between `th` and `td` is not appearance, it is function; `scope` declares
  which direction of cells a header names.
- With `colspan` and `rowspan`, the cell count in the source text separates from the slot
  count in the grid; the same cell appears in more than one slot.
- In structures that cannot be resolved by adjacency, explicit association is established with
  `headers` and `id`; because maintenance cost is high, splitting the table is considered
  first.

#### Next Step

This topic built the document's skeleton: root structure, syntax, headings, text, lists,
links, and tables. Everything written in the document is a structural declaration, but these
declarations do not yet name the document's **sections**: where does navigation end, where
does the main content begin, which one is the footer? The next topic takes up the sectioning
tags that answer this question and builds the document's navigation structure.

## Semantic Markup and Media

Giving the document landmarks with sectioning elements; placing images, audio, video, and embedded content in an accessible and isolated way.

### Semantic Section Tags

Source: https://academia.sh/en/courses/web-fundamentals-and-html/semantic-section-tags

How header, navigation, main content, and footer elements correspond to landmark roles, naming sections, and where a meaningless container belongs.

The previous topic built the document's skeleton: headings, text,
lists, links, tables. None of these declarations name the document's
**sections**. Where does navigation end, where does the main content
start, which part is the footer?

This lesson takes up the elements that answer that question. Their
effect on appearance is close to nothing; what they earn is adding a
**high-level map** to the document.

#### What a Sectioning Element Declares

A reader who can see the screen infers sections from the layout: the
strip at the top is the header, the column on the side is navigation,
the area in the middle is content. This inference rests on visual cues
and is not declared in the document itself.

Semantic sectioning elements turn this inference into a
**declaration**. Once the document says "this is navigation," that
information opens up to non-visual tools too: screen reader users can
jump from section to section, reading modes can isolate the main
content, indexers can separate navigation links from content links.

The structure that carries this information is called the
**accessibility tree**. It gets derived from the tree in the Document
Object Model lesson, and gives every element a **role**, a **name**,
and a **state** as a trio. Sectioning elements produce **landmark**
roles in this tree.

#### Landmark Roles

The correspondences are defined, and a section is conditional on them.
The script below applies the rules.

```js
// roles.mjs - what landmark role section elements correspond to
const ROLES = {
  header: "banner",       // only at the document's top level
  nav: "navigation",
  main: "main",
  aside: "complementary",
  footer: "contentinfo",  // only at the document's top level
  form: "form",           // only if it has an accessible name
  section: "region",      // only if it has an accessible name
  article: "article",
  div: null,
};

const doc = [
  { tag: "header", depth: 1 },
  { tag: "nav", depth: 2, label: "Main navigation" },
  { tag: "main", depth: 1 },
  { tag: "section", depth: 2, label: "Daily measurements" },
  { tag: "section", depth: 2 },
  { tag: "aside", depth: 2, label: "Station profile" },
  { tag: "div", depth: 2 },
  { tag: "footer", depth: 1 },
  { tag: "footer", depth: 3 },
];

for (const item of doc) {
  let role = ROLES[item.tag];
  if ((item.tag === "header" || item.tag === "footer") && item.depth > 1) role = null;
  if ((item.tag === "section" || item.tag === "form") && !item.label) role = null;
  const suffix = item.label ? ' ("' + item.label + '")' : "";
  console.log(("  ".repeat(item.depth - 1) + item.tag + suffix).padEnd(34), role ?? "— not a landmark");
}

console.log("--- landmark list ---");
const landmarks = doc
  .map((item) => {
    let role = ROLES[item.tag];
    if ((item.tag === "header" || item.tag === "footer") && item.depth > 1) role = null;
    if ((item.tag === "section" || item.tag === "form") && !item.label) role = null;
    return role ? role + (item.label ? ": " + item.label : "") : null;
  })
  .filter(Boolean);
console.log(landmarks.join("\n"));
```

```
header                             banner
  nav ("Main navigation")          navigation
main                               main
  section ("Daily measurements")   region
  section                          — not a landmark
  aside ("Station profile")        complementary
  div                              — not a landmark
footer                             contentinfo
    footer                         — not a landmark
--- landmark list ---
banner
navigation: Main navigation
main
region: Daily measurements
complementary: Station profile
contentinfo
```

The output's last section is the list a screen reader user sees. It is
the document's map, and the user can jump straight to a section from
it.

Three conditions need attention. `header` and `footer` elements
produce a landmark only at the document's **top level**; a footer
inside an article is not a landmark, it is that article's footer. A
`section` element produces a landmark only once it has a **name**; an
unnamed section does not show up in the list. `div` never produces a
landmark, under any condition.

The script's `aside` correspondence is unconditional: the element keeps producing a landmark even when it
is nested inside an article or a section. This sets it apart from the top-level condition on
`header`/`footer` and the name condition on `section`. An unnamed `aside` is still hard to tell apart in
the list; giving it a name is a matter of usefulness, not a condition of the mapping.

#### The Elements

`header` is an introductory block: a title, a subtitle, a byline. At
the top level, it is the document's introduction; inside an article,
it is that article's introduction.

`nav` carries navigation links. Not every group of links in a document
has to be `nav` — the element marks the document's **primary**
navigation blocks. Links inside a paragraph are not navigation.

`main` is the document's main content, and it occurs **once** in visible
form in the document; additional `main` elements marked `hidden` are
allowed. It contains no navigation, no footer, no repeated content
spread across the whole site; it holds only content specific to that
document.

`aside` is content indirectly related to the main flow: a side note, a
profile box, related links. The test is that the main content does not
lose its meaning once the `aside` gets removed.

`footer` is a section's closing information: author, copyright,
related documents. At the top level, it is the document's footer;
inside an article, it is that article's footer.

#### Naming a Section

When more than one landmark of the same kind exists — two navigation
blocks, say — which one is which gets told apart by name. There are
two paths.

```html
<nav aria-label="Main navigation">
  <ul><li><a href="/">Network directory</a></li></ul>
</nav>

<nav aria-labelledby="station-navigation">
  <h2 id="station-navigation">Station sections</h2>
  <ul><li><a href="#measurements">Measurements</a></li></ul>
</nav>
```

`aria-label` writes the name directly; it does not appear in the
document. `aria-labelledby` takes the name from the text of an element
already present in the document, and this is **preferred**: the name
and the visible heading do not drift apart.

When writing the name, the role's name does not get repeated. Writing
`aria-label="Main navigation menu"` gets announced by a screen reader
as "main navigation menu navigation"; the role already comes from the
element.

#### The Skip Link

A user navigating by keyboard reaches the main content on every page
by passing through **all** of the navigation links. The structure that
shortens this is a link written as the document's **first focusable
element**.

```html
<body>
  <a href="#main-content" class="skip-link">Skip to main content</a>
  <header>
    <nav aria-label="Site-wide navigation">
      <ul><li><a href="/stations/">Stations</a></li></ul>
    </nav>
  </header>
  <main id="main-content">
    <h1>North Slope Measurement Station</h1>
  </main>
</body>
```

The link usually gets hidden at the presentation layer and becomes
visible only once it gets focused. The target's identifier sits on the
`main` element itself.

#### When to Use a Meaningless Container

`div` and `span` elements declare no meaning. Their reason for
existing is that not every grouping is meaningful: centering a box,
giving a background, building a presentation group needs an element,
and that element has to add no false declaration to the document.

The choice criterion is this: does this group say something about the
document's **structure**? If it does, a semantic element gets used; if
not, `div` gets used. Writing `section` purely for presentation adds a
meaningless entry to the landmark list — or, being unnamed, adds
nothing at all, and the element stays pointless.

`div` groups at the block level, `span` groups within text flow. Both
are handles for the presentation layer.

#### In the Station Document

```html
<body>
  <a href="#main-content">Skip to main content</a>

  <header>
    <p>Measurement Network</p>
    <nav aria-label="Site-wide navigation">
      <ul>
        <li><a href="/stations/">Stations</a></li>
        <li><a href="/method.html">Measurement method</a></li>
      </ul>
    </nav>
  </header>

  <main id="main-content">
    <h1>North Slope Measurement Station</h1>
    <p>Automatic measurement station built at an elevation of 1840 meters above sea level.</p>

    <section aria-labelledby="measured-quantities">
      <h2 id="measured-quantities">Measured Quantities</h2>
      <ul>
        <li>Temperature</li>
        <li>Relative humidity</li>
      </ul>
    </section>

    <aside aria-labelledby="station-profile">
      <h2 id="station-profile">Station Profile</h2>
      <dl>
        <dt>Installation elevation</dt><dd>1840 m</dd>
        <dt>Measurement interval</dt><dd>10 minutes</dd>
      </dl>
    </aside>
  </main>

  <footer>
    <p>Measurement Network Working Group</p>
  </footer>
</body>
```

The document's landmark list has **six** entries at this point, and this list is a navigation plane
separate from the heading outline built in the previous topic. The two complement each other: landmarks
give the document's coarse sections, headings give the hierarchy inside those sections.

#### Summary

- Semantic sectioning elements turn section information inferred from
  the visual layout into a declaration in the document, and produce
  landmark roles in the accessibility tree.
- `header` and `footer` produce a landmark only at the document's top
  level; `section` produces one only if it has a name; `div` never
  produces one, under any condition.
- `main` occurs once in the document and carries only content specific
  to that document.
- More than one landmark of the same kind gets told apart by name;
  taking the name from a heading visible in the document is preferred,
  and the role's name does not get written into the name.
- `div` and `span` get used for groupings that declare no meaning;
  writing a semantic element for presentation purposes adds a false
  declaration to the document.

#### Next Step

This lesson used `section` as a named section and showed `article` in
the landmark table, but did not open up the distinction between them.
The choice between the two is the most frequently misjudged semantic
decision. The next lesson defines this distinction and gives the test
for whether a section is meaningful on its own.

### Article and Section Distinction

Source: https://academia.sh/en/courses/web-fundamentals-and-html/article-and-section-distinction

Choosing between article and section with the independent-distributability test, the problem with an unheaded section, and the structure of nested articles.

The previous lesson used `section` as a named section, showed
`article` in the landmark table, but did not open up the distinction
between them. The choice between the two is the most frequently
misjudged decision in semantic markup — because it has no visible
outcome, and a wrong choice produces no error.

The distinction has a single test, and this lesson defines that test.

#### The Independence Test

The `article` element marks a unit of content that is **independently
distributable**. The test is this: if this content got taken out of
the document it is in and published on its own somewhere else — in a
feed, a list, a reading app — would it keep its meaning?

If it keeps its meaning, `article`; if not, `section`.

The test has nothing to do with the content's **type**. A newspaper
piece can be `article`; so can a product card, a comment, a log entry,
a search result. Conversely, a long document's "Method" section is not
`article`: taken out, what it is the method **of** gets lost.

The `section` element, though, is a **thematic grouping**. It sets
apart a part of the document; its meaning comes from its relationship
to the document as a whole. Every part that would form a
table-of-contents entry is a candidate for this element.

#### A Section Must Have a Heading

If a `section` element has no heading, what it sets apart never got
declared. This is the reason for the previous lesson's rule: a
`section` with no name does not show up in the landmark list, and the
element stays functionless.

The script below scans the sectioning elements in a document and
reports whether they carry a heading.

```js
// sections.mjs - heading check for sectioning elements
const doc = `
<main>
  <section id="summary">
    <h2>Station Summary</h2>
    <p>The North Slope station is installed at 1840 meters.</p>
  </section>
  <section id="notice">
    <p>Data flow may pause for maintenance.</p>
  </section>
  <article id="record-2">
    <h2>Calibration Record</h2>
    <p>Humidity sensor readjusted.</p>
  </article>
</main>`;

const SECTIONING = new Set(["section", "article", "nav", "aside"]);
const pattern = /<(\/?)([a-z0-9]+)([^>]*)>/g;
const stack = [];
let match;
while ((match = pattern.exec(doc)) !== null) {
  const [, closing, tag, attrs] = match;
  if (closing) {
    if (SECTIONING.has(tag)) {
      const item = stack.pop();
      console.log(
        (item.tag + (item.id ? "#" + item.id : "")).padEnd(18),
        item.heading ? "has heading" : "NO HEADING",
      );
    }
    continue;
  }
  if (SECTIONING.has(tag)) {
    const id = attrs.match(/id="([^"]*)"/)?.[1] ?? "";
    stack.push({ tag, id, heading: false });
  } else if (/^h[1-6]$/.test(tag) && stack.length > 0) {
    stack[stack.length - 1].heading = true;
  }
}
```

```
section#summary    has heading
section#notice     NO HEADING
article#record-2   has heading
```

The second section reports a defect. Its content is a notice, and it
is not a thematic section; the right element for a notice block would
be `div`, or — if the notice is meant to be a structural section — a
`section` with a heading added.

This check is not a style preference. An unheaded section does not
show up in the previous lesson's landmark list; while it looks like it
splits the document into sections, it does nothing at all.

#### Nested Articles

An article can contain another article, and this is valid: the inner
article relates to the outer one but is also meaningful on its own —
comments under a post are the typical example.

```html
<article>
  <h2>Humidity Sensor Calibration</h2>
  <p>The sensor showed a two percent deviation from the reference value.</p>

  <section>
    <h3>Observer Notes</h3>
    <article>
      <h4>Morning measurement</h4>
      <p>Internal condensation was observed.</p>
    </article>
    <article>
      <h4>Evening measurement</h4>
      <p>The deviation did not recur.</p>
    </article>
  </section>
</article>
```

The structure declares two levels. The outer article is a calibration
record; the inner ones are notes taken on that record, and each is
readable on its own. The `section` element connecting the two is not
independent — taken out, the information "notes for which record" gets
lost.

Heading levels need attention: nesting does not change the heading
level on its own. As stated in the Heading Hierarchy lesson, this
course counts levels across the document as a whole; inside an
`article`, level does not restart at `h1`.

#### An Article's Metadata

Independent distributability requires the article to carry its own
byline: taken out, when it was written and by whom has to go with it.
Two elements make this information machine-readable.

The `time` element marks a date or a duration. The visible text can be
written freely; the `datetime` attribute carries the same value in a
**defined** format.

```html
<article>
  <h3>Humidity sensor readjusted</h3>
  <p>A two percent deviation from the reference value was corrected.</p>
  <footer>
    <p>Record date: <time datetime="2024-03-12T07:30">March 12 morning</time></p>
    <address>Observer: A. Fisher</address>
  </footer>
</article>
```

`datetime`'s value has a defined format: the date gets written as
`YYYY-MM-DD`, the time as `HH:MM`, and the two get joined with `T`.
This format is needed because a free-form writing like "March 12
morning" cannot be sorted or compared.

The `address` element is not an address, it is **contact
information**: it marks the way to reach the article's author (or, at
the top level, the document's). It does not get used to mark a postal
address.

#### Decision Criteria

A sequential check resolves most cases.

| Question | Answer if yes |
|---|---|
| Is the content meaningful on its own once taken out? | `article` |
| Is it a named section setting apart one of the document's topics? | `section` |
| Is it a supplement that can get detached from the main content, indirectly related? | `aside` |
| Is it a grouping purely for presentation? | `div` |

If the first three questions all get answered "no," the element is
unnecessary. Adding a semantic element is not always an improvement: a
**wrong** declaration is more harmful than none, because the tool
reading the document trusts it.

Another common mistake is replacing every `div` element with
`section`. This fills the landmark list with meaningless entries, or,
being unnamed, adds nothing and only grows the source.

#### In the Station Document

Both structures sit together on the measurement station page.

```html
<main id="main-content">
  <h1>North Slope Measurement Station</h1>

  <section aria-labelledby="measured-quantities">
    <h2 id="measured-quantities">Measured Quantities</h2>
    <ul>
      <li>Temperature</li>
      <li>Relative humidity</li>
    </ul>
  </section>

  <section aria-labelledby="calibration-records">
    <h2 id="calibration-records">Calibration Records</h2>

    <article>
      <h3>Humidity sensor readjusted</h3>
      <p>A two percent deviation from the reference value was corrected.</p>
      <footer><p>Observer: A. Fisher</p></footer>
    </article>

    <article>
      <h3>Anemometer bearing replaced</h3>
      <p>Sticking had been observed at low speeds.</p>
      <footer><p>Observer: A. Fisher</p></footer>
    </article>
  </section>
</main>
```

"Measured Quantities" is a section: taken out, which station's
quantities they are gets lost. Each calibration record, though, is an
article: along with its date, action, and observer information, it can
get published on its own in another document.

The inner `footer` elements, as noted in the previous lesson, are not
the document's footer; they are the footer of the article they are in,
and do not enter the landmark list.

#### Summary

- `article` is a unit of content that keeps its meaning once taken out
  of the document; the test is not content type, it is independent
  distributability.
- `section` is a thematic grouping that sets apart one of the
  document's topics, and takes its meaning from its relationship to
  the document.
- A `section` with no heading does not show up in the landmark list
  and declares nothing about what it sets apart.
- Articles can nest; the inner article is related to the outer one,
  but is also meaningful on its own.
- The wrong semantic element is more harmful than writing none;
  groupings that fail the first three tests get written with `div`.

#### Next Step

The document is sectioned, but it still consists of nothing but text.
The measurement station page's location has to get told with an
image, and that image has to carry a counterpart for the reader who
cannot see it too. The next lesson takes up the image element: what
alternative text should say, how a size declaration affects layout,
and how the same image gets served at different sizes for different
screens.

### Images

Source: https://academia.sh/en/courses/web-fundamentals-and-html/images

What alternative text should say, how a size declaration affects layout, and how the same image gets served for different screens with a candidate set.

The document is sectioned, but it still consists of nothing but text.
The measurement station page's location has to get told with an
image, and that image has to carry a counterpart for the reader who
cannot see it too.

`img` is an empty element; it has no content, its source gets declared
with the `src` attribute. Two of its attributes are required: `src`
and `alt`.

#### Alternative Text

The `alt` attribute carries the text that **stands in for** the
image. It is not a description of the image; it is the text that does
the image's job once the image cannot get shown. The test is this: if
I had to translate this image into text, what would I write?

The answer depends on the image's function in the document, and there
are three cases.

**An informative image.** The alternative text gives that information.

```html
<img src="location.png" alt="The station sits in the middle section of
     the north-facing slope, 400 meters above the valley floor.">
```

What gets written here is not how the image looks — colors, drawing
style — it is what it says.

**A functional image.** For an image inside a link or a button, the
alternative text stands for the **action**, not the image.

```html
<a href="/data/2024-03.csv"><img src="download.svg" alt="Download March records"></a>
```

**A decorative image.** For an image that adds no information to the
document, the alternative text gets left **empty**.

```html
<img src="divider.svg" alt="">
```

An empty `alt` and not writing `alt` at all are not the same thing.
The empty value is the declaration "this image has no text
counterpart, skip it," and a screen reader does not announce the
image. If the attribute never gets written, no declaration gets made
at all; many screen readers then read out the file name, which gives
the user nothing.

The words "picture," "image," "photo" do not get written into the
alternative text; the element's type already gets announced from its
role. The text's length depends on the content; for descriptions past
a few sentences, a separate long-description link gets preferred.

#### Size Declaration

The `width` and `height` attributes declare the image's **intrinsic
dimensions** in pixels. This declaration matters even if the
presentation layer shows the image at a different size: the browser
calculates the ratio from it and allots space before the image
downloads.

```js
// ratio.mjs — aspect ratio and allotted height
const w = 1440, h = 960;
console.log("width/height =", w + "/" + h, "-> ratio", (w / h).toFixed(4));
for (const width of [300, 480, 720]) {
  console.log(String(width).padStart(4), "pixels wide, height:", (width * h / w).toFixed(1));
}
```

```
width/height = 1440/960 -> ratio 1.5000
 300 pixels wide, height: 200.0
 480 pixels wide, height: 320.0
 720 pixels wide, height: 480.0
```

If the declaration does not get written, the browser allots it zero
height until the image downloads; the moment the image downloads, the
content below it shifts down. If the user is reading at that moment,
or is about to press a button, this shift is a concrete problem. A
size declaration prevents this shift and lets layout get calculated
without waiting for the download.

The attributes get written without a unit; the writing
`width="960px"` is invalid.

The `loading="lazy"` attribute declares that the image does not get
requested until it nears the viewport. It does not get written on
images visible on the first screen — those images are needed right
away, and delaying them slows down the opening.

#### Candidate Set

A single image file is not suitable for every screen. Sending a wide
file to a narrow screen wastes bandwidth; sending a narrow file to a
wide, dense screen gives a blurry result.

The `srcset` attribute declares candidates of the same image at
different widths; `sizes` declares the image's width **in the
layout**. The browser makes the choice.

```html
<img src="slope-960.jpg"
     srcset="slope-480.jpg 480w, slope-960.jpg 960w,
             slope-1440.jpg 1440w, slope-1920.jpg 1920w"
     sizes="(max-width: 600px) 100vw, (max-width: 1000px) 50vw, 640px"
     width="1440" height="960"
     alt="The station sits in the middle section of the north-facing slope.">
```

The selection rule is computable: the first matching condition in the
`sizes` list gives the source size, each candidate's density is the
ratio of its `w` value to the source size, and the smallest-density
candidate meeting the device's pixel ratio gets chosen.

```js
// candidates.mjs — model of the srcset/sizes candidate selection rule
const candidates = [
  { src: "slope-480.jpg", w: 480 },
  { src: "slope-960.jpg", w: 960 },
  { src: "slope-1440.jpg", w: 1440 },
  { src: "slope-1920.jpg", w: 1920 },
];

// "(max-width: 600px) 100vw, (max-width: 1000px) 50vw, 640px"
const rules = [
  { maxWidth: 600, value: (v) => v * 1.0 },
  { maxWidth: 1000, value: (v) => v * 0.5 },
  { maxWidth: Infinity, value: () => 640 },
];

function sourceSize(viewportWidth) {
  return rules.find((r) => viewportWidth <= r.maxWidth).value(viewportWidth);
}

function pick(viewportWidth, pixelRatio) {
  const size = sourceSize(viewportWidth);
  const densities = candidates.map((c) => ({ ...c, density: c.w / size }));
  const sufficient = densities.filter((c) => c.density >= pixelRatio);
  const chosen = sufficient.length > 0
    ? sufficient.reduce((min, c) => (c.density < min.density ? c : min))
    : densities.reduce((max, c) => (c.density > max.density ? c : max));
  return { size, chosen };
}

const cases = [[390, 1], [390, 3], [800, 1], [800, 2], [1440, 1], [1440, 2]];
console.log("viewport  ratio  source size  chosen candidate  density");
for (const [width, ratio] of cases) {
  const { size, chosen } = pick(width, ratio);
  console.log(
    String(width).padStart(7),
    String(ratio).padStart(5),
    String(size).padStart(14),
    "  " + chosen.src.padEnd(15),
    chosen.density.toFixed(2),
  );
}
```

```
viewport  ratio  source size  chosen candidate  density
    390     1            390   slope-480.jpg   1.23
    390     3            390   slope-1440.jpg  3.69
    800     1            400   slope-480.jpg   1.20
    800     2            400   slope-960.jpg   2.40
   1440     1            640   slope-960.jpg   1.50
   1440     2            640   slope-1440.jpg  2.25
```

The output's first two lines compare the same screen width at two
different pixel densities: for an image shown at the same size, a
three-times-denser screen gets a three-times-bigger file chosen. The third and fifth lines show that as the screen widens,
the source size changes according to the `sizes` rule — half the
image at 800 pixels, a fixed 640 pixels at 1440.

Writing `sizes` incorrectly produces a silent defect: the browser
chooses by the declared size, not the actual size. The declaration
has to stay consistent with the layout.

The choice is the browser's decision, and it can also weigh network
conditions, cached files, or user settings. The calculation above is
the rule itself; it is not a guarantee of the result.

#### Format Selection

The `picture` element also gets used to choose the candidate set among
different **file formats**. Its `source` elements get tried in order,
and the first supported format gets chosen; the `img` element serves
as both the fallback and the alt-text carrier.

```html
<picture>
  <source srcset="slope.avif" type="image/avif">
  <source srcset="slope.webp" type="image/webp">
  <img src="slope.jpg" width="1440" height="960"
       alt="The station sits in the middle section of the north-facing slope.">
</picture>
```

The same mechanism also shows a different crop on a narrow screen:
when a `media` condition gets written on the `source` elements, choice
gets made by screen width. The distinction
between `srcset` and `picture` is this — `srcset` chooses different
scales of the same image, `picture` chooses different images or
different formats.

#### Summary

- `alt` is not a description of the image, it is the text that stands
  in for it; in functional images it stands for the action, in
  decorative images for an empty string.
- An empty `alt` and not writing `alt` differ: the first is the "skip"
  declaration, the second makes no declaration at all.
- A `width` and `height` declaration gives the aspect ratio and
  prevents content from shifting by letting space get allotted before
  the image downloads.
- `srcset` declares candidates, `sizes` declares the width in the
  layout; the choice gets calculated from the source size and the
  device's pixel ratio.
- `picture` chooses between different formats or different crops;
  `srcset` between scales of the same image.

#### Next Step

The image entered the document, but context is still missing: which
measurement point it shows, when it got taken, and which text it
belongs to — none of that has been declared. The next lesson takes up
the element that
associates an image with a caption, and shows how this relationship
changes the accessible name.

### Figure and Caption

Source: https://academia.sh/en/courses/web-fundamentals-and-html/figure-and-caption

The figure element's function as a content unit detachable from the flow, the difference between a caption and alternative text, and the order of the accessible-name calculation.

The image entered the document, but context is missing: which
measurement point it shows, when it got taken, which text it belongs
to — none of that has been declared. Alternative text does not carry
this information — that is for the reader who cannot see the image;
context is for everyone.

The `figure` element establishes this distinction: it binds its
content and its caption together as a single unit.

#### The Figure Element

`figure` is a content unit that can get **detached** from the main
flow but gets referenced in the flow. The test is this: if this
content got moved from the middle of the text to the page's margin,
an appendix, or the next page, would the text keep reading?

If it keeps reading, it is a figure. Content that breaks the text's
flow and cannot get moved from its place is not a figure.

`figcaption` is the figure's caption, written as the `figure`
element's first or last child. A figure has at most one caption.

```html
<figure>
  <img src="location.png" width="1440" height="960"
       alt="The station sits in the middle section of the north-facing
            slope, 400 meters above the valley floor.">
  <figcaption>Figure 1. The station's location on the slope and its
    height relative to the valley floor.</figcaption>
</figure>
```

#### The Caption/Alternative-Text Distinction

Two texts attach to the same image and do different jobs.

`alt` **stands in for** the image. It gives the reader who cannot see
the image the information a sighted reader gets from it. It does not
get presented to a sighted reader.

`figcaption` is the information **surrounding** the image: its name,
its number, its source, its date, a comment. It gets presented to
every reader.

Having both carry the same text is a defect: a screen reader user
hears the same sentence twice. Instead, information the caption
already contains gets left out of the alternative text.

If the caption gives the image's information in full — a caption that
writes out a chart's values, say — the alternative text can get left
empty. This is a non-decorative use of `alt=""`: the image's
information already exists in the document as text.

#### The Accessible-Name Calculation

The previous lesson noted that elements get met in the accessibility
tree with a **role**, a **name**, and a **state**. Which source the
name comes from is set by an ordered rule, and `figcaption` is one
link in it.

```js
// name.mjs — order of the accessible-name calculation
const LOCAL_SOURCE = {
  img: "alt",
  figure: "figcaption",
  table: "caption",
  fieldset: "legend",
};

function accessibleName(el, doc) {
  if (el["aria-labelledby"]) {
    const parts = el["aria-labelledby"]
      .split(/\s+/)
      .map((id) => doc[id] ?? "")
      .filter(Boolean);
    if (parts.length > 0) return ["aria-labelledby", parts.join(" ")];
  }
  if (el["aria-label"]) return ["aria-label", el["aria-label"]];
  const field = LOCAL_SOURCE[el.tag];
  if (field && el[field] !== undefined && el[field] !== "") return [field, el[field]];
  if (el.title) return ["title", el.title];
  return ["none", ""];
}

const doc = { "measurement-heading": "North Slope", "measurement-date": "March 12" };

const elements = [
  { tag: "img", alt: "Measurement pole on the slope" },
  { tag: "img", alt: "" },
  { tag: "img", title: "Pole" },
  { tag: "figure", figcaption: "Figure 1: The pole's location" },
  { tag: "figure", figcaption: "Figure 1: The pole's location", "aria-label": "Pole location" },
  { tag: "figure", "aria-labelledby": "measurement-heading measurement-date", figcaption: "Figure 1" },
  { tag: "table", caption: "Daily measurements" },
];

for (const el of elements) {
  const [source, name] = accessibleName(el, doc);
  console.log(el.tag.padEnd(8), source.padEnd(16), JSON.stringify(name));
}
```

```
img      alt              "Measurement pole on the slope"
img      none             ""
img      title            "Pole"
figure   figcaption       "Figure 1: The pole's location"
figure   aria-label       "Pole location"
figure   aria-labelledby  "North Slope March 12"
table    caption          "Daily measurements"
```

The order has four steps, and the source above overrides the one
below: `aria-labelledby`, then `aria-label`, then the element's
**local** source, and `title` last.

Three lines deserve attention. The second shows an empty `alt`
erasing the name, the mechanism behind an unannounced image. The
third shows `title` as a last resort: without `alt`, `title` becomes
the name — a fallback, not a fix. The fifth and sixth show
`aria-label` and `aria-labelledby` **overriding** the visible caption
— name and visible text pull apart, best avoided without a reason for
the split.

The table row shows that the `caption` element from the previous
topic is part of the same mechanism: different elements have
different local sources, but the calculation order is shared.

#### A Figure Is Not Just for Images

The `figure` element does not restrict the content's type. A code
listing, a table, a quotation, an audio recording can also be a
figure; the test is still detachment from the flow.

```html
<figure>
  <figcaption>Figure 2. The raw data file's first lines.</figcaption>
  <pre><code>time,temperature,humidity
07:00,-4.2,72
07:10,-4.1,71</code></pre>
</figure>
```

A table already has its `caption` element; wrapping the table in
`figure` makes sense when it needs a detachable-from-flow declaration
too. When both get used together, neither caption should repeat the
other.

#### Long Description

A complex chart's or map's information does not fit into an
alternative text. In such cases `alt` briefly states what the image
is, and the detailed counterpart exists in the document as text, tied
to the figure.

```html
<figure>
  <img src="weekly.png" width="1200" height="700"
       alt="Weekly temperature chart; values in the table below.">
  <figcaption>Figure 3. Weekly temperature trend.</figcaption>
  <details>
    <summary>The chart's numeric counterpart</summary>
    <table>
      <caption>Daily lowest and highest temperatures</caption>
      <tr><th scope="col">Day</th><th scope="col">Lowest</th></tr>
      <tr><th scope="row">Monday</th><td>-4</td></tr>
    </table>
  </details>
</figure>
```

The `details` element sets up a disclosure section that starts
closed; the `summary` element is its visible label. Opening and
closing are built into the element, needing no script. Here it keeps
the detailed counterpart in the document without occupying the flow.

This approach's advantage over a longer alternative text is that the
counterpart is open to **everyone**: a sighted reader who wants the
numeric values gets the same table too.

#### Figure Numbers

The "Figure 1," "Figure 2" numbers written in the caption are the
document's text; they do not come from the element. Their value is
that the text can refer to the figure from a distance: the sentence
"In the file format shown in Figure 2…" keeps its meaning even if the
figure moves elsewhere on the page.

When the numbers get written by hand, inserting a figure in between
shifts all of them. The presentation layer can also generate these
numbers on its own; then the number does not exist in the document
and cannot get referenced from the text. The choice depends on
whether the text needs to reference it.

#### In the Station Document

```html
<section aria-labelledby="location">
  <h2 id="location">Location and Installation</h2>
  <p>The station sits in the middle section of the north-facing
     slope. Because this side of the slope gets no direct light
     throughout the day, deviation in the temperature measurement is
     low.</p>

  <figure>
    <img src="location-960.png"
         srcset="location-480.png 480w, location-960.png 960w, location-1440.png 1440w"
         sizes="(max-width: 600px) 100vw, 640px"
         width="1440" height="960"
         alt="Slope cross-section: the station is marked 400 meters
              above the valley floor and 260 meters below the summit.">
    <figcaption>Figure 1. The station's location on the slope.</figcaption>
  </figure>
</section>
```

The alternative text gives the numeric information the cross-section
carries; the caption names and numbers the figure, with no repetition
between them.

#### Summary

- `figure` is a content unit detachable from the main flow but
  referenced in it; the test is whether the text keeps reading once
  moved.
- `alt` stands in for the image and gets presented only to the reader
  who cannot see it; `figcaption` surrounds the image, presented to
  everyone.
- Having both carry the same text is repetition; the information the
  caption gives gets left out of the alternative text.
- The accessible-name calculation is ordered: `aria-labelledby`,
  `aria-label`, the element's local source, `title`. The one above
  overrides the one below.
- A figure does not restrict the content's type; code listings,
  tables, and quotations can also be figures.

#### Next Step

Visual content is in place. Content that flows through time — audio
and video — brings separate problems: who holds the controls, how speech gets a
text counterpart, and why unwanted autoplaying sound gets restricted.
The next lesson takes up these elements and their source-selection
rules.

### Audio and Video

Source: https://academia.sh/en/courses/web-fundamentals-and-html/audio-and-video

Controls for content that flows through time, the source-selection rule, caption and description tracks, and the rationale behind the autoplay restriction.

Visual content is in place. Content that flows through time brings
separate problems. Once an image gets shown, the reader looks at it
as long as wanted; once a video plays, though, the flow may not be in
the user's control, its sound can interrupt other work, and without a
text counterpart of the speech, the content stays closed to those who
cannot hear.

The `audio` and `video` elements carry this content. Both share the
same attribute set; `video` additionally declares the image
dimensions and a cover image.

#### Basic Markup

```html
<video controls preload="metadata" width="1280" height="720" poster="cover.jpg">
  <source src="briefing.webm" type="video/webm">
  <source src="briefing.mp4" type="video/mp4">
  <track kind="captions" src="briefing-en.vtt" srclang="en"
         label="English captions" default>
  <p>The video cannot be played.
     <a href="briefing.mp4">Download the file</a>.</p>
</video>
```

The `controls` attribute shows the playback controls. If it does not
get written, the user cannot stop the content, lower its volume, or
seek forward; it therefore gets written on every media the user needs
to control.

`preload` declares how much data gets downloaded ahead of time:
`none` nothing, `metadata` only duration and size information, `auto`
as much as the browser sees fit. For media whose playback is
uncertain, `metadata` shows the duration and waits without
downloading the data.

The `p` block inside the element is fallback for a client that does
not process media; it gets written after the `source` and `track`
elements.

#### Source Selection

The `source` elements get tried in order, and the **first supported**
source gets chosen. The `type` attribute carries the container format
and codec information, letting the browser decide whether it can play
the file without downloading it.

```js
// media.mjs — trying source elements in order
const sources = [
  { src: "briefing.av1.mp4", type: 'video/mp4; codecs="av01.0.05M.08"' },
  { src: "briefing.vp9.webm", type: 'video/webm; codecs="vp9"' },
  { src: "briefing.h264.mp4", type: 'video/mp4; codecs="avc1.42E01E"' },
];

function pick(supported) {
  for (const source of sources) {
    const codec = source.type.match(/codecs="([^"]+)"/)?.[1] ?? "";
    const container = source.type.split(";")[0].trim();
    if (supported.containers.includes(container) && supported.codecs.some((c) => codec.startsWith(c))) {
      return source.src;
    }
  }
  return null;
}

const clients = [
  { name: "A", containers: ["video/mp4", "video/webm"], codecs: ["av01", "vp9", "avc1"] },
  { name: "B", containers: ["video/mp4"], codecs: ["avc1"] },
  { name: "C", containers: ["video/webm"], codecs: ["vp9"] },
  { name: "D", containers: ["video/ogg"], codecs: ["theora"] },
];

for (const client of clients) {
  console.log("client " + client.name + ":", pick(client) ?? "no source can play");
}

console.log("--- track kind values ---");
const tracks = [
  { kind: "captions", srclang: "en", label: "English captions", default: true },
  { kind: "subtitles", srclang: "es", label: "Spanish subtitles" },
  { kind: "descriptions", srclang: "en", label: "Audio description" },
];
for (const track of tracks) {
  const line = track.kind.padEnd(14) + track.srclang.padEnd(4)
    + track.label.padEnd(20) + (track.default ? "default" : "");
  console.log(line.trimEnd());
}
```

```
client A: briefing.av1.mp4
client B: briefing.h264.mp4
client C: briefing.vp9.webm
client D: no source can play
--- track kind values ---
captions      en  English captions    default
subtitles     es  Spanish subtitles
descriptions  en  Audio description
```

The output shows three things. Order is decisive: client A can play
all three sources and picks the first on the list, which is why the
most efficient format gets written first. Clients B and C fall to the
first suitable source on the list. Client D cannot play any of them
and sees the fallback content.

Container format and codec differ: the same container can carry
different codecs, and a client may support the container without
supporting the codec inside it. Writing only the container in the
`type` declaration hides this distinction and leads the browser to
choose the wrong source and fail.

#### Text Tracks

The `track` element ties timed text to the media; `kind` declares the
track's function, and functions are not interchangeable.

`captions` writes out meaningful sounds alongside speech — a door
sound, a warning signal — and is for the user who cannot hear.
`subtitles` only translates the speech into another language, for the
user who can hear but does not know the language. The two look alike
visually, but their content differs, and neither substitutes for the
other.

`descriptions` narrates information in the picture not covered in the
speech, and is for the user who cannot see. `chapters`
divides the media into sections; the user can jump from a chapter
list.

The `default` attribute declares the track that starts on absent
another choice. `srclang` is the track's language, `label` is the
name shown to the user.

Captions are not an enhancement added to the media afterward. If a
piece of audio content has no text counterpart, that content does not
exist for users who cannot hear it; this is the same situation as an
image with no alternative text.

#### Cover Image and Allotted Space

The `poster` attribute declares the image shown before the video
plays. If it does not get written, the browser waits for the media's
first frame; with `preload="none"`, no frame downloads and the space
stays empty.

The `width` and `height` attributes do the same job here as with
images: as in the Images lesson, they declare the aspect ratio and
get space allotted before download, keeping content below from
shifting. The cover image's ratio gets kept the same as the video's;
if it differs, a jump appears once playback starts.

#### Linking to a Time Range

A specific moment in the media can get linked to with the address's
fragment part. As in the Links lesson, the fragment part does not go
to the server; here the player interprets it.

```html
<p>The removal step begins <a href="maintenance.mp4#t=90">at second 90</a>;
   <a href="maintenance.mp4#t=90,145">the 90–145 second range</a> shows only that step.</p>
```

The value is in seconds; when two values get written separated by a
comma, a start and an end get specified. This lets text refer to a
specific part of a long recording, saving the user from seeking by
hand. When unsupported, the link opens the media from the start; the
failure cost is low.

#### Autoplay

The `autoplay` attribute requests that the media start without the
user asking for it. Browsers restrict this request for the user's
sake: unexpected sound drowns out screen-reader output, causes
disruption in shared spaces, and costs money on a metered data
connection.

The shape of the restriction varies by browser; the common point:
media starting with sound on does not play without user interaction.
The restriction is looser for media that start muted. This cannot get
tested by feature detection; the start request returns a promise that
can get rejected.

In practice, two consequences follow. Autoplay does not get assumed
to work; the media itself must look meaningful even when unstarted by
the user. Writing `autoplay` without `muted` does nothing in most
contexts.

The `loop` attribute repeats the media. Repeating motion with no time
limit is a problem for users with motion sensitivity; repeating
content longer than five seconds needs a way to stop it.

#### In the Station Document

```html
<figure>
  <video controls preload="metadata" width="1280" height="720" poster="maintenance-cover.jpg">
    <source src="maintenance.webm" type='video/webm; codecs="vp9"'>
    <source src="maintenance.mp4" type='video/mp4; codecs="avc1.42E01E"'>
    <track kind="captions" src="maintenance-en.vtt" srclang="en" label="English captions" default>
    <track kind="descriptions" src="maintenance-description.vtt" srclang="en" label="Audio description">
    <p>The video cannot be played. <a href="maintenance.mp4">Download the file</a>.</p>
  </video>
  <figcaption>Figure 2. Reading the humidity sensor without removing it from its mount.</figcaption>
</figure>
```

The video is a figure: it can get detached from the text's flow and
gets referenced in the flow. Controls are on, caption and description
tracks are declared, and autoplay has not been requested.

#### Summary

- If `controls` does not get written, the user cannot stop the media;
  it gets written on every media that needs control.
- `source` elements get tried in order and the first supported one
  gets chosen; the `type` declaration carries container and codec
  information together.
- `captions` is for the user who cannot hear, `subtitles` for the one
  who does not know the language, `descriptions` for the one who
  cannot see; one does not substitute for another.
- Audio content with no text counterpart gets treated as nonexistent
  for users who cannot hear it.
- An autoplay request can get rejected; audible autoplay does not
  happen in most contexts, and the document gets written for the case
  where it does not.

#### Next Step

Audio and video were the document's own resources. Some content,
though, is the whole of another document: a map, a player, a section
from another site. Embedding these hands part of the control outside.
The next lesson takes up embedding a frame and the isolation
attributes restricting the embedded document's capabilities.

### Embedded Content

Source: https://academia.sh/en/courses/web-fundamentals-and-html/embedded-content

Embedding another document with a frame, the embedded document's isolation, the capability-restriction mechanism, and the performance and accessibility consequences of embedding.

Audio and video were the document's own resources. Some content,
though, is the whole of another document: a map, a player, a section
from another site. Embedding these means placing a second document
inside the document.

The `iframe` element does this job. The element's name means "inline
frame," and that is exactly what it is: a separate document, loaded
on its own and with its own tree, within the document's flow.

#### The Embedded Document Is a Separate Document

The document inside `iframe` is not part of the outer document's
tree. It has its own root, its own head section, its own styles, and
its own scripts. The outer document's style rules do not get applied
inside; the script inside cannot access the outer document's tree
directly.

This separation is not a limitation, it is the entire rationale for
embedding. Placing content from an uncontrolled source directly into
your document gives that content access to the whole document. The
frame establishes the boundary between them.

The boundary rests on the concept of **origin**: the trio of scheme,
host, and port. Same-origin documents can access each other's tree;
different-origin ones cannot. The address parts separated in the What
the Browser Does
lesson in the How the Internet Works curriculum turn into a security
boundary here.

#### The Isolation Attribute

Different-origin separation draws a basic boundary, but is not
enough: the embedded document can still run scripts, submit
forms, open a new window, navigate the top document to another
address. The `sandbox` attribute shuts these capabilities off.

The attribute works in reverse: the moment it gets written, **no
capability gets granted**; the capabilities wanted back get listed one
by one.

```js
// embed.mjs — capability model of the sandbox attribute
const CAPABILITIES = [
  ["allow-scripts", "running scripts"],
  ["allow-forms", "submitting forms"],
  ["allow-popups", "opening a new window"],
  ["allow-same-origin", "being counted as its own origin"],
  ["allow-top-navigation", "navigating the top document elsewhere"],
  ["allow-modals", "opening a modal window"],
];

function resolve(attrValue) {
  if (attrValue === null) return new Set(CAPABILITIES.map(([name]) => name)); // no sandbox
  return new Set(attrValue.split(/\s+/).filter(Boolean));
}

const cases = [
  ["no sandbox attribute", null],
  ["sandbox (empty)", ""],
  ["sandbox=allow-scripts", "allow-scripts"],
  ["sandbox=allow-scripts allow-forms", "allow-scripts allow-forms"],
];

for (const [label, value] of cases) {
  const open = resolve(value);
  console.log("[" + label + "]");
  for (const [key, description] of CAPABILITIES) {
    console.log("  " + (open.has(key) ? "open" : "closed").padEnd(6) + "  " + description);
  }
}

console.log("--- dangerous combination ---");
const both = resolve("allow-scripts allow-same-origin");
console.log(
  "allow-scripts + allow-same-origin:",
  both.has("allow-scripts") && both.has("allow-same-origin")
    ? "the frame can remove its own sandbox attribute"
    : "isolation is preserved",
);
```

```
[no sandbox attribute]
  open    running scripts
  open    submitting forms
  open    opening a new window
  open    being counted as its own origin
  open    navigating the top document elsewhere
  open    opening a modal window
[sandbox (empty)]
  closed  running scripts
  closed  submitting forms
  closed  opening a new window
  closed  being counted as its own origin
  closed  navigating the top document elsewhere
  closed  opening a modal window
[sandbox=allow-scripts]
  open    running scripts
  closed  submitting forms
  closed  opening a new window
  closed  being counted as its own origin
  closed  navigating the top document elsewhere
  closed  opening a modal window
[sandbox=allow-scripts allow-forms]
  open    running scripts
  open    submitting forms
  closed  opening a new window
  closed  being counted as its own origin
  closed  navigating the top document elsewhere
  closed  opening a modal window
--- dangerous combination ---
allow-scripts + allow-same-origin: the frame can remove its own sandbox attribute
```

The first two cases show that whether the attribute gets written
makes the entire difference. An empty `sandbox` also places the
embedded document into a separate origin:
since it does not count as the same origin as its own server, it
cannot access stored data or cookies.

The last line declares two values that should not get written
together. `allow-same-origin` counts the embedded document as the
same as its own origin; `allow-scripts` permits running scripts. With
both together and a shared origin, the script inside the frame can
remove the outer document's `sandbox` attribute and reload the frame
— isolation cancels itself out.

#### Capability Permissions

`sandbox` restricts the document's **behavior**; the `allow`
attribute, though, regulates access to device and platform
capabilities: camera, microphone, location, full screen.

```html
<iframe src="https://map.test/north-slope"
        title="The station's location on the map"
        width="640" height="480" loading="lazy"
        sandbox="allow-scripts"
        allow="fullscreen; geolocation 'none'"
        referrerpolicy="no-referrer"></iframe>
```

The default is that access is closed for most capabilities; the
`allow` declaration opens it, and the `'none'` value closes it
explicitly. These two attributes do not substitute for each other and
get used together.

The `referrerpolicy` attribute declares how much information about
the requesting document gets sent to the embedded resource. The
`no-referrer` value sends none; the embedded third party cannot learn
which page the user is on.

#### Name and Loading

The `title` attribute gives the frame an accessible name. If it does
not get written, a screen reader announces the frame as an unnamed
region, and the user cannot tell what it is without entering it. This
attribute is not optional on embedded content.

`loading="lazy"` declares that the frame does not get loaded until it
nears the viewport. Because the embedded document also requests its
own subresources, its cost is much higher than a single image's; for
a map or player at the bottom of the page, this declaration noticeably
lowers opening cost.

The `width` and `height` attributes do the space-allotting job here
too; a frame with no size declaration shifts the content below it
once it loads.

#### Protecting Against Being Embedded

Embedding runs both ways: just as a document can embed another,
others can embed this one in their own documents too. This can get used to
mislead the user about which site they are interacting with — the
visible document is yours, the transparent layer placed on top of it
is someone else's, and the place the user clicks and the button they
think they pressed pull apart.

Protection gets declared not in the document but in the server
response. The `frame-ancestors` directive in the
`Content-Security-Policy` header lists which origins can embed the
document.

```
Content-Security-Policy: frame-ancestors 'self'
```

This declaration allows the document to get embedded only by pages on
its own origin; the `'none'` value permits no embedding at all. As in
the Meta Tags lesson, this can also get written with `http-equiv`, but
not for this directive: the embedding decision gets made before the
document gets parsed, so the declaration has to exist in the response
header.

This header gets written on every document that carries session data
or initiates a transaction. If it does not get written, embedding the
document is free, letting a page outside the document's control put
it before the user in any context.

#### The Cost of Embedding

Every embedded document starts an independent loading chain: its own
name resolution, its own connection, its own subresources. All the
steps listed in the Resource Loading Order lesson get carried out
again, and these steps share the same bandwidth as the outer
document's resources.

Where a function works without embedding, not embedding gets
preferred. If an interactive map is not needed to show
a location, an image and a coordinate text give the same information
much more cheaply. The station document's choice runs the same way:
the location got told with a figure, and the map did not get
embedded.

#### Summary

- The document inside `iframe` is a separate document; it has its own
  tree, styles, and scripts, and cannot access the outer document if
  it comes from a different origin.
- `sandbox` shuts off all capabilities the moment it gets written;
  wanted capabilities get granted back one by one.
- When `allow-scripts` and `allow-same-origin` get written together
  at the same origin, isolation can get removed by the frame itself.
- `sandbox` regulates behavior, `allow` regulates device and platform
  capabilities; the two do not substitute for each other.
- `title` is required on embedded content; `loading="lazy"` and a
  size declaration limit the embedding's opening cost and content
  shift.

#### Next Step

This topic completed the elements that give the document meaning.
What remains is a mechanism with no defined meaning of its own, but
useful for attaching data to the document: attributes that can get
attached to any element, produce no name collision, and can get read
by scripts. The next lesson takes up this mechanism and how an
attribute's name gets translated into its program-side counterpart.

### Data Attributes

Source: https://academia.sh/en/courses/web-fundamentals-and-html/data-attributes

Attaching data to a document without a name collision, the conversion between an attribute name and its program-side key, and the consequence of values always being strings.

The previous lessons completed the elements that give the document
meaning. What remains is a mechanism with no defined meaning but
useful: attributes that can get attached to any element,
produce no name collision, and can get read by the program.

The problem is this: an element may need information attached to it
that the language does not define — a sensor's ID, a record's status,
a section's refresh interval. This information is not visible in the
document but is needed by the behavior layer.

#### Why a Separate Mechanism

The obvious ways of carrying such information are problematic.

Using an existing attribute outside its purpose — embedding data in
`title` or `class`, say — breaks that attribute's defined behavior:
`title` gets announced by a screen reader, `class` mixes into the
presentation layer's selectors.

Making up your own name — `sensor-id="4"`, say — does not produce a
valid document. As noted in the Markup Language Concept lesson, the
parser keeps an unknown attribute, so it works; but if the language
later adds an attribute named `sensor-id`, the document's meaning
changes silently.

The `data-` prefix answers both problems at once. The prefix is
reserved by the language: the language's own attributes never start
with this prefix, so a collision is impossible. And it has no defined
behavior: it does nothing until its value gets read.

```html
<article data-record-id="4"
         data-sensor-type="humidity"
         data-measurement-interval="600"
         data-verified="false">
  <h3>Humidity sensor readjusted</h3>
</article>
```

#### Name Conversion

There is a defined conversion between the attribute name and the
program-side key. The attribute name gets written lowercase and
hyphenated; on the program side, the prefix drops and the hyphens get
removed with the following letter capitalized.

```js
// data.mjs — conversion between data-* attribute names and dataset keys
function attrToKey(writtenName) {
  const name = writtenName.toLowerCase(); // the parser lowercases attribute names
  if (!name.startsWith("data-")) return null;
  return name.slice(5).replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
}

function keyToAttr(key) {
  if (/-[a-z]/.test(key)) return null; // a hyphen before a lowercase letter is forbidden
  return "data-" + key.replace(/[A-Z]/g, (letter) => "-" + letter.toLowerCase());
}

const attributes = [
  "data-station",
  "data-measurement-interval",
  "data-last-calibration-date",
  "data-x2",
  "data-Sensor",
  "class",
];

console.log("attribute name                dataset key            round trip");
for (const name of attributes) {
  const key = attrToKey(name);
  const back = key === null ? "—" : keyToAttr(key);
  console.log(name.padEnd(30) + String(key ?? "—").padEnd(23) + String(back));
}

console.log("--- values are always strings ---");
const dataset = { station: "north-slope", measurementInterval: "600", active: "false" };
for (const [key, value] of Object.entries(dataset)) {
  console.log(key.padEnd(14), JSON.stringify(value), "-> typeof:", typeof value);
}
console.log("Number(measurementInterval) =", Number(dataset.measurementInterval));
console.log('Boolean("false")     =', Boolean(dataset.active));
```

```
attribute name                dataset key            round trip
data-station                  station                data-station
data-measurement-interval     measurementInterval    data-measurement-interval
data-last-calibration-date    lastCalibrationDate    data-last-calibration-date
data-x2                       x2                     data-x2
data-Sensor                   sensor                 data-sensor
class                         —                      —
--- values are always strings ---
station        "north-slope" -> typeof: string
measurementInterval "600" -> typeof: string
active         "false" -> typeof: string
Number(measurementInterval) = 600
Boolean("false")     = true
```

The fifth row shows a trap: the `data-Sensor` writing does not
produce the expected key, because, as in the Tags, Attributes, and
Entities lesson, the parser lowercases attribute names. An
attribute name does not use uppercase letters; multi-word names get
separated with a hyphen.

The fourth row shows that digits do not enter the conversion: a
hyphen only gets removed if a lowercase letter follows it.

#### Values Are Strings

The output's second section shows data attributes' most often
overlooked property: values are always strings. Even if a number gets
written, it gets read as a string and has to get converted for
comparisons.

The last line is the consequence of this for boolean values. The
string `"false"` counts as true because it is not empty; the
`Boolean` conversion does not give the expected result. Carrying
boolean information takes one of two paths: comparing the value
explicitly, or following the boolean-attribute rule and counting
**presence** as the meaning — writing the attribute means true, never
writing it means false.

#### When It Is Not Appropriate

A data attribute is not a storage space. It does not get used in
three cases.

**To carry content.** If a text that needs to be visible in the
document gets written into a data attribute, it exists in the source
text but not in the document: it cannot get selected, searched,
translated, or presented to a screen reader.

**For information the language already has a counterpart for.** The
information that an element is disabled, a section is hidden, a field
is required is defined in the language and declared with that
attribute. Writing this information into a data attribute conveys
nothing to the accessibility tree.

**For large data.** Attribute values get sent with the document on
every request, and parsing slows down as the document grows. Instead
of attaching a whole record, attaching its ID and requesting the data
separately gets preferred.

#### Declaring State: Class or Data

There are two ways to declare an element's current state — selected,
loading, errored — and both are in use.

Adding a class name is common, but a class list is a set, carrying no
value: the information "state is loading" can only get told through
the presence of a class named `loading`, and keeping only one state
valid at a time is done by hand. If removing the old state gets
forgotten, two states appear at once.

A data attribute is a name-value pair; in the writing
`data-state="loading"`, a new value replaces the old one, and two
states existing at once is impossible. The presentation layer reads
this value with an attribute selector; selectors are the next
course's subject.

This distinction does not hold for states that need to get announced
to the user. The information that a field is errored, a button is
pressed, a section is expanded has to get conveyed to the
accessibility tree, via the language's own attributes and the ARIA
state attributes. A data attribute conveys nothing to that tree.

#### Data Exposed to the Outside

Data attributes are a contract between the document's author and the
document's own scripts. Their names are free, their meanings are
specific to the document, and an outside program cannot make anything
of them.

If information needs to get exposed to the outside — to indexers, to
aggregators — this mechanism is not enough; a shared vocabulary is
needed. Structured data contracts embedded in the document do this,
resting on a defined set of concepts. The distinction is this:
`data-` is for your own program, structured data is for someone
else's.

#### In the Station Document

```html
<section aria-labelledby="calibration-records" data-refresh-interval="600">
  <h2 id="calibration-records">Calibration Records</h2>

  <article data-record-id="4" data-sensor-type="humidity">
    <h3>Humidity sensor readjusted</h3>
    <p>A two percent deviation from the reference value was corrected.</p>
    <footer>
      <p>Record date: <time datetime="2024-03-12T07:30">March 12 morning</time></p>
    </footer>
  </article>
</section>
```

All three attached values are information that does not need to be
visible in the document and is useful to the behavior layer. The
record's date, though, is written to the `time` element, not a data
attribute: that information is both visible and has a counterpart in
the language.

#### Summary

- The `data-` prefix is reserved by the language; attributes written
  with this prefix do not collide with the language's features and
  have no defined behavior.
- The attribute name gets written lowercase and hyphenated; the
  program-side key drops the prefix and capitalizes the letter after
  each hyphen.
- Values are always strings; they get converted for numeric
  comparisons, and in boolean information the string `"false"` counts
  as true.
- Content that needs to be visible in the document, information with
  a counterpart in the language, and large data do not get written to
  these attributes.

#### Next Step

At this point the document is structured, sectioned, and carries
media; but it is one-directional. The reader receives information and
cannot give any. The next topic opens this direction: what a form
actually sends to the server, how that body gets encoded, and which
checks run before submission starts. The first lesson begins with
submission itself.

## Forms

The body of a form submission that reaches the server, input types, labeling, constraint validation, file upload, and accessible error reporting.

### Form Structure

Source: https://academia.sh/en/courses/web-fundamentals-and-html/form-structure

The entry list a submission produces, the effect the submission method has on the url and the body, the reasoning behind choosing a method, and grouping fields.

The document is now structured, sectioned, and carries media — but it is still one-way. The
reader receives information, but cannot give any back. This topic opens that direction.

A form is the mechanism that turns values taken from the user into a request. The HTTP
Request and Response lesson in the How the Internet Works course defined the parts of a
request — the request line, the headers, the body. This lesson looks at how the values that
feed those parts are collected in the document, and in what shape they are written.

#### The Parts of a Form

The `form` element is a unit of submission. Two attributes determine where and how the
submission happens.

`action` is the url the request goes to. If it is not written, the document's own url is
used.

`method` is the HTTP method: `get` or `post`. If it is not written, `get` is assumed.

Of the fields inside a form, only the ones **with a name** are submitted. The `name`
attribute is what enters a field into the submission; a field with no name still appears on
screen and can have a value typed into it, but it never reaches the server.

#### The Entry List

When submission starts, the browser walks the fields inside the form and builds an **entry
list** of name-value pairs. This list is ordered and follows the fields' order in the
document; the same name can appear more than once.

Some fields never enter the list: ones with no name, ones that are disabled, and unchecked
checkboxes. This last rule is easy to miss — an unchecked checkbox is not submitted with a
"false" value, it is not submitted at all.

#### The Method Determines the Body

Once the entry list is built, where it gets written depends on the method. The script below
opens a local server, makes both submissions, and prints what the server sees.

```js
// submission.mjs — how get and post submissions look on the server
import { createServer } from "node:http";

// The name-value pairs the form produces (the entry list).
const entryList = [
  ["station", "north-slope"],
  ["measurement", "temperature"],
  ["value", "-4.2"],
  ["note", "windy & clear — no rain"],
];

const server = createServer((request, response) => {
  const chunks = [];
  request.on("data", (chunk) => chunks.push(chunk));
  request.on("end", () => {
    const body = Buffer.concat(chunks);
    console.log("request line :", request.method, request.url);
    console.log("content-type :", request.headers["content-type"] ?? "(none)");
    console.log("body         :", body.length ? JSON.stringify(body.toString()) : "(empty)");
    console.log("---");
    response.writeHead(204).end();
  });
});

server.listen(0, "127.0.0.1", async () => {
  const base = "http://127.0.0.1:" + server.address().port;
  const query = new URLSearchParams(entryList).toString();

  // method="get": the action url's query part is erased, replaced with the entry list.
  const url = new URL("/record?old=value", base);
  url.search = query;
  await fetch(url);

  // method="post", default enctype: the entry list is written to the body.
  await fetch(new URL("/record?old=value", base), {
    method: "POST",
    headers: { "content-type": "application/x-www-form-urlencoded" },
    body: query,
  });

  server.close();
});
```

```
request line : GET /record?station=north-slope&measurement=temperature&value=-4.2&note=windy+%26+clear+%E2%80%94+no+rain
content-type : (none)
body         : (empty)
---
request line : POST /record?old=value
content-type : application/x-www-form-urlencoded
body         : "station=north-slope&measurement=temperature&value=-4.2&note=windy+%26+clear+%E2%80%94+no+rain"
---
```

The output says four things.

**The `get` method sends no body.** The list is written into the url's query part. As a
result, the values show up in the browser's history, in bookmarks, and in server logs.

**The action url's existing query is erased.** The `old=value` pair from the request line is
gone. Writing a query onto an `action` url does nothing on a form submitted with `get`; it has
to be carried as a hidden field instead.

**The `post` method writes the list to the body** and keeps the url's query intact.

**The encoding is the same either way.** In both cases the list becomes `name=value` pairs
joined with `&`; spaces become `+`, non-ASCII characters and `&` get percent-encoded. The
separator inside `windy & clear — no rain` became `%26`, and the em dash became `%E2%80%94` —
the same percent-encoding as in the Links lesson. This format is called
`application/x-www-form-urlencoded`, announced in the `Content-Type` header on a `post`
submission.

#### Choosing a Method

The choice is not a habit, it follows from the nature of the request. The two properties
defined in the HTTP Request and Response lesson apply directly here: a **safe** method makes no
change on the server, an **idempotent** method leaves the same state no matter how many times
it runs.

`get` is safe. It is used only for submissions that make no change on the server: a search, a
filter, a query. That the result can be shared as a url and bookmarked is a gain.

`post` is not safe. Every submission that changes something on the server uses this method:
adding a record, sending a notification. The values do not show up in the url, and the browser
warns before repeating the submission.

Submitting a record with `get` means that sharing the link, or reloading the url, redoes the
operation. This distinction is not a privacy measure — a `post` body is unencrypted too;
protection comes from the transport layer described in the Role of HTTPS lesson.

#### Grouping Fields

The `fieldset` element groups fields that belong together, and the `legend` element gives the
group a name, written as the group's first child.

```html
<fieldset>
  <legend>Measurement source</legend>
  <p><label><input type="radio" name="source" value="automatic" checked> Automatic</label></p>
  <p><label><input type="radio" name="source" value="manual"> Entered manually</label></p>
</fieldset>
```

Grouping is not optional for radio buttons. The individual labels say only "Automatic" and
"Entered manually" — it is the `legend` text that says what the group is a choice of. A screen
reader announces the group name along with each option.

The `disabled` attribute written on `fieldset` disables the fields inside it — fields that sit inside the
group's `legend` element are the exception — and disabled fields do not enter the entry list; in a
multi-step form, this is how a whole section is shut off at once.

#### The Submit Button

The `button` element's `type` attribute takes three values: `submit` submits the form, `reset`
returns the fields to their initial values, `button` does nothing. A `type` attribute left
unwritten inside a form counts as `submit` — an ordinary button placed inside a form submits it
without anyone intending that.

The `formaction` and `formmethod` attributes override the form's own settings for the
submission made with that button; this is how the same form is submitted to two different urls.

#### Fields Outside the Form

Which form a field belongs to usually follows from nesting: every field sitting inside a
`form` element belongs to it. Forms cannot be nested, so this relation is single-valued.

Nesting does not always give the wanted layout — for example, fields inside a table that
belongs to a form outside the table. In this case, the `form` attribute written on the field
announces the id of the form it belongs to.

```html
<form id="correction" action="/measurement/correction" method="post"></form>

<table>
  <tr>
    <th scope="row">Monday</th>
    <td><input form="correction" name="mon" type="number" value="-4"></td>
  </tr>
</table>

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

A submit button can use this attribute too; the form itself stays empty and carries only the
submission settings.

The `autocomplete` attribute controls whether the browser suggests values entered before. When written at
the field level, it also announces the kind of value expected; this announcement is one of the mechanisms
that make a form easier to fill for users with a cognitive or motor difficulty; it works together with
clear labels and instructions. It is turned off only for fields that should not be stored on a shared
machine.

#### In the Station Document

```html
<section aria-labelledby="notice">
  <h2 id="notice">Report a Measurement Correction</h2>

  <form action="/measurement/correction" method="post">
    <fieldset>
      <legend>Measurement information</legend>
      <p><label for="station">Station code</label>
         <input id="station" name="station" type="text" value="north-slope"></p>
      <p><label for="value">Measured value (°C)</label>
         <input id="value" name="value" type="number"></p>
    </fieldset>

    <p><button type="submit">Submit correction</button></p>
  </form>
</section>
```

`post` is chosen as the method because the submission creates a record on the server. The
fields are gathered into a group, and the group is given a name.

#### Summary

- A form builds an entry list from its fields; only fields that have a name, are not
  disabled, and are checked enter this list.
- With `get`, the list is written into the url's query part and the action url's existing
  query is erased; with `post` it is written to the body and the url's query is kept.
- The default encoding is the same in both methods: `name=value` pairs, space as `+`, special
  characters percent-encoded.
- The choice of method follows from the safe and idempotent properties; a submission that
  changes something on the server is made with `post`.
- `fieldset` and `legend` are mandatory for option groups: only the group name says what the
  group is a choice of.

#### Next Step

This lesson built a form with a single kind of field. But the data to be collected is not
always free text: a number, a date, a time, a choice, a multiple choice, a file. The next
lesson looks at input types, and shows how much of the type information survives submission —
how all of them reach the server — through a real body.

### Input Types

Source: https://academia.sh/en/courses/web-fundamentals-and-html/input-types

The distinction between text, number, date, choice, and file fields; how much type information survives submission, and the shape values take when they reach the server.

The previous lesson built a form with a single kind of field. But the data to be collected is
not always free text: a number, a date, a time, a single choice, a multiple choice, long text,
a file. The `input` element's `type` attribute makes this distinction.

Choosing a type affects three things at once: the interface the user sees, the validation the
browser applies, and the shape the value takes on submission. This lesson focuses on the
third, because the first two are only understood correctly once the third is known.

#### Three Effects of a Type

Once a type is declared, the browser offers a matching interface: increment buttons on a
number field, a calendar on a date field, a picker on a color field. These interfaces vary by
browser and device; on narrow screens, the type declaration also determines the keyboard
layout — digit keys for a number, a layout including the `@` character for email.

A type also brings a constraint: submission is blocked when an invalid value is entered. This
behavior is the subject of the next two lessons.

The third effect is the submission format, and it is defined.

#### How Values Reach the Server

```js
// input-values.mjs — the values different types produce and how they look on the server
import { createServer } from "node:http";

// An unchecked checkbox and a disabled field never enter the entry list at all.
const entryList = [
  ["name", "North Slope"],           // type="text"
  ["temperature", "-4.2"],           // type="number"
  ["measurementDate", "2024-03-12"], // type="date"
  ["measurementTime", "07:30"],      // type="time"
  ["threshold", "18"],               // type="range"
  ["verified", "on"],                // checked type="checkbox", value not written
  ["source", "automatic"],           // selected type="radio"
  ["tag", "humidity"],               // <select> selected option
  ["tag", "wind"],                   // <select multiple> second selected option
  ["note", "first line\r\nsecond line"], // <textarea>: line endings become CRLF
];

const server = createServer((request, response) => {
  const chunks = [];
  request.on("data", (chunk) => chunks.push(chunk));
  request.on("end", () => {
    const body = Buffer.concat(chunks).toString();
    console.log("raw body:");
    console.log(body);
    console.log("--- decoded pairs on the server ---");
    for (const [key, val] of new URLSearchParams(body)) {
      console.log(key.padEnd(16), JSON.stringify(val), "(" + typeof val + ")");
    }
    response.writeHead(204).end();
  });
});

server.listen(0, "127.0.0.1", async () => {
  await fetch("http://127.0.0.1:" + server.address().port + "/record", {
    method: "POST",
    headers: { "content-type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams(entryList).toString(),
  });
  server.close();
});
```

```
raw body:
name=North+Slope&temperature=-4.2&measurementDate=2024-03-12&measurementTime=07%3A30&threshold=18&verified=on&source=automatic&tag=humidity&tag=wind&note=first+line%0D%0Asecond+line
--- decoded pairs on the server ---
name             "North Slope" (string)
temperature      "-4.2" (string)
measurementDate  "2024-03-12" (string)
measurementTime  "07:30" (string)
threshold        "18" (string)
verified         "on" (string)
source           "automatic" (string)
tag              "humidity" (string)
tag              "wind" (string)
note             "first line\r\nsecond line" (string)
```

The last column of the output is the lesson's most important result: **every value is a
string.** Type information is not carried in submission. The server does not know that the
`temperature` field is a number field; it receives the string `"-4.2"`, and converting it to a
number is its own job.

A direct consequence of this is that a type declaration is not a server guarantee. The topic
is detailed in the fourth lesson.

Three more details are visible.

**The date and time format is defined.** Even though the interface the user sees uses a local
format, the value sent has the shape `YYYY-MM-DD` and `HH:MM`. The server can parse the value
without knowing which country the user is in.

**The same name can appear more than once.** The `tag` field was sent twice — the two selected
options of a multiple-selection list. On the server side, this field needs to be read as a
list; parsing that only takes the first value silently loses data.

**Line endings in a long-text field become CRLF.** `%0D%0A` appears in the body. Whatever
operating system the user is on, the value sent is the same.

#### The Set of Types

| Type | Data collected | Value sent |
|---|---|---|
| `text` | single-line text | the text entered |
| `search` | search term | the text entered |
| `email` | email address | the text entered |
| `url` | url | the text entered |
| `tel` | phone number | the text entered, format not checked |
| `password` | masked text | the text entered, unencrypted |
| `number` | number | period as the decimal separator |
| `range` | number from a range | number; the field does not display the value |
| `date`, `time`, `month`, `week` | date and time | in a defined format |
| `checkbox` | yes/no or multiple choice | `value` or `on`; nothing at all if unchecked |
| `radio` | single choice from a group | the `value` of the selected one |
| `file` | file | requires a separate encoding |
| `hidden` | invisible carried value | the `value` |

The `password` type only masks the value on screen. The body goes out unencrypted; protection
comes from the transport layer described in the Role of HTTPS lesson.

The `range` type does not show the selected value to the user, and is unsuitable when a value
needs to be entered precisely; it suits an imprecise setting.

The `hidden` type is not a privacy mechanism: its value is visible in the source text and can
be changed. It exists only to carry a piece of information back from the server.

#### Separate Elements

Three input types are not written with the `input` element.

`textarea` is multi-line text. Its initial value is given not through a `value` attribute but
through the element's content.

`select` is a choice from a list; `option` elements carry the choices, and `optgroup` carries
groups of choices. The `multiple` attribute allows a multiple choice, and produces the
repeated name seen in the output above.

```html
<p><label for="tag">Tags</label>
   <select id="tag" name="tag" multiple size="4">
     <optgroup label="Atmosphere">
       <option value="humidity" selected>Relative humidity</option>
       <option value="wind" selected>Wind</option>
     </optgroup>
     <optgroup label="Surface">
       <option value="soil">Soil temperature</option>
     </optgroup>
   </select></p>

<p><label for="note">Description</label>
   <textarea id="note" name="note" rows="4">first line
second line</textarea></p>
```

If an option's `value` attribute is not written, the value sent is the option's text. `value`
is written so that the sent value does not change when the visible text changes.

The `output` element carries the result of a computation and takes no part in submission; the
behavior layer is what does the computing.

#### Suggestion List

A field may need to accept free text while also suggesting ready-made options. The `datalist`
element combines these two behaviors: it carries options without restricting the field to
them.

```html
<p><label for="sensor">Sensor code</label>
   <input id="sensor" name="sensor" type="text" list="sensor-list">
   <datalist id="sensor-list">
     <option value="HUM-01">
     <option value="TMP-02">
     <option value="WND-03">
   </datalist></p>
```

The field's `list` attribute names the list's id. The user can pick a value from the list or
type their own; the value sent is the field's text either way. This is the distinction from
`select`: `select` restricts the choices, `datalist` only suggests them.

Whether the list appears and how it is presented depends on the browser. For this reason,
`datalist` content is written as a convenience, not a constraint; a field that genuinely needs
to be restricted uses `select` or a validation rule instead.

#### In the Station Document

```html
<fieldset>
  <legend>Measurement information</legend>
  <p><label for="date">Measurement date</label>
     <input id="date" name="date" type="date"></p>
  <p><label for="time">Measurement time</label>
     <input id="time" name="time" type="time" step="600"></p>
  <p><label for="value">Measured value (°C)</label>
     <input id="value" name="value" type="number" step="0.1"></p>
  <input type="hidden" name="station" value="north-slope">
</fieldset>
```

The `step="600"` value on the time field is the markup equivalent of a ten-minute measurement
interval: six hundred in seconds. The hidden field carries the station code, and the user does
not need to enter it.

#### Summary

- The choice of type affects the interface, the validation, and the submission format
  together.
- Apart from the file field, every value sent is a string; type information is not carried to the server,
  and converting it is the server's job.
- Date and time values are sent in a defined format; the local format the user sees is not
  reflected in submission.
- An unchecked checkbox is never sent at all; a multiple choice produces the same name more
  than once.
- The `password` and `hidden` types provide no privacy: the first only masks on screen, the
  second is visible in the source text.

#### Next Step

The fields are declared, but what each one wants is understood only from the text next to it.
Until that text is declared as bound to the field in the document, the field is nameless:
clicking the label with a mouse does not move focus to the field, and a screen reader
announces the field as "editable text." The next lesson builds that bond, and makes the cases
where the bond is missing visible with an audit.

### Label-Field Relationship

Source: https://academia.sh/en/courses/web-fundamentals-and-html/label-field-relationship

Explicit and implicit binding of a label to a field, the distinction between the id and name attributes, auditing cases where the bond is missing, and the limit of the placeholder.

The fields are declared, but what each one wants is understood only from the text next to it.
Until this text is declared in the document as bound to the field, the field is **nameless**.

A nameless field has three concrete consequences. Clicking the label does not move focus to
the field — a convenience that, for small checkboxes, multiplies the clickable area several
times over. A screen reader announces the field only by its type: "editable text." And a
voice-control user cannot refer to the field by name.

#### Two Ways to Bind

**Explicit bond**: the `label` element's `for` attribute names the field's `id` value.

```html
<p><label for="station">Station code</label>
   <input id="station" name="station_code" type="text"></p>
```

**Implicit bond**: the `label` element wraps the field; `for` and `id` are not needed.

```html
<p><label>Temperature <input name="temperature" type="number"></label></p>
```

Both ways are valid. The explicit bond allows the label and the field to sit in different
places in the document — a label in a table's header cell bound to a field in a body cell can
only be done this way. The implicit bond is shorter and needs no id, but requires the wrapped
field to stay inside the label.

A field can have more than one label; its accessible name is made up of all of their text. A
label, however, can have only one field.

#### Id and Name Are Separate Things

The two attributes are often confused, and the confusion produces a silent flaw.

`id` is the element's identity **within the document**. It must be unique in the document.
Label binding, url fragments, and declarations like `aria-describedby` use it.

`name` is the field's name **in submission**. It does not need to be unique in the document —
radio button groups share the same name. This is the name the server sees.

The two can carry the same value, but they are not the same thing. The script below scans a
form and finds each field's label source.

```js
// label-audit.mjs — auditing the label-field bond
const form = `
<form action="/record" method="post">
  <p><label for="station">Station code</label>
     <input id="station" name="station_code" type="text"></p>
  <p><label>Temperature <input name="temperature" type="number"></label></p>
  <p><label for="humidity">Relative humidity</label>
     <input id="relative-humidity" name="humidity" type="number"></p>
  <p><input name="note" type="text" placeholder="Description"></p>
  <p><input name="hidden" type="hidden" value="north-slope"></p>
</form>`;

const explicitBonds = new Map();
for (const m of form.matchAll(/<label for="([^"]+)">([^<]*)<\/label>/g)) {
  explicitBonds.set(m[1], m[2].trim());
}
const implicitBonds = new Map();
for (const m of form.matchAll(/<label>([^<]*)<input([^>]*)>[^<]*<\/label>/g)) {
  const id = m[2].match(/id="([^"]*)"/)?.[1] ?? null;
  const name = m[2].match(/name="([^"]*)"/)?.[1] ?? null;
  implicitBonds.set(id ?? "name:" + name, m[1].trim());
}

console.log("field (name)     id                label source     accessible name");
for (const m of form.matchAll(/<input([^>]*)>/g)) {
  const attrs = m[1];
  const name = attrs.match(/name="([^"]*)"/)?.[1] ?? "";
  const id = attrs.match(/id="([^"]*)"/)?.[1] ?? "";
  const type = attrs.match(/type="([^"]*)"/)?.[1] ?? "text";
  if (type === "hidden") {
    console.log(name.padEnd(18) + id.padEnd(18) + "not needed".padEnd(17) + "(hidden field)");
    continue;
  }
  let source = "NONE";
  let labelText = "";
  if (id && explicitBonds.has(id)) { source = "for/id"; labelText = explicitBonds.get(id); }
  else if (implicitBonds.has(id) || implicitBonds.has("name:" + name)) {
    source = "wrapping label";
    labelText = implicitBonds.get(id) ?? implicitBonds.get("name:" + name);
  }
  console.log(name.padEnd(18) + (id || "—").padEnd(18) + source.padEnd(17) + (labelText || "—"));
}

console.log("--- for values that fall through ---");
const ids = new Set([...form.matchAll(/id="([^"]*)"/g)].map((m) => m[1]));
for (const target of explicitBonds.keys()) {
  if (!ids.has(target)) console.log('for="' + target + '" -> no such id');
}
```

```
field (name)     id                label source     accessible name
station_code      station           for/id           Station code
temperature       —                 wrapping label   Temperature
humidity          relative-humidity NONE             —
note              —                 NONE             —
hidden                              not needed       (hidden field)
--- for values that fall through ---
for="humidity" -> no such id
```

The third row is the typical shape of an id-name mix-up. The label has `for="humidity"`
written on it, and the field's `name` value is also `humidity`; but its `id` value is
`relative-humidity`. Since the bond is built through `id`, it is never built at all. Nothing
looks wrong in the document: the label text stays put, the field works, the form submits.
Only the name is lost.

The first row shows the opposite: `id` and `name` can carry different values, and that is not
a problem. The bond is built through `id`, and `name` is used in submission.

The fourth row is a field with no label at all — a `placeholder` stands in its place instead.

The fifth row shows that hidden fields need no label: they are never presented to the user.

#### A Placeholder Is Not a Label

The `placeholder` attribute is text that appears inside the field and disappears once typing
begins. It cannot substitute for a label, and there is more than one reason.

The text disappears the moment the field is filled; when the user wants to check what was
asked, there is nowhere left to look. If the form is long, or the user paused and came back,
this is a concrete loss.

Placeholder text is shown at low contrast in browser defaults; it is hard to read for users
with limited vision, and raising the contrast makes it get mistaken for an entered value.

In some screen reader and browser combinations, the placeholder is announced as the
accessible name; in others it is not — the behavior is inconsistent.

The right use of a placeholder is to give a **format example** in addition to the label: while
the label says "Measurement time," the placeholder can show `07:30`.

#### What Label Text Should Say

The label says what the field **wants**, not how to fill it in. If "Name" is enough, "Type
your name here" is not written: a screen reader re-announces this text on every focus, and
long labels make the form tiring.

Extra marks added to label text are announced too. Marking required fields with an asterisk is
common; what the asterisk means must be explained at the top of the form, and the requirement
must also be declared through the field's own attribute. A visual mark is not a declaration.

Unit information can be carried in the label — as in "Measured value (°C)" — or left to the
description. If the unit is necessary to interpret the value, it is preferred in the label;
some modes skip the description, none ever skip the name.

Whether the label is written before or after the field can look like a presentation choice,
but source order determines reading order. For checkboxes and radio buttons the label is
written after the field, for other fields before it; this makes sure the state of the control
is announced in order with what it is for.

#### Description

Help text about a field, unit information, or a format rule is bound with
`aria-describedby`. The label gives a name; the description gives extra information and is
announced after the name on a screen reader.

```html
<p>
  <label for="value">Measured value</label>
  <input id="value" name="value" type="number" step="0.1"
         aria-describedby="value-help">
  <span id="value-help">Degrees Celsius, period as the decimal separator.</span>
</p>
```

The attribute can take more than one id separated by spaces; the texts are joined in order.
This will be used in the next lesson to bind an error message to a field.

#### Summary

- A label is bound to a field explicitly with `for`/`id` or implicitly by wrapping; if no bond
  is built, the field stays nameless and clicking, announcing, and voice-control behavior all
  break.
- `id` is the identity within the document and must be unique; `name` is the name in
  submission and can repeat. When the two are confused, the bond silently fails to form.
- A field can have more than one label; a label can have only one field.
- `placeholder` does not substitute for a label: it disappears once filled, is shown at low
  contrast, and is not consistently announced as the name.
- Help text is bound with `aria-describedby` and announced after the name.

#### Next Step

The fields are named; next comes whether the value entered is accepted. Before submission, the
browser tests the constraints declared, and stops submission if any field is invalid. The next
lesson looks at these constraints, the validity states they produce, and why the same check
needs to be redone on the server.

### Built-in Validation

Source: https://academia.sh/en/courses/web-fundamentals-and-html/built-in-validation

The validity states constraint attributes produce, submission being stopped, and why the same rules need to be reapplied on the server.

The fields are named; next comes whether the value entered is accepted.

Before submission begins, the browser tests the fields inside the form. If a field violates a
declared constraint, submission is stopped and a message is shown to the user. This mechanism
is called **constraint validation**, and it is declared entirely in markup; it needs no script.

This lesson takes up both sides at once: the rules the browser applies, and why the same rules
need to be reapplied on the server.

#### Constraint Attributes

Constraints are written on fields as attributes, and each is meaningful for certain types.

| Attribute | Constraint |
|---|---|
| `required` | The value cannot be empty |
| `min`, `max` | Lower and upper bound on numeric and date fields |
| `step` | The increments the value is allowed to move in |
| `minlength`, `maxlength` | Lower and upper bound on text length |
| `pattern` | The regular expression the text must match |
| `type` | The type's own format rule (`email`, `url`) |

The `pattern` value is a regular expression and uses the concepts defined in the Regular
Expressions lesson in the Shell Programming course. There is one distinction: the pattern
applies to the **entire** value; anchors at the start and end are not needed, and writing them
changes nothing.

#### Validity States

When a constraint is violated, the field acquires a **validity state**. The names of the
states are defined, and more than one can be true on a field at the same time.

```js
// validation.mjs — the model of constraint validation rules
const FIELDS = {
  station: { type: "text", required: true, maxlength: 20, pattern: "[a-z-]+" },
  email: { type: "email", required: true },
  temperature: { type: "number", required: true, min: -60, max: 60, step: 0.1 },
  date: { type: "date", required: true },
};

const EMAIL = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
const DATE = /^\d{4}-\d{2}-\d{2}$/;

function validate(name, value) {
  const rule = FIELDS[name];
  const states = [];
  if (rule.required && value === "") states.push("valueMissing");
  if (value === "") return states;
  if (rule.type === "email" && !EMAIL.test(value)) states.push("typeMismatch");
  if (rule.type === "date" && !DATE.test(value)) states.push("typeMismatch");
  if (rule.maxlength !== undefined && [...value].length > rule.maxlength) states.push("tooLong");
  if (rule.pattern && !new RegExp("^(?:" + rule.pattern + ")$", "u").test(value)) {
    states.push("patternMismatch");
  }
  if (rule.type === "number") {
    const num = Number(value);
    if (value.trim() === "" || Number.isNaN(num)) return [...states, "badInput"];
    if (rule.min !== undefined && num < rule.min) states.push("rangeUnderflow");
    if (rule.max !== undefined && num > rule.max) states.push("rangeOverflow");
    if (rule.step) {
      const base = rule.min ?? 0;
      const steps = Math.round(((num - base) / rule.step) * 1e6) / 1e6;
      if (Math.abs(steps - Math.round(steps)) > 1e-9) states.push("stepMismatch");
    }
  }
  return states;
}

const trials = [
  ["station", "north-slope"],
  ["station", ""],
  ["station", "North Slope"],
  ["station", "a-very-long-station-name"],
  ["email", "observer@example.test"],
  ["email", "observer(at)example"],
  ["temperature", "-4.2"],
  ["temperature", "-4.25"],
  ["temperature", "95"],
  ["temperature", "four"],
  ["date", "2024-03-12"],
  ["date", "12.03.2024"],
];

for (const [name, value] of trials) {
  const states = validate(name, value);
  console.log(
    name.padEnd(12) + JSON.stringify(value).padEnd(28)
    + (states.length === 0 ? "valid" : states.join(", ")),
  );
}
```

```
station     "north-slope"               valid
station     ""                          valueMissing
station     "North Slope"               patternMismatch
station     "a-very-long-station-name"  tooLong
email       "observer@example.test"     valid
email       "observer(at)example"       typeMismatch
temperature "-4.2"                      valid
temperature "-4.25"                     stepMismatch
temperature "95"                        rangeOverflow
temperature "four"                      badInput
date        "2024-03-12"                valid
date        "12.03.2024"                typeMismatch
```

Four rows call for explanation.

`stepMismatch` shows how the step rule works. A `step="0.1"` declaration does not say "any decimal is
accepted"; it requires the value to fall on step multiples starting from a **base value**. The base is
`min` if it is written — here `-60` — otherwise the field's initial value, and zero if that is missing
too. `-4.25` does not fall on this grid.

`badInput` is a separate state from the others: the field **could not even produce a value**.
When non-numeric text is entered into a number field, the field's value becomes the empty
string; even though the user typed something, the field appears empty. This state never
reaches the server.

The `tooLong` state is rarely seen in the browser, because `maxlength` also blocks typing past
the limit — the user cannot enter more than the bound. The state only arises when the user
pastes a value, or when the behavior layer writes the value in afterward; an initial value
given through the `value` attribute and never edited does not produce this state. This is an
example of the constraint acting not only before submission but during input too.

The last row shows the date field's format rule: a local written form is not accepted, the
value the field produces is in a defined format.

#### Submission Is Stopped

If a field is invalid, submission does not happen, and the browser focuses the first invalid
field and shows a message. The message's text is produced in the browser's own language and
cannot be changed from the document; its style and duration also depend on the browser.

This behavior has two limits. The message shows for only one field at a time; if a ten-field
form has nine errors, the user discovers them one by one. The message can disappear quickly,
and the user cannot bring it back.

Writing the `novalidate` attribute on the form turns this behavior off; submission happens
regardless of the constraints. The attribute makes sense in two cases: declaring the
constraints in the document while leaving the check to the server, or doing a custom check
without leaving the error presentation to the browser. The second is the next lesson's topic.

#### Client Validation Is Not a Guarantee

Every rule the browser applies is applied **on the client**, and the client is under the
user's control. Removing the constraints needs no special tool: submission can be made without
using the document at all. As shown in the Input Types lesson, all that reaches the server is
name-value strings; which field they came from, and which constraints were written on that
field, is not information the body carries.

The server below reapplies the same constraints and prints its reply to four different
bodies. Two of these bodies were sent by violating the constraints written in the document.

```js
// server-validation.mjs — the same constraints reapplied on the server
import { createServer } from "node:http";

const FIELDS = {
  station: { required: true, maxlength: 20, pattern: "[a-z-]+" },
  temperature: { required: true, numeric: true, min: -60, max: 60, step: 0.1 },
};

function validate(pairs) {
  const errors = [];
  for (const [name, rule] of Object.entries(FIELDS)) {
    const value = pairs.get(name) ?? "";
    if (rule.required && value === "") { errors.push([name, "valueMissing"]); continue; }
    if (rule.maxlength && [...value].length > rule.maxlength) errors.push([name, "tooLong"]);
    if (rule.pattern && !new RegExp("^(?:" + rule.pattern + ")$", "u").test(value)) {
      errors.push([name, "patternMismatch"]);
    }
    if (rule.numeric) {
      const num = Number(value);
      if (Number.isNaN(num)) { errors.push([name, "badInput"]); continue; }
      if (num < rule.min) errors.push([name, "rangeUnderflow"]);
      if (num > rule.max) errors.push([name, "rangeOverflow"]);
      const steps = (num - rule.min) / rule.step;
      if (Math.abs(steps - Math.round(steps)) > 1e-9) errors.push([name, "stepMismatch"]);
    }
  }
  // Undeclared fields: names that should not be in the entry list.
  for (const name of new Set(pairs.keys())) {
    if (!(name in FIELDS)) errors.push([name, "unknownField"]);
  }
  return errors;
}

const server = createServer((request, response) => {
  const chunks = [];
  request.on("data", (chunk) => chunks.push(chunk));
  request.on("end", () => {
    const pairs = new URLSearchParams(Buffer.concat(chunks).toString());
    const errors = validate(pairs);
    const status = errors.length === 0 ? 201 : 400;
    response.writeHead(status, { "content-type": "text/plain; charset=utf-8" });
    response.end(errors.map(([name, code]) => name + ": " + code).join("\n") || "record accepted");
  });
});

server.listen(0, "127.0.0.1", async () => {
  const url = "http://127.0.0.1:" + server.address().port + "/record";
  const bodies = [
    "station=north-slope&temperature=-4.2",
    "station=North%20Slope&temperature=95",
    "temperature=-4.25",
    "station=north-slope&temperature=-4.2&admin=true",
  ];
  for (const body of bodies) {
    const response = await fetch(url, {
      method: "POST",
      headers: { "content-type": "application/x-www-form-urlencoded" },
      body,
    });
    console.log("body   :", body);
    console.log("status :", response.status);
    console.log("reply  :", (await response.text()).replace(/\n/g, " | "));
    console.log("---");
  }
  server.close();
});
```

```
body   : station=north-slope&temperature=-4.2
status : 201
reply  : record accepted
---
body   : station=North%20Slope&temperature=95
status : 400
reply  : station: patternMismatch | temperature: rangeOverflow
---
body   : temperature=-4.25
status : 400
reply  : station: valueMissing | temperature: stepMismatch
---
body   : station=north-slope&temperature=-4.2&admin=true
status : 400
reply  : admin: unknownField
---
```

The third body never sends a field that is declared `required` in the document; the second
body violates two constraints at once. The browser would have blocked these submissions; when
they are made directly to the server, the only thing blocking them is the server's own check.

The fourth body shows a separate kind of flaw. A field that does not exist in the form is
sent, and the server rejects it. Rejecting unexpected fields instead of silently ignoring them
prevents privilege escalation in implementations that turn the body directly into a record.

The status code of the reply also carries information: by the classification in the HTTP
Request and Response lesson, `400` announces that the request was malformed, `201` that a
resource was created.

#### A Rule Kept in Two Places

The client and server checks state the same rule twice, and this looks like a repetition.
Their jobs are different.

Client validation is a **convenience**: it gives the user feedback without delay and keeps
wasted requests from reaching the server. Its being removable is not a problem, because it is
not there to protect anyone.

Server validation is a **rule**: it is the only place that ensures the data's consistency. It
applies regardless of what was written on the client.

The way to reduce the repetition is to generate the rules from a single definition: field
definitions live in one data structure, and both the markup and the server check are derived
from it. In the two scripts above, the `FIELDS` object is the counterpart of this definition.

#### In the Station Document

```html
<fieldset>
  <legend>Measurement information</legend>
  <p><label for="station">Station code</label>
     <input id="station" name="station" type="text" value="north-slope"
            required maxlength="20" pattern="[a-z-]+"
            aria-describedby="station-help">
     <span id="station-help">Lowercase letters and hyphens only.</span></p>

  <p><label for="value">Measured value (°C)</label>
     <input id="value" name="value" type="number"
            required min="-60" max="60" step="0.1"></p>
</fieldset>
```

The constraints both help the user and announce the contract the document carries: what
values this field accepts is written in the document.

#### Summary

- Constraints are declared as attributes, and when violated produce validity states with
  defined names; more than one state can be true on a field at the same time.
- The `step` rule defines a grid starting from a base value; the base is `min` if it is
  written.
- `badInput` is the state where the field could not produce a value at all and never reaches
  the server; `tooLong` is rare because `maxlength` also blocks typing.
- The browser stops submission on an invalid field; `novalidate` turns this behavior off.
- Client validation is a convenience and can be removed; the only place ensuring the data's
  consistency is the server, and the same constraints are reapplied there.

#### Next Step

Validation was built on text and number fields. A file field does not fit these patterns: its
value is not a string, fitting it into the body requires a separate encoding, and a size limit
cannot be declared in the document. The next lesson looks at file upload and shows the
difference between the two submission encodings in the body, through real bytes.

### File Upload

Source: https://academia.sh/en/courses/web-fundamentals-and-html/file-upload

The value a file field produces, the multipart submission encoding's counterpart in the body, the accept type declaration, and where the size limit is enforced.

Validation was built on text and number fields. A file field does not fit these patterns. Its
value is not a string; it carries binary content, and has its own name and its own content
type.

The default encoding defined in the Form Structure lesson — joining `name=value` pairs with
`&` — cannot carry this content. On a form with a file field, the submission encoding
changes.

#### Three Attributes

```html
<form action="/measurement/upload" method="post" enctype="multipart/form-data">
  <p><label for="record">Raw record file</label>
     <input id="record" name="record" type="file" accept=".csv,text/csv" required></p>
  <p><button type="submit">Upload</button></p>
</form>
```

`enctype="multipart/form-data"` changes the submission encoding. If it is not written, the
file field sends only the **file name**; the content does not go. This is the most common
reason a form silently misbehaves: the form submits, the server gets a value, but what it gets
is a string.

The `accept` attribute announces which types should be surfaced in the file picker. Its value
can be a list of extensions, a list of content types, or a mix of both.

The `multiple` attribute allows more than one file to be selected; each file joins the entry
list as a separate entry under the same name.

#### The Shape of the Body

The script below opens a server, sends the same two fields with two different encodings, and
prints the raw body the server receives. To make line endings visible, the `\r\n` sequence is
printed explicitly.

```js
// file-upload.mjs — the two submission encodings' counterpart in the body
import { createServer } from "node:http";

// The boundary string is generated randomly on every submission; to keep the
// output reproducible it is replaced with a fixed placeholder before printing.
const server = createServer((request, response) => {
  const chunks = [];
  request.on("data", (chunk) => chunks.push(chunk));
  request.on("end", () => {
    const body = Buffer.concat(chunks);
    const contentType = request.headers["content-type"];
    const boundary = contentType.match(/boundary=(.+)$/)?.[1];
    const mask = (d) => (boundary ? d.split(boundary).join("BOUNDARY") : d);
    console.log("content-type  :", mask(contentType));
    console.log("body length   :", body.length, "bytes");
    console.log("--- raw body ---");
    console.log(mask(body.toString()).replace(/\r\n/g, "\\r\\n\n"));
    console.log("===");
    response.writeHead(204).end();
  });
});

server.listen(0, "127.0.0.1", async () => {
  const url = "http://127.0.0.1:" + server.address().port + "/upload";

  // enctype="multipart/form-data"
  const formData = new FormData();
  formData.append("station", "north-slope");
  formData.append("record", new Blob(["time,temperature\n07:00,-4.2\n"], { type: "text/csv" }), "measurement.csv");
  await fetch(url, { method: "POST", body: formData });

  // enctype="application/x-www-form-urlencoded" — the same two fields, without the file content
  await fetch(url, {
    method: "POST",
    headers: { "content-type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams([["station", "north-slope"], ["record", "measurement.csv"]]).toString(),
  });

  server.close();
});
```

```
content-type  : multipart/form-data; boundary=BOUNDARY
body length   : 304 bytes
--- raw body ---
--BOUNDARY\r\n
Content-Disposition: form-data; name="station"\r\n
\r\n
north-slope\r\n
--BOUNDARY\r\n
Content-Disposition: form-data; name="record"; filename="measurement.csv"\r\n
Content-Type: text/csv\r\n
\r\n
time,temperature
07:00,-4.2
\r\n
--BOUNDARY--\r\n

===
content-type  : application/x-www-form-urlencoded
body length   : 42 bytes
--- raw body ---
station=north-slope&record=measurement.csv
===
```

`BOUNDARY` in the output is a placeholder: the real boundary string is generated randomly on
every submission, and the script replaces it with a fixed piece of text before printing it. The
boundary's text is chosen by the sender; its only condition is that it must not occur anywhere
in the body. The body length here does not vary, since the generated boundary's length is
fixed.

The body's shape is this. A **boundary** string is announced in the `Content-Type` header. The
body is split into parts by this boundary; each part carries its own headers, an empty line,
and its content. The `Content-Disposition` header gives the field's name; file fields also
carry a `filename` parameter. A file part additionally has a `Content-Type` header announcing
the file's type. The body ends with the boundary followed by two hyphens.

Two basic differences are visible.

**Content is not escaped.** In multipart encoding, file content is carried as-is; no
percent-encoding is applied. The boundary string does the job of separation. A binary file can
be sent without being encoded.

**The overhead ratio is high.** For the same two fields, 304 bytes were used against 42 bytes.
Each part carries its own headers, and the boundary string is repeated in every part. This
cost shrinks proportionally as the file grows larger; but sending a form with no file field
using multipart encoding is unnecessary overhead.

The second part of the output shows the result of the wrong encoding: the `record` field
carries only the file name. The server receives a value, and there is no content.

#### An Accept Type Is Not a Constraint

The `accept` attribute filters the file picker window. The filter can be removed by the user,
and submission can be made without using the document at all; the conclusion from the Built-in
Validation lesson applies here too.

Whether a file is actually of the type it declares cannot be learned from the body's
`Content-Type` header — that header too is written by the sender. The server can verify the
type only by looking at the file's **content** — the identifying byte sequences at the start of
file formats exist for exactly this purpose.

Trusting the extension is not enough either. The uploaded file name is not used directly on the
server: a name from the user can contain a path separator, overwrite an existing file, or carry
an extension the server executes. The safe behavior is for the server to generate its own name
and store the original only as metadata.

#### The Field's Value Cannot Be Written From the Document

A file field differs from other fields in one more way: its value cannot be given from the
document. Even if a `value` attribute is written, it is ignored, because otherwise a document
could silently submit any file on the user's disk. The field's value is formed only by the
user's own choice.

The same reasoning also hides the file's **path**. What is opened up to the document is the
file's name, size, and type — not the directory it sits in. A user's directory structure
carries information about that user, and it is not given to the document.

One consequence is that a form cannot redisplay a file left over from a previous submission. In
a form returned due to a validation error, other fields' values can be rewritten into the
document; the file field stays empty and the user must select the file again. In multi-step
forms, this requires the file to be uploaded at an early step, held temporarily on the server,
and referred to by an id in later steps.

#### The Size Limit Cannot Be Declared in the Document

There is no attribute that limits file size. This can be called not a gap but a consistent
design: a limit declared in the document could not have been enforced anyway.

The limit is enforced in two places. The server can reject a request by looking at the
`Content-Length` header before reading the whole body, and can also enforce an upper bound
while reading; this prevents a single request from filling memory. The server software's own
configuration usually also carries a request body upper bound.

If early feedback to the user is wanted, the file size can be read in the behavior layer
before submission; this is a convenience, not a constraint. The expected limit is written as
text in the document — the user should not learn that the file they chose will be rejected
only after the upload finishes.

#### In the Station Document

```html
<fieldset>
  <legend>Raw record</legend>
  <p><label for="record">Measurement file</label>
     <input id="record" name="record" type="file"
            accept=".csv,text/csv" aria-describedby="record-help">
     <span id="record-help">Comma-separated value file, 5 MB maximum.</span></p>
</fieldset>
```

The form's `enctype` attribute must be changed the moment this field is added; if forgotten,
the form appears to work and the file content never arrives.

#### Summary

- A form with a file field writes `enctype="multipart/form-data"`; if not written, only the
  file name is sent and the content is lost.
- A multipart body is split into parts by a boundary string; each part carries its own headers
  and the content is transferred without being escaped.
- This encoding's overhead ratio is high; it is not used on forms with no file field.
- `accept` only filters the file picker; type checking is done on the server by looking at the
  file's content, and the uploaded name is not used directly.
- The size limit cannot be declared in the document; it is enforced on the server and
  announced to the user as text.

#### Next Step

At this point the form collects data, validates it, and can accept a file. One question
remains: what does the user see when submission is rejected? The browser's own message shows
only one field, and an error returned from the server arrives as a new document. The final
lesson looks at error reporting and focus management, and closes the course by building the
accessible version of this form.

### Form Accessibility

Source: https://academia.sh/en/courses/web-fundamentals-and-html/form-accessibility

Binding error messages to a field, building an error summary returned from the server, focus management, and the requirements of keyboard use.

At this point the form collects data, validates it, and can accept a file. One question
remains: what does the user see when submission is rejected?

The Built-in Validation lesson noted two limits of the browser's own message: it shows only
one field at a time, and can disappear quickly. An error returned from the server, on the
other hand, arrives as a new document, and the user has to work out which fields need fixing.

This lesson builds error reporting into the document. The structure built needs no script; it
is markup produced by the server, and so it works under every condition.

#### What an Error Report Must Meet

There are four requirements, and all four are met with markup.

**Findability.** The user must see where on the page they landed and how many errors there
are. A single red line in the middle of the page tells a user who is not looking at that line
nothing.

**Binding.** Each message must be bound to the field it belongs to through a declaration. Text
placed under a field is visually bound; it is not bound in the accessibility tree.

**Announceability.** When the field receives focus, the error message must be announced too.

**Reachability.** Reaching the message must not require guessing which field the error is in.

It should also be added that color alone is not a report: a red border tells a user who
cannot distinguish the color nothing. The state must also be reported with text.

#### The Error Summary Returned From the Server

The script below builds the document the server produces after validation. The input is the
field definitions and the error list; the output is the markup to be sent.

```js
// accessibility.mjs — the error summary produced on the server, and the field-error bonds
const FIELDS = [
  { id: "station", name: "station", label: "Station code", type: "text" },
  { id: "temperature", name: "temperature", label: "Temperature (°C)", type: "number" },
  { id: "email", name: "email", label: "Notification address", type: "email" },
];

const MESSAGES = {
  valueMissing: "This field is required.",
  patternMismatch: "Use lowercase letters and hyphens only.",
  rangeOverflow: "The value can be at most 60.",
  typeMismatch: "Enter a valid email address.",
};

function escapeHtml(text) {
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;");
}

function renderForm(values, errors) {
  const lines = [];
  if (errors.length > 0) {
    lines.push('<div role="alert" tabindex="-1" id="error-summary">');
    lines.push("  <h2>Form could not be submitted: " + errors.length + " fields need to be fixed</h2>");
    lines.push("  <ul>");
    for (const [fieldId, code] of errors) {
      const field = FIELDS.find((f) => f.id === fieldId);
      lines.push(
        '    <li><a href="#' + field.id + '">' + escapeHtml(field.label) + ": " + escapeHtml(MESSAGES[code]) + "</a></li>",
      );
    }
    lines.push("  </ul>");
    lines.push("</div>");
  }
  lines.push('<form action="/record" method="post" novalidate>');
  for (const field of FIELDS) {
    const error = errors.find(([id]) => id === field.id);
    lines.push('  <p><label for="' + field.id + '">' + escapeHtml(field.label) + "</label>");
    const attrs = [
      'id="' + field.id + '"',
      'name="' + field.name + '"',
      'type="' + field.type + '"',
      'value="' + escapeHtml(values[field.name] ?? "") + '"',
    ];
    if (error) {
      attrs.push('aria-invalid="true"');
      attrs.push('aria-describedby="' + field.id + '-error"');
    }
    lines.push("     <input " + attrs.join(" ") + ">");
    if (error) {
      lines.push(
        '     <span id="' + field.id + '-error" class="error">' + escapeHtml(MESSAGES[error[1]]) + "</span>",
      );
    }
    lines.push("  </p>");
  }
  lines.push('  <p><button type="submit">Submit record</button></p>');
  lines.push("</form>");
  return lines.join("\n");
}

const values = { station: "North Slope", temperature: "95", email: "observer@example.test" };
const errors = [["station", "patternMismatch"], ["temperature", "rangeOverflow"]];
console.log(renderForm(values, errors));
```

```
<div role="alert" tabindex="-1" id="error-summary">
  <h2>Form could not be submitted: 2 fields need to be fixed</h2>
  <ul>
    <li><a href="#station">Station code: Use lowercase letters and hyphens only.</a></li>
    <li><a href="#temperature">Temperature (°C): The value can be at most 60.</a></li>
  </ul>
</div>
<form action="/record" method="post" novalidate>
  <p><label for="station">Station code</label>
     <input id="station" name="station" type="text" value="North Slope" aria-invalid="true" aria-describedby="station-error">
     <span id="station-error" class="error">Use lowercase letters and hyphens only.</span>
  </p>
  <p><label for="temperature">Temperature (°C)</label>
     <input id="temperature" name="temperature" type="number" value="95" aria-invalid="true" aria-describedby="temperature-error">
     <span id="temperature-error" class="error">The value can be at most 60.</span>
  </p>
  <p><label for="email">Notification address</label>
     <input id="email" name="email" type="email" value="observer@example.test">
  </p>
  <p><button type="submit">Submit record</button></p>
</form>
```

The markup produced meets each of the four requirements.

**The summary sits before the form** and states with a heading how many fields need to be
fixed. Sitting at the top of the document ensures the user reaches it without extra
navigation; the `role="alert"` declaration marks the section as a region to be announced
immediately. Whether it is announced while the document loads depends on the implementation,
so the summary does not rely on the announcement alone: it is also findable by its position
and heading.

**Every error is a link**, and its target is the relevant field's id. The link text does not
just say "this field is required" — it also says which field it is; the "understandable when
taken out of context" criterion from the Links lesson applies here too.

**Fields carry `aria-invalid="true"`.** This is an invalidity declaration to the accessibility
tree, and carries information color does not.

**The message is bound to the field with `aria-describedby`.** The mechanism built in the
Label-Field Relationship lesson is used here for the error message: when the field receives
focus, the name is announced, then the type, then the description.

Note that entered values are **preserved**. Even invalid values are written back into the
fields; the user sees what they typed and corrects it. Returning the form emptied destroys the
user's work on long forms.

Escaping is also required: field values come from the user, and as noted in the Tags,
Attributes, and Entities lesson, writing `<`, `&`, and quote characters into the document
unescaped breaks the structure.

#### Focus Management

The summary carries a `tabindex="-1"` attribute so that it can be focused programmatically. A
value of negative one keeps the element out of the tab order but still lets it receive focus.

In a document returned from the server, focus starts at the top of the document and the
summary is already there; no extra step is needed. In checks made without reloading the page,
focus must be moved to the summary, or the user stays unaware of the newly added section.

When the link in the summary is clicked, focus goes to the target field. All that is needed
for this is that the field be focusable; input fields already are.

Values of `tabindex` greater than zero are not used. These values move the element to the
front of the tab order and break the order of the rest of the document; the order should come
from the document's source order. When focus order and visual order diverge, a keyboard user
jumps around the screen.

#### Reporting Without a Page Reload

If the check is done without reloading the page, a message that appears on screen needs to be
announced. A **live region** is a section whose content is announced by a screen reader when
it changes.

```html
<div role="alert" id="error-summary" tabindex="-1"></div>
```

A `role="alert"` declaration demands an immediate announcement and interrupts the user's
current reading; this suits error reports. For less urgent information — "submitting
record," for instance — a live region that announces without interrupting is used instead.

The live region must exist in the document **beforehand**. In some implementations, the
content of a live region added to the document afterward is not announced; an empty container
is placed beforehand and its content is filled in later.

#### Keyboard Use

A form being usable by keyboard is provided automatically as long as nothing is done in the
document to prevent it: the `input`, `select`, `textarea`, and `button` elements are
focusable, tab order is source order, and submission can be made with the enter key while in
one of the fields.

The places where this behavior breaks are places where the language's own elements are
replaced with something else. A button built by adding a click behavior to a `div` element is
not focusable, cannot be pressed by keyboard, and is not announced as a button by a screen
reader. The declarations needed to win these back — focusability, role, key handling — already
exist on the `button` element.

The focus indicator is not removed either. When the focused element has no visible mark, a
keyboard user cannot tell where in the document they are. The indicator can be changed in the
presentation layer; it cannot be removed.

#### The Finished Form

The final section of the document developed throughout the course:

```html
<section aria-labelledby="notice">
  <h2 id="notice">Report a Measurement Correction</h2>

  <div role="alert" id="error-summary" tabindex="-1"></div>

  <form action="/measurement/correction" method="post" enctype="multipart/form-data">
    <fieldset>
      <legend>Measurement information</legend>

      <p><label for="station">Station code</label>
         <input id="station" name="station" type="text" value="north-slope"
                required maxlength="20" pattern="[a-z-]+"
                aria-describedby="station-help">
         <span id="station-help">Lowercase letters and hyphens only.</span></p>

      <p><label for="date">Measurement date</label>
         <input id="date" name="date" type="date" required></p>

      <p><label for="value">Measured value (°C)</label>
         <input id="value" name="value" type="number"
                required min="-60" max="60" step="0.1"></p>
    </fieldset>

    <fieldset>
      <legend>Measurement source</legend>
      <p><label><input type="radio" name="source" value="automatic" checked> Automatic</label></p>
      <p><label><input type="radio" name="source" value="manual"> Entered manually</label></p>
    </fieldset>

    <fieldset>
      <legend>Raw record</legend>
      <p><label for="record">Measurement file</label>
         <input id="record" name="record" type="file"
                accept=".csv,text/csv" aria-describedby="record-help">
         <span id="record-help">Comma-separated value file, 5 MB maximum.</span></p>
    </fieldset>

    <p><button type="submit">Submit correction</button></p>
  </form>
</section>
```

Every field has a label, every group has a name, every constraint has a declaration, and every
description has a bond. There is not a single script in the document.

#### Summary

- An error report must be findable, bound to its field, announceable, and reachable; color
  alone is not a report.
- An error summary returned from the server sits before the form, states the error count, and
  presents each error as a link bound to its field.
- Fields announce invalidity with `aria-invalid` and the error message with
  `aria-describedby`; entered values are preserved and written escaped.
- `tabindex="-1"` makes an element focusable without putting it in the tab order; values
  greater than zero break the document's focus order.
- Live regions must exist in the document beforehand; the content of a region added afterward
  may not be announced.
- Keyboard use is provided automatically as long as the language's own elements are used; the
  focus indicator can be changed but not removed.

#### Course Wrap-Up

This course opened with a single question: what is the response body that comes back from the
server? The response is a document, and the document is turned from bytes into a tree of
nodes.

Four topics built this tree. **How the Web Works** traced the conversion from bytes to
characters, from characters to tokens, from tokens to a tree; it showed the parser's error
recovery rules and how resource loading holds up this chain. **Document Structure** built the
document's skeleton from the root structure to tables, and repeated that every tag is a
structural declaration, not a presentation choice. **Semantic Markup and Media** added a
top-level map to the document and placed images, audio, video, and embedded content in an
accessible way. **Forms** turned the one-way document into a two-way one, and showed through
real bodies how data coming from the user reaches the server.

The measurement-station document followed throughout the course grew from a two-line skeleton
into a whole page: sectioned, tabled, carrying media, and containing a validatable form. There
is not a single presentation decision in the document: nowhere is a color, a measurement, a
position declared.

This absence is deliberate. Of the three layers defined in the first lesson, only the first
has been built. The document is readable and navigable right now when opened in a browser —
because the browser carries default styles for every element. But that appearance is not the
document's decision, it is the browser's.

The next course, **Visual Presentation with CSS**, builds the second layer: the selectors that
decide which rule applies to which element, how conflicting rules are resolved, the box model,
and flow layout. This is the document that course will work on top of — and a well-built
structure is the precondition for good presentation: giving an element style first requires
that element to exist.

