# HTML essentials

> Write well-structured, semantic HTML pages with a sound heading outline and forms that validate and submit correctly.

Frontend Developer roadmap · The web platform · 16 min read · https://levelcode.org/roadmaps/frontend/html-essentials

HTML describes what each piece of a page *is*: a heading, a navigation menu, a list, a form field. Browsers, search engines, screen readers and your own CSS and JavaScript all rely on that description, so getting it right makes everything downstream easier. In this lesson you will build the skeleton every page needs, structure content with semantic elements and headings, and write forms that work before a single line of JavaScript.

## The skeleton of every page

Here is a complete, modern HTML document. Every line earns its place:

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Trail Shoes – Ridgeway Outdoors</title>
    <link rel="stylesheet" href="/styles.css">
    <script src="/app.js" defer></script>
  </head>
  <body>
    <h1>Trail shoes</h1>
    <p>Grippy, light and waterproof.</p>
  </body>
</html>
```

- `<!doctype html>` switches the browser into standards mode. Without it you get "quirks mode", which emulates bugs from the 1990s.
- `lang="en"` tells screen readers which voice to use and lets the browser offer translation.
- `<meta charset="utf-8">` must come early so characters like é and – display correctly.
- The `viewport` meta tag makes mobile browsers use the device width instead of pretending to be a desktop screen.
- `<title>` appears in the tab, bookmarks and search results. Put the specific page first and the site name last.
- `<head>` holds information *about* the page; `<body>` holds what the user sees.

### Elements, attributes and nesting

An **element** is usually an opening tag, content, and a closing tag: `<p>Hello</p>`. **Attributes** go in the opening tag and configure the element: `<a href="/about">`. Elements nest like boxes and must close in reverse order – `<p><strong>Hi</strong></p>`, never `<p><strong>Hi</p></strong>`.

**Void elements** have no content and no closing tag: `<img>`, `<input>`, `<br>`, `<meta>`, `<link>`, `<hr>`.

To show characters HTML treats specially, use **character references**: `&lt;` for `<`, `&gt;` for `>`, and `&amp;` for `&`.

## Semantic structure

You could build an entire page out of `<div>` and `<span>`, and it would look fine once styled. But a `<div>` means nothing. Semantic elements carry meaning that the browser exposes to assistive technology, search engines and browser features like reader mode.

| Element | Use it for |
| --- | --- |
| `<header>` | Introductory content: logo, site title, sometimes the main nav. |
| `<nav>` | A block of major navigation links. |
| `<main>` | The page's primary content. Use exactly one visible `<main>` per page. |
| `<article>` | Self-contained content that would make sense on its own: a blog post, a product card, a comment. |
| `<section>` | A thematic group of content, normally with its own heading. |
| `<aside>` | Content tangential to the main content: a sidebar, related links. |
| `<footer>` | Closing content: copyright, contact links. |

A typical page layout looks like this:

```html
<body>
  <header>
    <a href="/">Ridgeway Outdoors</a>
    <nav>
      <ul>
        <li><a href="/shoes">Shoes</a></li>
        <li><a href="/jackets">Jackets</a></li>
      </ul>
    </nav>
  </header>

  <main>
    <h1>Trail shoes</h1>
    <article>
      <h2>Ridge Runner 3</h2>
      <p>Our lightest waterproof shoe.</p>
    </article>
  </main>

  <footer>
    <p>&copy; 2026 Ridgeway Outdoors</p>
  </footer>
</body>
```

Screen reader users can jump straight between these regions (called **landmarks**), the same way sighted users glance at a page's layout.

### Text-level semantics

Inside those regions, pick elements for meaning, not appearance:

- `<p>` for paragraphs, `<ul>` for unordered lists, `<ol>` for ordered steps, each item in an `<li>`.
- `<strong>` for importance and `<em>` for stress emphasis. `<b>` and `<i>` only style text without adding meaning.
- `<figure>` with `<figcaption>` for an image, chart or code sample with a caption.
- `<time datetime="2026-03-14">14 March</time>` for machine-readable dates.
- `<code>` for inline code, `<blockquote>` for quotations.

## Headings and the document outline

Headings `<h1>` to `<h6>` form the **outline** of your page – a table of contents that screen reader users navigate by and search engines use to understand structure. The rules are simple:

1. Give the page one `<h1>` describing its main topic.
2. Nest levels logically: an `<h3>` belongs under an `<h2>`. Do not skip from `<h2>` to `<h4>`.
3. Choose the level by *structure*, never by font size. Size is CSS's job.

A product page might produce this outline:

```text
h1 Trail shoes
  h2 Ridge Runner 3
    h3 Specifications
    h3 Reviews
  h2 Summit Grip
    h3 Specifications
```

> [!NOTE]
> Older tutorials claim that each `<section>` restarts heading levels, so every section can use its own `<h1>`. That "outline algorithm" was never implemented by any browser and has been removed from the HTML standard. Only the heading levels you write count.

## Links, images and buttons

### Links

`<a href="…">` navigates somewhere. The `href` can be absolute (`https://example.com/help`), root-relative (`/help`), relative to the current page (`help.html`), a fragment on the same page (`#reviews`), or a scheme like `mailto:`. Link text should make sense on its own – "Download the size guide", not "click here".

`target="_blank"` opens a new tab. Modern browsers automatically apply `rel="noopener"` behaviour to it, but use it sparingly: unexpected new tabs disorient people.

### Images

```html
<img src="/img/ridge-runner.jpg"
     alt="Grey trail shoe with an orange sole"
     width="800" height="600">
```

`alt` text replaces the image when it cannot be seen: describe what the image communicates. For purely decorative images, write `alt=""` so screen readers skip them – omitting `alt` entirely makes some screen readers read out the file name. Setting `width` and `height` lets the browser reserve space before the image loads, so the page does not jump.

### Buttons versus links

If it **goes somewhere**, it is a link. If it **does something** – opens a menu, submits a form, adds to the basket – it is a `<button>`. A `<div>` with a click handler is neither: it cannot be focused with the keyboard or activated with Enter or Space without extra work.

## Forms

Forms are how users send data to a server, and HTML does a lot of the work for you.

```html
<form action="/signup" method="post">
  <label for="email">Email</label>
  <input id="email" name="email" type="email" autocomplete="email" required>

  <label for="password">Password</label>
  <input id="password" name="password" type="password"
         minlength="12" autocomplete="new-password" required>

  <fieldset>
    <legend>Plan</legend>
    <label><input type="radio" name="plan" value="free" checked> Free</label>
    <label><input type="radio" name="plan" value="pro"> Pro</label>
  </fieldset>

  <label>
    <input type="checkbox" name="newsletter" value="yes"> Send me the newsletter
  </label>

  <button type="submit">Create account</button>
</form>
```

### How the pieces fit

- **`action`** is the URL that receives the data; **`method`** is `get` or `post`.
- **`name`** is the key the field's value is sent under. A field without a `name` is not submitted at all.
- **`<label>`** connects text to a field, either with `for` matching the input's `id` or by wrapping the input. Clicking the label focuses the field, and screen readers announce the label when the field is focused. Placeholder text is not a substitute.
- **Radio buttons** in a group share one `name`; only the checked one's `value` is sent.
- **`<fieldset>` and `<legend>`** group related controls and give the group a name.
- **Checkboxes** send their `value` only when checked (the default value is `on`). Unchecked boxes send nothing.

With `method="get"`, the browser puts the data in the URL's query string, which suits searches and filters:

```text
/search?q=trail+shoes&size=42
```

With `method="post"`, the data goes in the request body, which suits anything that changes state or contains private data.

### Useful input types

| Type | What you get |
| --- | --- |
| `email`, `url` | Format validation and a suitable mobile keyboard |
| `tel` | A phone keypad (no format validation – formats vary worldwide) |
| `number` | Numeric input with `min`, `max` and `step` |
| `date` | A native date picker |
| `password` | Masked characters |
| `search` | A search field, often with a clear button |

For longer text use `<textarea name="message" rows="5"></textarea>`, and for a fixed list of options use `<select name="size">` with `<option value="42">42</option>` children.

### Built-in validation

Attributes like `required`, `minlength`, `maxlength`, `min`, `max`, `pattern` and `type="email"` make the browser block submission and show a message when a value is invalid:

```html
<input name="username" required pattern="[A-Za-z0-9]{3,16}"
       title="3–16 letters or numbers">
```

> [!WARNING]
> Client-side validation is a convenience for honest users, not security. Anyone can bypass it by editing the page or sending a request directly. The server must validate everything again.

## Common pitfalls

- **Buttons inside forms default to `type="submit"`.** A "Show password" button without `type="button"` will submit the form.
- **Skipping heading levels for styling** breaks the outline. Pick the right level and resize it with CSS.
- **Missing `name` attributes** – the form "submits" but the server receives nothing.
- **Duplicate `id` values** – `for` and `id` must be unique on the page, or labels attach to the wrong field.
- **Using `<br>` for spacing** – use separate elements and let CSS handle spacing.
- **Putting block elements inside a `<p>`**, such as a `<div>` or a list. A paragraph can only contain text-level content, so the browser silently closes the `<p>` early and builds a different structure than you wrote.

## Practice

Build a `contact.html` page for a small bakery, from scratch, in a single file. It needs:

1. A correct document skeleton with `lang`, `charset`, `viewport` and a descriptive `<title>`.
2. A site header with the bakery name and a `<nav>` of three links (Home, Menu, Contact).
3. A `<main>` with an `<h1>` and a short intro paragraph.
4. A contact form posting to `/contact` with: name (required), email (required, validated), a topic `<select>` (Order, Catering, Other), a message `<textarea>` (required, at least 20 characters), and a submit button.
5. A footer with an opening-hours list.

Open it in a browser and try submitting it empty, with a bad email, and with a 10-character message.

**Solution**

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Contact us – Crumb &amp; Co. Bakery</title>
  </head>
  <body>
    <header>
      <a href="/">Crumb &amp; Co.</a>
      <nav>
        <ul>
          <li><a href="/">Home</a></li>
          <li><a href="/menu">Menu</a></li>
          <li><a href="/contact" aria-current="page">Contact</a></li>
        </ul>
      </nav>
    </header>

    <main>
      <h1>Contact us</h1>
      <p>Questions about an order or catering? We reply within one working day.</p>

      <form action="/contact" method="post">
        <label for="name">Your name</label>
        <input id="name" name="name" autocomplete="name" required>

        <label for="email">Email</label>
        <input id="email" name="email" type="email" autocomplete="email" required>

        <label for="topic">Topic</label>
        <select id="topic" name="topic">
          <option value="order">Order</option>
          <option value="catering">Catering</option>
          <option value="other">Other</option>
        </select>

        <label for="message">Message</label>
        <textarea id="message" name="message" rows="5" minlength="20" required></textarea>

        <button type="submit">Send message</button>
      </form>
    </main>

    <footer>
      <h2>Opening hours</h2>
      <ul>
        <li>Tuesday–Friday: 7:00–16:00</li>
        <li>Saturday: 8:00–14:00</li>
      </ul>
    </footer>
  </body>
</html>
```

Submitting empty highlights the first required field; a bad email triggers the email format message; a short message reports the `minlength` requirement. `aria-current="page"` marks the current page's link for screen readers.

## Key takeaways

- Every page needs a doctype, `lang`, a UTF-8 `charset`, a viewport meta tag and a specific `<title>`.
- Choose elements for meaning: landmarks like `<header>`, `<nav>`, `<main>` and `<footer>` structure the page for everyone.
- Headings form the outline: one `<h1>`, logical nesting, no skipped levels, size set in CSS.
- Links navigate, buttons act; give images meaningful `alt` text or `alt=""` if decorative.
- Form fields need a `name` to submit and a `<label>` to be usable; built-in validation helps users but never replaces server checks.

## Quiz

1. A form has <input id="city" type="text" required> and submits successfully, but the server never receives the city. Why?

   A. The input has no name attribute
   B. The input should use type="city"
   C. required prevents the value from being sent
   D. The form uses method="post"

   **Answer: A.** Only fields with a name are included in the submitted data; the id is for labels, CSS and scripts. There is no city input type, and required and post both work fine.

2. Which heading structure is correct for a page about tea with two subtopics?

   A. h2 Tea, then h4 Green, h4 Black
   B. h1 Tea, then h1 Green, h1 Black
   C. h3 Tea, then h1 Green, h1 Black
   D. h1 Tea, then h2 Green, h2 Black

   **Answer: D.** The page topic is the single h1 and subtopics are the next level down. Skipping levels or using multiple h1s for subsections breaks the outline that assistive technology relies on.

3. A "Show password" <button> inside a form submits the form when clicked. What fixes it?

   A. Replace it with a <div> that has a click handler
   B. Add type="button" to the button
   C. Move the button into the <head>
   D. Add required to the password field

   **Answer: B.** Buttons inside a form default to type="submit". type="button" makes it do nothing by default so your script can handle it. A div loses keyboard support.

4. An image is purely decorative. What should its alt attribute be?

   A. Omit alt entirely
   B. alt="image"
   C. alt="" (empty)
   D. alt="decorative"

   **Answer: C.** An empty alt tells assistive technology to skip the image. Omitting alt can cause screen readers to read the file name, and words like "image" or "decorative" just add noise.
